diff --git a/app/src/app.rs b/app/src/app.rs index 0fa900d..366e64b 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -28,15 +28,18 @@ use cosmic::{ }, Element, ElementExt, }; +use page::Page; use crate::{ config::Config, pages::{ desktop::{ self, + dock::{self, applets::ADD_DOCK_APPLET_DIALOGUE_ID}, panel::{ self, - applets::{self, APPLET_DND_ICON_ID}, + applets_inner::{self, AppletsPage, APPLET_DND_ICON_ID}, + inner as _panel, }, }, input::{self, keyboard}, @@ -125,8 +128,8 @@ impl Application for SettingsApp { // app.insert_page::(); let desktop_id = app.insert_page::().id(); - // app.insert_page::(); - // app.insert_page::(); + app.insert_page::(); + app.insert_page::(); // app.insert_page::(); @@ -175,13 +178,13 @@ impl Application for SettingsApp { wayland::OutputEvent::Created(Some(info)), o, ))) if info.name.is_some() => Some(Message::PageMessage(crate::pages::Message::Panel( - panel::Message::OutputAdded(info.name.unwrap(), o), + panel::Message(_panel::Message::OutputAdded(info.name.unwrap(), o)), ))), iced::Event::PlatformSpecific(PlatformSpecific::Wayland(wayland::Event::Output( wayland::OutputEvent::Removed, o, ))) => Some(Message::PageMessage(crate::pages::Message::Panel( - panel::Message::OutputRemoved(o), + panel::Message(_panel::Message::OutputRemoved(o)), ))), _ => None, }); @@ -224,7 +227,9 @@ impl Application for SettingsApp { return self.search.focus(); } }, - Message::Page(page) => return self.activate_page(page), + Message::Page(page) => { + return self.activate_page(page); + } Message::Drag => return start_drag_window(window::Id(0)), Message::Close => { process::exit(0); @@ -257,7 +262,6 @@ impl Application for SettingsApp { Message::Search(search::Message::Clear) => { self.search_clear(); } - Message::None | Message::Search(_) => {} Message::PageMessage(message) => match message { crate::pages::Message::About(message) => { page::update!(self.pages, message, system::about::Page); @@ -285,8 +289,16 @@ impl Application for SettingsApp { crate::pages::Message::Panel(message) => { page::update!(self.pages, message, panel::Page); } - crate::pages::Message::Applet(message) => { - if let Some(page) = self.pages.page_mut::() { + crate::pages::Message::PanelApplet(message) => { + if let Some(page) = self.pages.page_mut::() { + return page.update(message, applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID); + } + } + crate::pages::Message::Dock(message) => { + page::update!(self.pages, message, dock::Page); + } + crate::pages::Message::DockApplet(message) => { + if let Some(page) = self.pages.page_mut::() { return page.update(message); } } @@ -295,29 +307,54 @@ impl Application for SettingsApp { self.sharp_corners = matches!(state, WindowState::Activated); } Message::PanelConfig(config) if config.name.to_lowercase().contains("panel") => { - if let Some(page) = self.pages.page_mut::() { - page.update(panel::Message::PanelConfig(config.clone())); - } - if let Some(page) = self.pages.page_mut::() { - _ = page.update(applets::Message::PanelConfig(config)); + page::update!( + self.pages, + panel::Message(_panel::Message::PanelConfig(config.clone())), + panel::Page + ); + + if let Some(page) = self.pages.page_mut::() { + return page.update( + applets_inner::Message::PanelConfig(config), + applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID, + ); } } - Message::PanelConfig(_) => {} // ignore other config changes for now, + Message::PanelConfig(config) if config.name.to_lowercase().contains("dock") => { + page::update!( + self.pages, + dock::Message::Inner(_panel::Message::PanelConfig(config.clone(),)), + dock::Page + ); + page::update!( + self.pages, + dock::applets::Message(applets_inner::Message::PanelConfig(config,)), + dock::applets::Page + ); + } Message::DesktopInfo => { - if let Some(page) = self.pages.page_mut::() { - // collect the potential applets - ret = page.update(applets::Message::Applets( - freedesktop_desktop_entry::Iter::new( - freedesktop_desktop_entry::default_paths(), - ) - .filter_map(|p| applets::Applet::try_from(Cow::from(p)).ok()) - .collect(), - )); + let info_list: Vec<_> = freedesktop_desktop_entry::Iter::new( + freedesktop_desktop_entry::default_paths(), + ) + .filter_map(|p| applets_inner::Applet::try_from(Cow::from(p)).ok()) + .collect(); + + page::update!( + self.pages, + dock::applets::Message(applets_inner::Message::Applets(info_list.clone())), + dock::applets::Page + ); + if let Some(page) = self.pages.page_mut::() { + return page.update( + applets_inner::Message::Applets(info_list), + applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID, + ); } } Message::ThemeChanged(theme) => { self.theme = theme; } + Message::PanelConfig(_) | Message::None | Message::Search(_) => {} // Ignored } ret } @@ -325,14 +362,21 @@ impl Application for SettingsApp { #[allow(clippy::too_many_lines)] fn view(&self, id: window::Id) -> Element { if let Some(Some(page)) = - (id == APPLET_DND_ICON_ID).then(|| self.pages.page::()) + (id == APPLET_DND_ICON_ID).then(|| self.pages.page::()) { return page.dnd_icon(); } - if let Some(Some(page)) = - (id == applets::ADD_APPLET_DIALOGUE_ID).then(|| self.pages.page::()) + if let Some(Some(page)) = (id == applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID) + .then(|| self.pages.page::()) { - return page.add_applet_view(); + return page.add_applet_view(crate::pages::Message::PanelApplet); + } + if let Some(Some(page)) = + (id == ADD_DOCK_APPLET_DIALOGUE_ID).then(|| self.pages.page::()) + { + return page.inner().add_applet_view(|msg| { + crate::pages::Message::DockApplet(dock::applets::Message(msg)) + }); } if let Some(Some(page)) = (id == keyboard::ADD_INPUT_SOURCE_DIALOGUE_ID).then(|| self.pages.page::()) @@ -439,10 +483,14 @@ impl Application for SettingsApp { fn close_requested(&self, id: window::Id) -> Self::Message { if id == window::Id(0) { Message::Close - } else if id == applets::ADD_APPLET_DIALOGUE_ID { - Message::PageMessage(crate::pages::Message::Applet( - applets::Message::ClosedAppletDialogue, + } else if id == applets_inner::ADD_PANEL_APPLET_DIALOGUE_ID { + Message::PageMessage(crate::pages::Message::PanelApplet( + applets_inner::Message::ClosedAppletDialogue, )) + } else if id == ADD_DOCK_APPLET_DIALOGUE_ID { + Message::PageMessage(crate::pages::Message::DockApplet(dock::applets::Message( + applets_inner::Message::ClosedAppletDialogue, + ))) } else { Message::None } diff --git a/app/src/pages/desktop/dock.rs b/app/src/pages/desktop/dock.rs deleted file mode 100644 index 1e2cd97..0000000 --- a/app/src/pages/desktop/dock.rs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2023 System76 -// SPDX-License-Identifier: GPL-3.0-only - -use cosmic_settings_page::Section; -use cosmic_settings_page::{self as page, section}; -use slotmap::SlotMap; - -#[derive(Default)] -pub struct Page; - -impl page::Page for Page { - fn content( - &self, - sections: &mut SlotMap>, - ) -> Option { - Some(vec![sections.insert(Section::default())]) - } - - fn info(&self) -> page::Info { - page::Info::new("dock", "preferences-pop-desktop-dock-symbolic") - .title(fl!("dock")) - .description(fl!("dock", "desc")) - } -} - -impl page::AutoBind for Page {} diff --git a/app/src/pages/desktop/dock/applets.rs b/app/src/pages/desktop/dock/applets.rs new file mode 100644 index 0000000..f41480b --- /dev/null +++ b/app/src/pages/desktop/dock/applets.rs @@ -0,0 +1,82 @@ +use cosmic::{cosmic_config::CosmicConfigEntry, iced::window, iced_runtime::Command}; +use cosmic_panel_config::CosmicPanelConfig; +use cosmic_settings_page::{self as page, section, Section}; +use slotmap::SlotMap; +use std::borrow::Cow; + +use crate::{ + app, + pages::{ + self, + desktop::panel::applets_inner::{self, lists, AppletsPage, ReorderWidgetState}, + }, +}; + +pub const ADD_DOCK_APPLET_DIALOGUE_ID: window::Id = window::Id(1002); + +pub(crate) struct Page { + inner: applets_inner::Page, +} + +impl Default for Page { + fn default() -> Self { + let config_helper = CosmicPanelConfig::cosmic_config("Dock").ok(); + let current_config = config_helper.as_ref().and_then(|config_helper| { + let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?; + // If the config is not present, it will be created with the default values and the name will not match + (panel_config.name == "Dock").then_some(panel_config) + }); + Self { + inner: applets_inner::Page { + available_entries: freedesktop_desktop_entry::Iter::new( + freedesktop_desktop_entry::default_paths(), + ) + .filter_map(|p| applets_inner::Applet::try_from(Cow::from(p)).ok()) + .collect(), + config_helper, + current_config, + reorder_widget_state: ReorderWidgetState::default(), + search: String::new(), + has_dialogue: false, + }, + } + } +} + +impl AppletsPage for Page { + fn inner(&self) -> &applets_inner::Page { + &self.inner + } + + fn inner_mut(&mut self) -> &mut applets_inner::Page { + &mut self.inner + } +} + +#[derive(Debug, Clone)] +pub struct Message(pub applets_inner::Message); + +impl Page { + pub fn update(&mut self, message: Message) -> Command { + self.inner.update(message.0, ADD_DOCK_APPLET_DIALOGUE_ID) + } +} + +impl page::Page for Page { + #[allow(clippy::too_many_lines)] + fn content( + &self, + sections: &mut SlotMap>, + ) -> Option { + Some(vec![sections.insert(lists::(|msg| { + pages::Message::DockApplet(Message(msg)) + }))]) + } + + fn info(&self) -> page::Info { + page::Info::new("dock_applets", "preferences-pop-desktop-dock-symbolic") + // .title(fl!("applets")) + } +} + +impl page::AutoBind for Page {} diff --git a/app/src/pages/desktop/dock/mod.rs b/app/src/pages/desktop/dock/mod.rs new file mode 100644 index 0000000..2606c78 --- /dev/null +++ b/app/src/pages/desktop/dock/mod.rs @@ -0,0 +1,166 @@ +use std::collections::HashMap; + +use apply::Apply; +use cosmic::{ + cosmic_config::CosmicConfigEntry, + widget::{settings, text, toggler}, + Element, +}; +use cosmic_panel_config::{CosmicPanelConfig, CosmicPanelContainerConfig}; +use cosmic_settings_page::{self as page, section, Section}; +use slotmap::SlotMap; + +use crate::pages::desktop::panel::inner::{add_panel, behavior_and_position, configuration, style}; + +use super::panel::inner::{self, PageInner, PanelPage}; + +pub mod applets; + +pub struct Page { + inner: PageInner, +} + +#[derive(Clone, Debug)] +pub enum Message { + EnableDock(bool), + Inner(inner::Message), +} + +impl Page { + pub fn update(&mut self, message: Message) { + match message { + Message::EnableDock(enabled) => { + let Some(container_config) = self.inner.container_config.as_mut() else { + return; + }; + let Some(panel_config) = self.inner.panel_config.as_ref() else { + return; + }; + if enabled { + container_config.config_list.push(panel_config.clone()); + } else { + container_config + .config_list + .retain(|c| c.name.as_str() != "Dock"); + } + _ = container_config.write_entries(); + } + Message::Inner(inner) => { + self.inner.update(inner); + } + }; + } +} + +impl page::AutoBind for Page { + fn sub_pages(page: page::Insert) -> page::Insert { + page.sub_page::() + } +} + +impl PanelPage for Page { + fn inner(&self) -> &PageInner { + &self.inner + } + + fn inner_mut(&mut self) -> &mut PageInner { + &mut self.inner + } + + fn autohide_label(&self) -> String { + fl!("panel-behavior-and-position", "dock-autohide") + } + + fn gap_label(&self) -> String { + fl!("panel-style", "dock-anchor-gap") + } + + fn extend_label(&self) -> String { + fl!("panel-style", "dock-extend") + } + + fn configure_applets_label(&self) -> String { + fl!("panel-applets", "dock-desc") + } + + fn applets_page_id(&self) -> &'static str { + "dock_applets" + } +} + +impl Default for Page { + fn default() -> Self { + // TODO CosmicPanelConfig should return its own version + let config_helper = CosmicPanelConfig::cosmic_config("Dock").ok(); + let panel_config = config_helper.as_ref().and_then(|config_helper| { + let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?; + // If the config is not present, it will be created with the default values and the name will not match + (panel_config.name == "Dock").then_some(panel_config) + }); + let container_config = CosmicPanelContainerConfig::load().ok(); + Self { + inner: PageInner { + config_helper, + panel_config, + container_config, + outputs: HashMap::new(), + }, + } + } +} + +pub(crate) fn enable() -> Section { + Section::default() + .descriptions(vec![fl!("dock")]) + .view::(|_binder, page, section| { + let descriptions = §ion.descriptions; + let Some(container_config) = page.inner.container_config.as_ref() else { + return Element::from(text(fl!("unknown"))); + }; + settings::view_section(§ion.title) + .add(settings::item( + &descriptions[0], + toggler( + None, + container_config + .config_list + .iter() + .any(|e| e.name.as_str() == "Dock"), + Message::EnableDock, + ), + )) + .apply(Element::from) + .map(crate::pages::Message::Dock) + }) +} +// TODO cleanup +impl page::Page for Page { + #[allow(clippy::too_many_lines)] + fn content( + &self, + sections: &mut SlotMap>, + ) -> Option { + Some(if self.inner.panel_config.is_some() { + vec![ + sections.insert(enable()), + sections.insert(behavior_and_position::(self, |m| { + crate::pages::Message::Dock(Message::Inner(m)) + })), + sections.insert(style::(self, |m| { + crate::pages::Message::Dock(Message::Inner(m)) + })), + sections.insert(configuration::(self)), + ] + } else { + vec![sections.insert(add_panel::(|m| { + crate::pages::Message::Dock(Message::Inner(m)) + }))] + }) + } + + fn info(&self) -> page::Info { + page::Info::new("dock", "preferences-pop-desktop-dock-symbolic") + .title(fl!("dock")) + .description(fl!("dock", "desc")) + } +} diff --git a/app/src/pages/desktop/panel/applets.rs b/app/src/pages/desktop/panel/applets_inner.rs similarity index 95% rename from app/src/pages/desktop/panel/applets.rs rename to app/src/pages/desktop/panel/applets_inner.rs index 2b78235..0a524fa 100644 --- a/app/src/pages/desktop/panel/applets.rs +++ b/app/src/pages/desktop/panel/applets_inner.rs @@ -1,15 +1,6 @@ -use std::{ - borrow::{Borrow, Cow}, - fmt::Debug, - mem, - path::{Path, PathBuf}, - str::FromStr, - sync::Arc, -}; - use apply::Apply; use cosmic::{ - cosmic_config::{self, Config, CosmicConfigEntry}, + cosmic_config::{Config, CosmicConfigEntry}, iced::{ alignment::{Horizontal, Vertical}, event::{ @@ -47,6 +38,14 @@ use cosmic::{ widget::{button, header_bar, icon, list_column}, Element, }; +use std::{ + borrow::{Borrow, Cow}, + fmt::Debug, + mem, + path::{Path, PathBuf}, + str::FromStr, + sync::Arc, +}; use crate::{app, pages}; use cosmic_panel_config::CosmicPanelConfig; @@ -70,28 +69,31 @@ const SPACING: f32 = 8.0; const DRAG_START_DISTANCE_SQUARED: f32 = 64.0; pub const APPLET_DND_ICON_ID: window::Id = window::Id(1000); -pub const ADD_APPLET_DIALOGUE_ID: window::Id = window::Id(1001); +pub const ADD_PANEL_APPLET_DIALOGUE_ID: window::Id = window::Id(1001); pub struct Page { - available_entries: Vec>, - config_helper: Option, - current_config: Option, - reorder_widget_state: ReorderWidgetState, - search: String, - has_dialogue: bool, + pub(crate) available_entries: Vec>, + pub(crate) config_helper: Option, + pub(crate) current_config: Option, + pub(crate) reorder_widget_state: ReorderWidgetState, + pub(crate) search: String, + pub(crate) has_dialogue: bool, } impl Default for Page { fn default() -> Self { - let config_helper = cosmic_config::Config::new("com.system76.CosmicPanel.Panel", 1).ok(); + let config_helper = CosmicPanelConfig::cosmic_config("Panel").ok(); let current_config = config_helper.as_ref().and_then(|config_helper| { - // TODO error handling... let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?; // If the config is not present, it will be created with the default values and the name will not match (panel_config.name == "Panel").then_some(panel_config) }); Self { - available_entries: Vec::new(), + available_entries: freedesktop_desktop_entry::Iter::new( + freedesktop_desktop_entry::default_paths(), + ) + .filter_map(|p| Applet::try_from(Cow::from(p)).ok()) + .collect(), config_helper, current_config, reorder_widget_state: ReorderWidgetState::default(), @@ -101,13 +103,31 @@ impl Default for Page { } } +pub trait AppletsPage { + fn inner(&self) -> &Page; + + fn inner_mut(&mut self) -> &mut Page; +} + +impl AppletsPage for Page { + fn inner(&self) -> &Page { + self + } + + fn inner_mut(&mut self) -> &mut Page { + self + } +} + impl page::Page for Page { #[allow(clippy::too_many_lines)] fn content( &self, sections: &mut SlotMap>, ) -> Option { - Some(vec![sections.insert(lists())]) + Some(vec![ + sections.insert(lists::(pages::Message::PanelApplet)) + ]) } fn info(&self) -> page::Info { @@ -141,7 +161,6 @@ pub enum Message { DragAppletDialogue, Save, Cancel, - Ignore, } impl Debug for Message { @@ -154,7 +173,6 @@ impl Debug for Message { Message::PanelConfig(_) => write!(f, "PanelConfig"), Message::StartDnd(_) => write!(f, "StartDnd"), Message::DnDCommand(_) => write!(f, "DnDCommand"), - Message::Ignore => write!(f, "Ignore"), Message::Save => write!(f, "ApplyReorder"), Message::RemoveStart(_) => write!(f, "RemoveStart"), Message::RemoveCenter(_) => write!(f, "RemoveCenter"), @@ -195,7 +213,10 @@ impl Page { #[must_use] #[allow(clippy::too_many_lines)] - pub fn add_applet_view(&self) -> Element { + pub fn add_applet_view crate::pages::Message + Copy + 'static>( + &self, + msg_map: T, + ) -> Element { let mut list_column = list_column(); let mut has_some = false; for info in self @@ -245,9 +266,9 @@ impl Page { }) }) .padding(8.0) - .on_press(app::Message::PageMessage(pages::Message::Applet( - Message::AddApplet(info.clone()) - ))), + .on_press(app::Message::PageMessage(msg_map(Message::AddApplet( + info.clone() + )))), ] .padding([0, 32, 0, 32]) .spacing(12) @@ -264,10 +285,10 @@ impl Page { column![ header_bar() .title(fl!("add-applet")) - .on_close(app::Message::PageMessage(pages::Message::Applet( + .on_close(app::Message::PageMessage(msg_map( Message::CloseAppletDialogue ))) - .on_drag(app::Message::PageMessage(pages::Message::Applet( + .on_drag(app::Message::PageMessage(msg_map( Message::DragAppletDialogue ))), container( @@ -284,15 +305,11 @@ impl Page { spacing: 12.0, side: Side::Left, }) - .on_input(|s| { - app::Message::PageMessage(pages::Message::Applet(Message::Search( - s, - ))) + .on_input(move |s| { + app::Message::PageMessage(msg_map(Message::Search(s))) }) - .on_paste(|s| { - app::Message::PageMessage(pages::Message::Applet(Message::Search( - s, - ))) + .on_paste(move |s| { + app::Message::PageMessage(msg_map(Message::Search(s))) }) .width(Length::Fixed(312.0)), list_column @@ -312,7 +329,7 @@ impl Page { } #[allow(clippy::too_many_lines)] - pub fn update(&mut self, message: Message) -> Command { + pub fn update(&mut self, message: Message, window_id: window::Id) -> Command { match message { Message::PanelConfig(c) => { self.current_config = Some(c); @@ -349,6 +366,7 @@ impl Page { *list = end_list.into_iter().map(|a| a.id.into()).collect(); } Message::Applets(applets) => { + dbg!(&applets); self.available_entries = applets; } Message::StartDnd(state) => { @@ -358,7 +376,6 @@ impl Page { Message::DnDCommand(action) => { return data_device_action(action()); } - Message::Ignore => {} Message::Save => { self.reorder_widget_state = ReorderWidgetState::default(); self.save(); @@ -429,12 +446,12 @@ impl Page { list.push(applet.id.to_string()); self.save(); - return commands::window::close_window(ADD_APPLET_DIALOGUE_ID); + return commands::window::close_window(window_id); } Message::AddAppletDialogue => { self.has_dialogue = true; let window_settings = SctkWindowSettings { - window_id: ADD_APPLET_DIALOGUE_ID, + window_id, app_id: Some("com.system76.CosmicSettings".to_string()), title: Some(fl!("add-applet")), parent: Some(window::Id(0)), @@ -456,10 +473,10 @@ impl Page { } Message::CloseAppletDialogue => { self.has_dialogue = false; - return commands::window::close_window(ADD_APPLET_DIALOGUE_ID); + return commands::window::close_window(window_id); } Message::DragAppletDialogue => { - return commands::window::start_drag_window(ADD_APPLET_DIALOGUE_ID); + return commands::window::start_drag_window(window_id); } }; Command::none() @@ -467,13 +484,20 @@ impl Page { } #[allow(clippy::too_many_lines)] -pub fn lists() -> Section { - Section::default().view::(|_binder, page, _section| { +pub fn lists< + P: page::Page + AppletsPage, + T: Fn(Message) -> crate::pages::Message + Copy + 'static, +>( + msg_map: T, +) -> Section { + Section::default().view::

(move |_binder, page, _section| { + let page = page.inner(); let Some(config) = page.current_config.as_ref() else { return Element::from( text(fl!("unknown")) ); }; + let button = cosmic::iced::widget::button(text(fl!("add-applet"))) .style(theme::Button::Secondary) .padding(8.0); @@ -510,7 +534,7 @@ pub fn lists() -> Section { Message::ReorderStart, Message::Save, Message::Cancel, - page.reorder_widget_state.dragged_applet() + page.reorder_widget_state.dragged_applet().as_ref() ) ] .spacing(8.0), @@ -537,7 +561,7 @@ pub fn lists() -> Section { Message::ReorderCenter, Message::Save, Message::Cancel, - page.reorder_widget_state.dragged_applet() + page.reorder_widget_state.dragged_applet().as_ref() ) ] .spacing(8.0), @@ -565,7 +589,7 @@ pub fn lists() -> Section { Message::ReorderEnd, Message::Save, Message::Cancel, - page.reorder_widget_state.dragged_applet() + page.reorder_widget_state.dragged_applet().as_ref() ) ] .spacing(8.0), @@ -573,7 +597,7 @@ pub fn lists() -> Section { .padding([0, 16, 0, 16]) .spacing(12.0) .apply(Element::from) - .map(pages::Message::Applet) + .map(msg_map) }) } @@ -587,6 +611,7 @@ pub struct Applet<'a> { } impl Applet<'_> { + #[must_use] pub fn matches(&self, query: &str) -> bool { self.name.contains(query) || self.description.contains(query) || self.id.contains(query) } @@ -667,9 +692,8 @@ impl<'a, Message: 'static + Clone> AppletReorderList<'a, Message> { on_reorder: impl Fn(Vec>) -> Message + 'a, on_apply_reorder: Message, on_cancel: Message, - active_dnd: Option>, // state: Option<&State>, + active_dnd: Option<&Applet<'a>>, ) -> Self { - // let dragged_path = state.and_then(|state| state.dragged_applet()); let applet_buttons = info .clone() .into_iter() @@ -1057,7 +1081,6 @@ where ))); let data = match &state.dragging_state { DraggingState::Dragging(a) => Some(a.clone()), - _ => { shell.publish((self.on_dnd_command_produced.as_ref())(Box::new( move || { @@ -1087,7 +1110,7 @@ where shell.publish((self.on_reorder.as_ref())( filtered .into_iter() - .map(pages::desktop::panel::applets::Applet::into_owned) + .map(pages::desktop::panel::applets_inner::Applet::into_owned) .collect(), )); } @@ -1225,7 +1248,7 @@ where shell.publish((self.on_reorder.as_ref())( reordered_list .into_iter() - .map(pages::desktop::panel::applets::Applet::into_owned) + .map(pages::desktop::panel::applets_inner::Applet::into_owned) .collect(), )); } @@ -1292,7 +1315,7 @@ where shell.publish((self.on_reorder.as_ref())( filtered .into_iter() - .map(pages::desktop::panel::applets::Applet::into_owned) + .map(pages::desktop::panel::applets_inner::Applet::into_owned) .collect(), )); } diff --git a/app/src/pages/desktop/panel/inner.rs b/app/src/pages/desktop/panel/inner.rs new file mode 100644 index 0000000..67e84c5 --- /dev/null +++ b/app/src/pages/desktop/panel/inner.rs @@ -0,0 +1,393 @@ +use cosmic::{ + cosmic_config::{self, CosmicConfigEntry}, + iced::widget::{button, container, horizontal_space, pick_list, row}, + iced::Length, + iced_widget::slider, + sctk::reexports::client::{backend::ObjectId, protocol::wl_output::WlOutput, Proxy}, + theme, + widget::{icon, list, settings, text, toggler}, + Element, +}; + +use apply::Apply; +use cosmic_panel_config::{ + AutoHide, CosmicPanelBackground, CosmicPanelConfig, CosmicPanelContainerConfig, + CosmicPanelOuput, PanelAnchor, PanelSize, +}; +use cosmic_settings_page::{self as page, section, Section}; +use std::{borrow::Cow, collections::HashMap}; + +pub struct PageInner { + pub(crate) config_helper: Option, + pub(crate) panel_config: Option, + pub(crate) container_config: Option, + // TODO move these into panel config + pub(crate) outputs: HashMap, +} + +pub trait PanelPage { + fn inner(&self) -> &PageInner; + + fn inner_mut(&mut self) -> &mut PageInner; + + fn autohide_label(&self) -> String; + + fn gap_label(&self) -> String; + + fn extend_label(&self) -> String; + + fn configure_applets_label(&self) -> String; + + fn applets_page_id(&self) -> &'static str; +} + +pub(crate) fn behavior_and_position< + P: page::Page + PanelPage, + T: Fn(Message) -> crate::pages::Message + Copy + 'static, +>( + p: &P, + msg_map: T, +) -> Section { + Section::default() + .title(fl!("panel-behavior-and-position")) + .descriptions(vec![ + p.autohide_label(), + fl!("panel-behavior-and-position", "position"), + fl!("panel-behavior-and-position", "display"), + ]) + .view::

(move |_binder, page, section| { + let descriptions = §ion.descriptions; + let page = page.inner(); + let Some(panel_config) = page.panel_config.as_ref() else { + return Element::from(text(fl!("unknown"))); + }; + settings::view_section(§ion.title) + .add(settings::item( + &descriptions[0], + toggler(None, panel_config.autohide.is_some(), |value| { + Message::AutoHidePanel(value) + }), + )) + .add(settings::item( + &descriptions[1], + pick_list( + Cow::from(vec![ + Anchor(PanelAnchor::Top), + Anchor(PanelAnchor::Left), + Anchor(PanelAnchor::Right), + Anchor(PanelAnchor::Bottom), + ]), + Some(Anchor(panel_config.anchor)), + |a| Message::PanelAnchor(a.0), + ), + )) + .add(settings::item( + &descriptions[2], + pick_list( + Cow::from( + Some(fl!("all")) + .into_iter() + .chain(page.outputs.values().map(|(name, _)| name.clone())) + .collect::>(), + ), + match &panel_config.output { + CosmicPanelOuput::All => Some(fl!("all")), + CosmicPanelOuput::Active => None, + CosmicPanelOuput::Name(ref name) => Some(name.clone()), + }, + Message::Output, + ), + )) + .apply(Element::from) + .map(msg_map) + }) +} + +pub(crate) fn style< + P: page::Page + PanelPage, + T: Fn(Message) -> crate::pages::Message + Copy + 'static, +>( + p: &P, + msg_map: T, +) -> Section { + Section::default() + .title(fl!("panel-style")) + .descriptions(vec![ + p.gap_label(), + p.extend_label(), + fl!("panel-style", "appearance"), + fl!("panel-style", "size"), + fl!("panel-style", "background-opacity"), + ]) + .view::

(move |_binder, page, section| { + let descriptions = §ion.descriptions; + let inner = page.inner(); + let Some(panel_config) = inner.panel_config.as_ref() else { + return Element::from(text(fl!("unknown"))); + }; + settings::view_section(§ion.title) + .add(settings::item( + &descriptions[0], + toggler(None, panel_config.anchor_gap, |value| { + Message::AnchorGap(value) + }), + )) + .add(settings::item( + &descriptions[1], + toggler(None, panel_config.expand_to_edges, |value| { + Message::ExtendToEdge(value) + }), + )) + .add(settings::item( + &descriptions[2], + pick_list( + Cow::from(vec![Appearance::Match, Appearance::Light, Appearance::Dark]), + panel_config.background.clone().try_into().ok(), + Message::Appearance, + ), + )) + .add(settings::item( + &descriptions[3], + // TODO custom discrete slider variant + row![ + text(fl!("small")), + slider( + 0..=4, + match panel_config.size { + PanelSize::XS => 0, + PanelSize::S => 1, + PanelSize::M => 2, + PanelSize::L => 3, + PanelSize::XL => 4, + }, + |v| { + if v == 0 { + Message::PanelSize(PanelSize::XS) + } else if v == 1 { + Message::PanelSize(PanelSize::S) + } else if v == 2 { + Message::PanelSize(PanelSize::M) + } else if v == 3 { + Message::PanelSize(PanelSize::L) + } else { + Message::PanelSize(PanelSize::XL) + } + }, + ), + text(fl!("large")) + ] + .spacing(12), + )) + .add(settings::item( + &descriptions[4], + row![ + text(fl!("number", HashMap::from_iter(vec![("number", 0)]))), + slider(0..=100, (panel_config.opacity * 100.0) as i32, |v| { + Message::Opacity(v as f32 / 100.0) + },), + text(fl!("number", HashMap::from_iter(vec![("number", 100)]))), + ] + .spacing(12), + )) + .apply(Element::from) + .map(msg_map) + }) +} + +pub(crate) fn configuration + PanelPage>( + p: &P, +) -> Section { + Section::default() + .title(fl!("panel-applets")) + .descriptions(vec![p.configure_applets_label()]) + .view::

(move |binder, page, section| { + let mut settings = settings::view_section(§ion.title); + let descriptions = §ion.descriptions; + settings = if let Some((panel_applets_entity, _panel_applets_info)) = binder + .info + .iter() + .find(|(_, v)| v.id == page.applets_page_id()) + { + settings.add( + settings::item::builder(&descriptions[0]) + .control(row!( + horizontal_space(Length::Fill), + icon("go-next-symbolic", 20).style(theme::Svg::Symbolic) + )) + .spacing(16) + .apply(container) + .style(theme::Container::custom(list::column::style)) + .apply(button) + .padding(0) + .style(theme::Button::Transparent) + .on_press(crate::pages::Message::Page(panel_applets_entity)), + ) + } else { + settings + }; + + Element::from(settings) + }) +} + +#[allow(clippy::module_name_repetitions)] +pub(crate) fn add_panel< + P: page::Page + PanelPage, + T: Fn(Message) -> crate::pages::Message + Copy + 'static, +>( + msg_map: T, +) -> Section { + Section::default() + .title(fl!("panel-missing")) + .descriptions(vec![ + fl!("panel-missing", "desc"), + fl!("panel-missing", "fix"), + ]) + .view::

(move |_binder, page, section| { + // _descriptions = §ion.descriptions; + settings::view_section(§ion.title) + .apply(Element::from) + .map(msg_map) + }) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct Anchor(PanelAnchor); + +impl ToString for Anchor { + fn to_string(&self) -> String { + match self.0 { + PanelAnchor::Top => fl!("panel-top"), + PanelAnchor::Bottom => fl!("panel-bottom"), + PanelAnchor::Left => fl!("panel-left"), + PanelAnchor::Right => fl!("panel-right"), + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Appearance { + Match, + Light, + Dark, +} + +impl ToString for Appearance { + fn to_string(&self) -> String { + match self { + Appearance::Match => fl!("panel-appearance", "match"), + Appearance::Light => fl!("panel-appearance", "light"), + Appearance::Dark => fl!("panel-appearance", "dark"), + } + } +} + +impl TryFrom for Appearance { + type Error = (); + fn try_from(value: CosmicPanelBackground) -> Result { + match value { + CosmicPanelBackground::ThemeDefault => Ok(Appearance::Match), + CosmicPanelBackground::Light => Ok(Appearance::Light), + CosmicPanelBackground::Dark => Ok(Appearance::Dark), + _ => Err(()), + } + } +} + +impl From for CosmicPanelBackground { + fn from(appearance: Appearance) -> Self { + match appearance { + Appearance::Match => CosmicPanelBackground::ThemeDefault, + Appearance::Light => CosmicPanelBackground::Light, + Appearance::Dark => CosmicPanelBackground::Dark, + } + } +} + +#[derive(Clone, Debug)] +pub enum Message { + // panel messages + AutoHidePanel(bool), + PanelAnchor(PanelAnchor), + Output(String), + AnchorGap(bool), + PanelSize(PanelSize), + Appearance(Appearance), + ExtendToEdge(bool), + Opacity(f32), + OutputAdded(String, WlOutput), + OutputRemoved(WlOutput), + PanelConfig(CosmicPanelConfig), +} + +impl PageInner { + #[allow(clippy::too_many_lines)] + pub fn update(&mut self, message: Message) { + let helper = self.config_helper.as_ref().unwrap(); + let mut panel_config = self.panel_config.as_mut().unwrap(); + + match message { + Message::AutoHidePanel(enabled) => { + if enabled { + panel_config.exclusive_zone = false; + panel_config.autohide = Some(AutoHide { + wait_time: 1000, + transition_time: 200, + handle_size: 4, + }); + } else { + panel_config.exclusive_zone = true; + panel_config.autohide = None; + } + } + Message::PanelAnchor(anchor) => { + panel_config.anchor = anchor; + } + Message::Output(name) => { + panel_config.output = match name { + s if s == fl!("all") => CosmicPanelOuput::All, + _ => CosmicPanelOuput::Name(name), + }; + } + Message::AnchorGap(enabled) => { + panel_config.anchor_gap = enabled; + + if enabled { + panel_config.margin = 4; + } else { + panel_config.margin = 0; + } + } + Message::PanelSize(size) => { + panel_config.size = size; + } + Message::Appearance(a) => { + panel_config.background = a.into(); + } + Message::ExtendToEdge(enabled) => { + panel_config.expand_to_edges = enabled; + } + Message::Opacity(opacity) => { + panel_config.opacity = opacity; + } + Message::OutputAdded(name, output) => { + self.outputs.insert(output.id(), (name, output)); + } + Message::OutputRemoved(output) => { + self.outputs.remove(&output.id()); + } + Message::PanelConfig(c) => { + self.panel_config = Some(c); + return; + } + } + + if panel_config.anchor_gap || !panel_config.expand_to_edges { + panel_config.border_radius = 8; + } else { + panel_config.border_radius = 0; + } + + _ = panel_config.write_entry(helper); + } +} diff --git a/app/src/pages/desktop/panel/mod.rs b/app/src/pages/desktop/panel/mod.rs index 4371e12..29331de 100644 --- a/app/src/pages/desktop/panel/mod.rs +++ b/app/src/pages/desktop/panel/mod.rs @@ -1,64 +1,107 @@ -use cosmic::{ - cosmic_config::{self, CosmicConfigEntry}, - iced::widget::{button, container, horizontal_space, pick_list, row}, - iced::Length, - iced_widget::slider, - sctk::reexports::client::{backend::ObjectId, protocol::wl_output::WlOutput, Proxy}, - theme, - widget::{icon, list, settings, text, toggler}, - Element, -}; +use std::collections::HashMap; -use apply::Apply; -use cosmic_panel_config::{ - AutoHide, CosmicPanelBackground, CosmicPanelConfig, CosmicPanelOuput, PanelAnchor, PanelSize, -}; +use cosmic::cosmic_config::CosmicConfigEntry; +use cosmic_panel_config::{CosmicPanelConfig, CosmicPanelContainerConfig}; use cosmic_settings_page::{self as page, section, Section}; use slotmap::SlotMap; -use std::{borrow::Cow, collections::HashMap}; -pub mod applets; +use crate::pages::desktop::panel::inner::{add_panel, behavior_and_position, configuration, style}; + +use self::inner::{PageInner, PanelPage}; + +pub mod applets_inner; +pub mod inner; pub struct Page { - config_helper: Option, - panel_config: Option, - // TODO move these into panel config - pub outputs: HashMap, + inner: PageInner, +} + +#[derive(Clone, Debug)] +pub struct Message(pub inner::Message); + +impl Page { + pub fn update(&mut self, message: Message) { + self.inner.update(message.0); + } +} + +impl page::AutoBind for Page { + fn sub_pages(page: page::Insert) -> page::Insert { + page.sub_page::() + } +} + +impl PanelPage for Page { + fn inner(&self) -> &PageInner { + &self.inner + } + + fn inner_mut(&mut self) -> &mut PageInner { + &mut self.inner + } + + fn autohide_label(&self) -> String { + fl!("panel-behavior-and-position", "autohide") + } + + fn gap_label(&self) -> String { + fl!("panel-style", "anchor-gap") + } + + fn extend_label(&self) -> String { + fl!("panel-style", "extend") + } + + fn configure_applets_label(&self) -> String { + fl!("panel-applets", "desc") + } + + fn applets_page_id(&self) -> &'static str { + "panel_applets" + } } impl Default for Page { fn default() -> Self { - // TODO CosmicPanelConfig should return its own version - let config_helper = cosmic_config::Config::new("com.system76.CosmicPanel.Panel", 1).ok(); + let config_helper = CosmicPanelConfig::cosmic_config("Panel").ok(); let panel_config = config_helper.as_ref().and_then(|config_helper| { - // TODO error handling... let panel_config = CosmicPanelConfig::get_entry(config_helper).ok()?; // If the config is not present, it will be created with the default values and the name will not match (panel_config.name == "Panel").then_some(panel_config) }); - + let container_config = CosmicPanelContainerConfig::load().ok(); Self { - config_helper, - panel_config, - outputs: HashMap::new(), + inner: PageInner { + config_helper, + panel_config, + container_config, + outputs: HashMap::new(), + }, } } } +// TODO cleanup impl page::Page for Page { #[allow(clippy::too_many_lines)] fn content( &self, sections: &mut SlotMap>, ) -> Option { - Some(if self.panel_config.is_some() { + Some(if self.inner.panel_config.is_some() { vec![ - sections.insert(behavior_and_position()), - sections.insert(style()), - sections.insert(configuration()), + sections.insert(behavior_and_position::(self, |m| { + crate::pages::Message::Panel(Message(m)) + })), + sections.insert(style::(self, |m| { + crate::pages::Message::Panel(Message(m)) + })), + sections.insert(configuration::(self)), ] } else { - vec![sections.insert(add_panel())] + vec![sections.insert(add_panel::(|m| { + crate::pages::Message::Panel(Message(m)) + }))] }) } @@ -68,392 +111,3 @@ impl page::Page for Page { .description(fl!("panel", "desc")) } } - -impl page::AutoBind for Page { - fn sub_pages(page: page::Insert) -> page::Insert { - page.sub_page::() - } -} - -pub fn behavior_and_position() -> Section { - Section::default() - .title(fl!("panel-behavior-and-position")) - .descriptions(vec![ - fl!("panel-behavior-and-position", "autohide"), - fl!("panel-behavior-and-position", "position"), - fl!("panel-behavior-and-position", "display"), - ]) - .view::(|_binder, page, section| { - let descriptions = §ion.descriptions; - let Some(panel_config) = page.panel_config.as_ref() else { - return Element::from(text(fl!("unknown"))); - }; - settings::view_section(§ion.title) - .add(settings::item( - &descriptions[0], - toggler(None, panel_config.autohide.is_some(), |value| { - Message::AutoHidePanel(value) - }), - )) - .add(settings::item( - &descriptions[1], - pick_list( - Cow::from(vec![ - Anchor(PanelAnchor::Top), - Anchor(PanelAnchor::Left), - Anchor(PanelAnchor::Right), - Anchor(PanelAnchor::Bottom), - ]), - Some(Anchor(panel_config.anchor)), - |a| Message::PanelAnchor(a.0), - ), - )) - .add(settings::item( - &descriptions[2], - pick_list( - Cow::from( - Some(fl!("all")) - .into_iter() - .chain(page.outputs.values().map(|(name, _)| name.clone())) - .collect::>(), - ), - match &panel_config.output { - CosmicPanelOuput::All => Some(fl!("all")), - CosmicPanelOuput::Active => None, - CosmicPanelOuput::Name(ref name) => Some(name.clone()), - }, - Message::Output, - ), - )) - .apply(Element::from) - .map(crate::pages::Message::Panel) - }) -} - -pub fn style() -> Section { - Section::default() - .title(fl!("panel-style")) - .descriptions(vec![ - fl!("panel-style", "anchor-gap"), - fl!("panel-style", "extend"), - fl!("panel-style", "appearance"), - fl!("panel-style", "size"), - fl!("panel-style", "background-opacity"), - ]) - .view::(|_binder, page, section| { - let descriptions = §ion.descriptions; - let Some(panel_config) = page.panel_config.as_ref() else { - return Element::from(text(fl!("unknown"))); - }; - settings::view_section(§ion.title) - .add(settings::item( - &descriptions[0], - toggler(None, panel_config.anchor_gap, |value| { - Message::AnchorGap(value) - }), - )) - .add(settings::item( - &descriptions[1], - toggler(None, panel_config.expand_to_edges, |value| { - Message::ExtendToEdge(value) - }), - )) - .add(settings::item( - &descriptions[2], - pick_list( - Cow::from(vec![Appearance::Match, Appearance::Light, Appearance::Dark]), - panel_config.background.clone().try_into().ok(), - Message::Appearance, - ), - )) - .add(settings::item( - &descriptions[3], - // TODO custom discrete slider variant - row![ - text(fl!("small")), - slider( - 0..=4, - match panel_config.size { - PanelSize::XS => 0, - PanelSize::S => 1, - PanelSize::M => 2, - PanelSize::L => 3, - PanelSize::XL => 4, - }, - |v| { - if v == 0 { - Message::PanelSize(PanelSize::XS) - } else if v == 1 { - Message::PanelSize(PanelSize::S) - } else if v == 2 { - Message::PanelSize(PanelSize::M) - } else if v == 3 { - Message::PanelSize(PanelSize::L) - } else { - Message::PanelSize(PanelSize::XL) - } - }, - ), - text(fl!("large")) - ] - .spacing(12), - )) - .add(settings::item( - &descriptions[4], - row![ - text(fl!("number", HashMap::from_iter(vec![("number", 0)]))), - slider(0..=100, (panel_config.opacity * 100.0) as i32, |v| { - Message::Opacity(v as f32 / 100.0) - },), - text(fl!("number", HashMap::from_iter(vec![("number", 100)]))), - ] - .spacing(12), - )) - .apply(Element::from) - .map(crate::pages::Message::Panel) - }) -} - -pub fn configuration() -> Section { - Section::default() - .title(fl!("panel-applets")) - .descriptions(vec![fl!("panel-applets", "desc")]) - .view::(|binder, _page, section| { - let mut settings = settings::view_section(§ion.title); - settings = if let Some((panel_applets_entity, _panel_applets_info)) = - binder.info.iter().find(|(_, v)| v.id == "panel_applets") - { - settings.add( - settings::item::builder(fl!("panel-applets", "desc")) - .control(row!( - horizontal_space(Length::Fill), - icon("go-next-symbolic", 20).style(theme::Svg::Symbolic) - )) - .spacing(16) - .apply(container) - .style(theme::Container::custom(list::column::style)) - .apply(button) - .padding(0) - .style(theme::Button::Transparent) - .on_press(crate::pages::Message::Page(panel_applets_entity)), - ) - } else { - settings - }; - - Element::from(settings) - }) -} - -#[allow(clippy::module_name_repetitions)] -pub fn panel_dock_links() -> Section { - Section::default() - .title(fl!("desktop-panels-and-applets")) - .view::(|binder, _page, section| { - // TODO probably a way of getting the entity and its info - let mut settings = settings::view_section(§ion.title); - settings = if let Some((panel_entity, panel_info)) = - binder.info.iter().find(|(_, v)| v.id == "panel") - { - settings.add( - settings::item::builder(panel_info.title.clone()) - .description(panel_info.description.clone()) - .control(row!( - horizontal_space(Length::Fill), - icon("go-next-symbolic", 20).style(theme::Svg::Symbolic) - )) - .spacing(16) - .apply(container) - .style(theme::Container::custom(list::column::style)) - .apply(button) - .padding(0) - .style(theme::Button::Transparent) - .on_press(crate::pages::Message::Page(panel_entity)), - ) - } else { - settings - }; - - settings = if let Some((dock_entity, dock_info)) = - binder.info.iter().find(|(_, v)| v.id == "dock") - { - settings.add( - settings::item::builder(dock_info.title.clone()) - .description(dock_info.description.clone()) - .control(row!( - horizontal_space(Length::Fill), - icon("go-next-symbolic", 20).style(theme::Svg::Symbolic) - )) - .spacing(16) - .apply(container) - .style(theme::Container::custom(list::column::style)) - .apply(button) - .padding(0) - .style(theme::Button::Transparent) - .on_press(crate::pages::Message::Page(dock_entity)), - ) - } else { - settings - }; - - Element::from(settings) - }) -} - -#[allow(clippy::module_name_repetitions)] -pub fn add_panel() -> Section { - Section::default() - .title(fl!("panel-missing")) - .descriptions(vec![ - fl!("panel-missing", "desc"), - fl!("panel-missing", "fix"), - ]) - .view::(|_binder, _page, section| { - // _descriptions = §ion.descriptions; - settings::view_section(§ion.title) - .apply(Element::from) - .map(crate::pages::Message::Desktop) - }) -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Anchor(PanelAnchor); - -impl ToString for Anchor { - fn to_string(&self) -> String { - match self.0 { - PanelAnchor::Top => fl!("panel-top"), - PanelAnchor::Bottom => fl!("panel-bottom"), - PanelAnchor::Left => fl!("panel-left"), - PanelAnchor::Right => fl!("panel-right"), - } - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub enum Appearance { - Match, - Light, - Dark, -} - -impl ToString for Appearance { - fn to_string(&self) -> String { - match self { - Appearance::Match => fl!("panel-appearance", "match"), - Appearance::Light => fl!("panel-appearance", "light"), - Appearance::Dark => fl!("panel-appearance", "dark"), - } - } -} - -impl TryFrom for Appearance { - type Error = (); - fn try_from(value: CosmicPanelBackground) -> Result { - match value { - CosmicPanelBackground::ThemeDefault => Ok(Appearance::Match), - CosmicPanelBackground::Light => Ok(Appearance::Light), - CosmicPanelBackground::Dark => Ok(Appearance::Dark), - _ => Err(()), - } - } -} - -impl From for CosmicPanelBackground { - fn from(appearance: Appearance) -> Self { - match appearance { - Appearance::Match => CosmicPanelBackground::ThemeDefault, - Appearance::Light => CosmicPanelBackground::Light, - Appearance::Dark => CosmicPanelBackground::Dark, - } - } -} - -#[derive(Clone, Debug)] -pub enum Message { - // panel messages - AutoHidePanel(bool), - PanelAnchor(PanelAnchor), - Output(String), - AnchorGap(bool), - PanelSize(PanelSize), - Appearance(Appearance), - ExtendToEdge(bool), - Opacity(f32), - Applets, - OutputAdded(String, WlOutput), - OutputRemoved(WlOutput), - PanelConfig(CosmicPanelConfig), -} - -impl Page { - pub fn update(&mut self, message: Message) { - let helper = self.config_helper.as_ref().unwrap(); - let mut panel_config = self.panel_config.as_mut().unwrap(); - match message { - Message::AutoHidePanel(enabled) => { - if enabled { - panel_config.exclusive_zone = false; - panel_config.autohide = Some(AutoHide { - wait_time: 1000, - transition_time: 200, - handle_size: 4, - }); - } else { - panel_config.exclusive_zone = true; - panel_config.autohide = None; - } - } - Message::PanelAnchor(anchor) => { - panel_config.anchor = anchor; - } - Message::Output(name) => { - panel_config.output = match name { - s if s == fl!("all") => CosmicPanelOuput::All, - _ => CosmicPanelOuput::Name(name), - }; - } - Message::AnchorGap(enabled) => { - panel_config.anchor_gap = enabled; - - if enabled { - panel_config.margin = 4; - } else { - panel_config.margin = 0; - } - } - Message::PanelSize(size) => { - panel_config.size = size; - } - Message::Appearance(a) => { - panel_config.background = a.into(); - } - Message::ExtendToEdge(enabled) => { - panel_config.expand_to_edges = enabled; - } - Message::Opacity(opacity) => { - panel_config.opacity = opacity; - } - Message::Applets => todo!(), - - Message::OutputAdded(name, output) => { - self.outputs.insert(output.id(), (name, output)); - } - Message::OutputRemoved(output) => { - self.outputs.remove(&output.id()); - } - Message::PanelConfig(c) => { - self.panel_config = Some(c); - return; - } - } - - if panel_config.anchor_gap || !panel_config.expand_to_edges { - panel_config.border_radius = 8; - } else { - panel_config.border_radius = 0; - } - - _ = panel_config.write_entry(helper); - } -} diff --git a/app/src/pages/mod.rs b/app/src/pages/mod.rs index c265e0f..1e3ebc9 100644 --- a/app/src/pages/mod.rs +++ b/app/src/pages/mod.rs @@ -16,8 +16,10 @@ pub enum Message { DateAndTime(time::date::Message), Desktop(desktop::Message), Panel(desktop::panel::Message), + Dock(desktop::dock::Message), DesktopWallpaper(desktop::wallpaper::Message), - Applet(desktop::panel::applets::Message), + PanelApplet(desktop::panel::applets_inner::Message), + DockApplet(desktop::dock::applets::Message), Input(input::Message), External { id: String, message: Vec }, Page(Entity), diff --git a/app/src/subscription/desktop_files.rs b/app/src/subscription/desktop_files.rs index d994aa2..b497151 100644 --- a/app/src/subscription/desktop_files.rs +++ b/app/src/subscription/desktop_files.rs @@ -1,4 +1,7 @@ -use cosmic::iced::subscription; +use cosmic::{ + iced::subscription, + iced_futures::futures::{self, SinkExt}, +}; use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Watcher}; use std::fmt::Debug; use std::hash::Hash; @@ -21,19 +24,20 @@ pub enum DesktopFileEvent { pub fn desktop_files( id: I, -) -> cosmic::iced::Subscription<(I, DesktopFileEvent)> { - subscription::unfold(id, State::Ready, move |mut state| async move { +) -> cosmic::iced::Subscription { + subscription::channel(id, 50, move |mut output| async move { + let mut state = State::Ready; + loop { - let (event, new_state) = start_watching(id, state).await; - state = new_state; - if let Some(event) = event { - return (event, state); - } + state = start_watching(state, &mut output).await; } }) } -async fn start_watching(id: I, state: State) -> (Option<(I, DesktopFileEvent)>, State) { +async fn start_watching( + state: State, + output: &mut futures::channel::mpsc::Sender, +) -> State { match state { State::Ready => { let paths = freedesktop_desktop_entry::default_paths(); @@ -42,22 +46,19 @@ async fn start_watching(id: I, state: State) -> (Option<(I, DesktopFile for path in paths { let _ = watcher.watch(path.as_ref(), RecursiveMode::Recursive); } - ( - Some((id, DesktopFileEvent::Changed)), - State::Waiting { watcher, rx }, - ) + + _ = output.send(DesktopFileEvent::Changed).await; + State::Waiting { watcher, rx } } else { - (None, State::Finished) + State::Finished } } State::Waiting { watcher, rx } => { if let Some(rx) = async_watch(rx).await { - ( - Some((id, DesktopFileEvent::Changed)), - State::Waiting { watcher, rx }, - ) + _ = output.send(DesktopFileEvent::Changed).await; + State::Waiting { watcher, rx } } else { - (None, State::Finished) + State::Finished } } State::Finished => cosmic::iced::futures::future::pending().await, diff --git a/flake.nix b/flake.nix index c715c7b..e1508fc 100644 --- a/flake.nix +++ b/flake.nix @@ -44,6 +44,7 @@ ]; buildInputs = with pkgs; [ systemdMinimal + bashInteractive libxkbcommon freetype fontconfig diff --git a/i18n/en/cosmic_settings.ftl b/i18n/en/cosmic_settings.ftl index f1f0822..5a1105c 100644 --- a/i18n/en/cosmic_settings.ftl +++ b/i18n/en/cosmic_settings.ftl @@ -52,6 +52,7 @@ panel = Panel panel-behavior-and-position = Behavior and Positions .autohide = Automatically hide panel + .dock-autohide = Automatically hide dock .position = Position on screen .display = Show on display @@ -67,7 +68,9 @@ panel-appearance = Appearance panel-style = Style .anchor-gap = Gap between panel and screen edges + .dock-anchor-gap = Gap between dock and screen edges .extend = Extend panel to screen edges + .dock-extend = Extend dock to screen edges .appearance = Appearance .size = Size .background-opacity = Background opacity @@ -76,6 +79,7 @@ small = Small large = Large panel-applets = Configuration + .dock-desc = Configure dock applets. .desc = Configure panel applets. panel-missing = Panel Configuration is Missing