From 2a77cdacb4255089677cf49c06b20d5e17b7303f Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 28 Apr 2023 15:45:29 -0700 Subject: [PATCH 1/7] Add input settings, with mouse, keyboard, keyboard shortcuts sub-pages Still needs to be integrated with compositor so it actually works, need touchpad page, etc. --- Cargo.lock | 1 + app/Cargo.toml | 1 + app/src/app.rs | 14 +- app/src/pages/input/keyboard/mod.rs | 201 ++++++++++++++++++++++ app/src/pages/input/keyboard/shortcuts.rs | 54 ++++++ app/src/pages/input/mod.rs | 81 +++++++++ app/src/pages/input/mouse.rs | 120 +++++++++++++ app/src/pages/mod.rs | 2 + i18n/en/cosmic_settings.ftl | 46 +++++ page/src/binder.rs | 11 +- 10 files changed, 526 insertions(+), 5 deletions(-) create mode 100644 app/src/pages/input/keyboard/mod.rs create mode 100644 app/src/pages/input/keyboard/shortcuts.rs create mode 100644 app/src/pages/input/mod.rs create mode 100644 app/src/pages/input/mouse.rs diff --git a/Cargo.lock b/Cargo.lock index 1655f24..7ab6c99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -783,6 +783,7 @@ dependencies = [ "cosmic-settings-page", "cosmic-settings-system", "cosmic-settings-time", + "derivative", "derive_setters", "dirs 5.0.1", "downcast-rs", diff --git a/app/Cargo.toml b/app/Cargo.toml index 0bc7e75..56ef6be 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -13,6 +13,7 @@ cosmic-settings-desktop = { path = "../pages/desktop" } cosmic-settings-page = { path = "../page" } cosmic-settings-system = { path = "../pages/system" } cosmic-settings-time = { path = "../pages/time" } +derivative = "2.2.0" derive_setters = "0.1.6" dirs = "5.0.1" generator = "0.7.4" diff --git a/app/src/app.rs b/app/src/app.rs index 73c3301..20027c6 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -39,7 +39,7 @@ use crate::{ applets::{self, APPLET_DND_ICON_ID}, }, }, - sound, system, time, + input, sound, system, time, }, subscription::desktop_files, widget::{page_title, parent_page_button, search_header, sub_page_button}, @@ -142,6 +142,8 @@ impl Application for SettingsApp { // app.insert_page::(); // app.insert_page::(); + // + app.insert_page::(); let active_id = app .pages @@ -268,6 +270,16 @@ impl Application for SettingsApp { crate::pages::Message::DesktopWallpaper(message) => { page::update!(self.pages, message, desktop::wallpaper::Page); } + crate::pages::Message::Input(message) => { + if matches!(message, input::Message::OpenKeyboardShortcuts) { + if let Some(id) = self.pages.page_id::() { + self.activate_page(id); + } + } + if let Some(page) = self.pages.page_mut::() { + page.update(message); + } + } crate::pages::Message::External { .. } => { todo!("external plugins not supported yet"); } diff --git a/app/src/pages/input/keyboard/mod.rs b/app/src/pages/input/keyboard/mod.rs new file mode 100644 index 0000000..9738578 --- /dev/null +++ b/app/src/pages/input/keyboard/mod.rs @@ -0,0 +1,201 @@ +use apply::Apply; +use cosmic::iced::{ + self, + widget::{self, horizontal_space}, + Length, +}; +use cosmic::iced_style; +use cosmic::widget::settings; +use cosmic_settings_page::Section; +use cosmic_settings_page::{self as page, section}; +use slotmap::SlotMap; + +use super::Message; + +fn popover_menu_row(label: String) -> cosmic::Element<'static, Message> { + widget::text(label) + .apply(widget::container) + .style(cosmic::theme::Container::custom(|theme| { + iced_style::container::Appearance { + background: None, + ..cosmic::widget::list::column::style(theme) + } + })) + .apply(widget::button) + .style(cosmic::theme::Button::Transparent) + .into() +} + +// TODO for on press, would need to clone ID for each row? +fn popover_menu() -> cosmic::Element<'static, Message> { + // XXX translate + widget::column![ + popover_menu_row(fl!("keyboard-sources", "move-up")), + popover_menu_row(fl!("keyboard-sources", "move-down")), + //cosmic::widget::divider::horizontal::light(), + cosmic::widget::divider::horizontal::light(), + popover_menu_row(fl!("keyboard-sources", "settings")), + popover_menu_row(fl!("keyboard-sources", "view-layout")), + popover_menu_row(fl!("keyboard-sources", "remove")), + ] + .width(Length::Shrink) + .height(Length::Shrink) + .apply(cosmic::widget::container) + .style(cosmic::theme::Container::custom(|theme| { + iced_style::container::Appearance { + text_color: Some(theme.cosmic().background.on.into()), + background: Some(iced::Color::from(theme.cosmic().background.base).into()), + border_radius: (12.0).into(), + border_width: 0.0, + border_color: iced::Color::TRANSPARENT, + } + })) + .into() +} + +fn popover_button(input_source: &InputSource, expanded: bool) -> cosmic::Element<'static, Message> { + let style = if expanded { + cosmic::theme::Svg::SymbolicActive + } else { + cosmic::theme::Svg::Symbolic + }; + let on_press = Message::ExpandInputSourcePopover(if expanded { + None + } else { + Some(input_source.id.clone()) + }); + let button = cosmic::widget::button(cosmic::theme::Button::Secondary) + .icon(style, "open-menu-symbolic", 20) + .padding(0) + .on_press(on_press); + + if expanded { + cosmic::widget::popover(button, popover_menu()).into() + } else { + button.into() + } +} + +fn input_source<'a>( + input_source: &'a InputSource, + expanded_source_popover: Option<&'a str>, +) -> cosmic::Element<'a, Message> { + let expanded = expanded_source_popover == Some(input_source.id.as_str()); + settings::item(&input_source.label, popover_button(input_source, expanded)).into() +} + +pub mod shortcuts; + +pub struct InputSource { + id: String, + // TODO Translate? + label: String, +} + +#[derive(Default)] +pub struct Page; + +// XXX +pub fn default_input_sources() -> Vec { + vec![InputSource { + id: "us".to_string(), + label: "English (US)".to_string(), + }] +} + +impl page::Page for Page { + fn content( + &self, + sections: &mut SlotMap>, + ) -> Option { + Some(vec![ + sections.insert(input_sources()), + sections.insert(special_character_entry()), + sections.insert(keyboard_shortcuts()), + ]) + } + + fn info(&self) -> page::Info { + page::Info::new("keyboard", "input-keyboard-symbolic") + .title(fl!("keyboard")) + .description(fl!("keyboard", "desc")) + } +} + +impl page::AutoBind for Page { + fn sub_pages(page: page::Insert) -> page::Insert { + page.sub_page::() + } +} + +fn input_sources() -> Section { + // TODO desc + Section::default() + .title(fl!("keyboard-sources")) + .view::(|binder, _page, section| { + let input = binder.page::().expect("input page not found"); + + // TODO Need something more custom, with drag and drop + let mut section = settings::view_section(§ion.title); + + let expanded_source = input.expanded_source_popover.as_deref(); + for source in &input.sources { + section = section.add(input_source(source, expanded_source)); + } + + section + .apply(cosmic::Element::from) + .map(crate::pages::Message::Input) + }) +} + +fn special_character_entry() -> Section { + Section::default() + .title(fl!("keyboard-special-char")) + .descriptions(vec![ + fl!("keyboard-special-char", "alternate"), + fl!("keyboard-special-char", "compose"), + ]) + .view::(|_binder, _page, section| { + let descriptions = §ion.descriptions; + + // TODO dialogs + settings::view_section(§ion.title) + .add(settings::item(&descriptions[0], go_next_control())) + .add(settings::item(&descriptions[1], go_next_control())) + .apply(cosmic::Element::from) + .map(crate::pages::Message::Input) + }) +} + +fn keyboard_shortcuts() -> Section { + Section::default() + .title(fl!("keyboard-shortcuts")) + .descriptions(vec![fl!("keyboard-shortcuts", "desc")]) + .view::(|binder, _page, section| { + let descriptions = §ion.descriptions; + + settings::view_section(§ion.title) + .add( + settings::item(&descriptions[0], go_next_control()) + .apply(widget::container) + .style(cosmic::theme::Container::custom( + cosmic::widget::list::column::style, + )) + .apply(widget::button) + .style(cosmic::theme::Button::Transparent) + .padding(0) + .on_press(Message::OpenKeyboardShortcuts), + ) + .apply(cosmic::Element::from) + .map(crate::pages::Message::Input) + }) +} + +fn go_next_control() -> cosmic::Element<'static, Message> { + widget::row!( + horizontal_space(Length::Fill), + cosmic::widget::icon("go-next-symbolic", 20).style(cosmic::theme::Svg::Symbolic) + ) + .into() +} diff --git a/app/src/pages/input/keyboard/shortcuts.rs b/app/src/pages/input/keyboard/shortcuts.rs new file mode 100644 index 0000000..e01fe7c --- /dev/null +++ b/app/src/pages/input/keyboard/shortcuts.rs @@ -0,0 +1,54 @@ +use apply::Apply; +use cosmic::iced::{ + widget::{self, horizontal_space}, + Length, +}; +use cosmic::widget::settings; +use cosmic::Element; +use cosmic_settings_page::Section; +use cosmic_settings_page::{self as page, section}; +use slotmap::SlotMap; + +#[derive(Default)] +pub struct Page; + +//crate::app::Message::Page + +impl page::Page for Page { + fn content( + &self, + sections: &mut SlotMap>, + ) -> Option { + Some(vec![sections.insert(shortcuts())]) + } + + fn info(&self) -> page::Info { + page::Info::new("keyboard-shortcuts", "input-keyboard-symbolic") + .title(fl!("keyboard-shortcuts")) + .description(fl!("keyboard-shortcuts", "desc")) + } +} + +impl page::AutoBind for Page {} + +fn shortcuts() -> Section { + Section::default() + .descriptions(vec![]) + .view::(|binder, _page, section| { + let descriptions = §ion.descriptions; + + let input = binder + .page::() + .expect("input page not found"); + + // TODO need something more custom + /* + settings::view_section(§ion.title) + .apply(Element::from) + .map(crate::pages::Message::Input) + */ + widget::column![settings::view_section(§ion.title)] + .apply(Element::from) + .map(crate::pages::Message::Input) + }) +} diff --git a/app/src/pages/input/mod.rs b/app/src/pages/input/mod.rs new file mode 100644 index 0000000..744f65a --- /dev/null +++ b/app/src/pages/input/mod.rs @@ -0,0 +1,81 @@ +use cosmic_settings_page as page; + +pub mod keyboard; +mod mouse; + +#[derive(Clone, Debug)] +pub enum Message { + SetAcceleration(bool), + SetNaturalScroll(bool), + SetScrollSpeed(u32), + SetDoubleClickSpeed(u32), + SetMouseSpeed(u32), + PrimaryButtonSelected(cosmic::widget::segmented_button::Entity), + // seperate close message, to make sure another isn't closed? + ExpandInputSourcePopover(Option), + OpenKeyboardShortcuts, +} + +#[derive(derivative::Derivative)] +#[derivative(Default)] +pub struct Page { + // Mouse + #[derivative(Default(value = "mouse::default_primary_button()"))] + primary_button: cosmic::widget::segmented_button::SingleSelectModel, + acceleration: bool, + natural_scroll: bool, + double_click_speed: u32, + scroll_speed: u32, + mouse_speed: u32, + + // Keyboard + expanded_source_popover: Option, + #[derivative(Default(value = "keyboard::default_input_sources()"))] + sources: Vec, +} + +impl Page { + // TODO + pub fn update(&mut self, message: Message) { + match message { + Message::SetAcceleration(value) => { + self.acceleration = value; + } + Message::SetNaturalScroll(value) => { + self.natural_scroll = value; + } + Message::SetScrollSpeed(value) => { + self.scroll_speed = value; + } + Message::SetDoubleClickSpeed(value) => { + self.double_click_speed = value; + } + Message::SetMouseSpeed(value) => { + self.mouse_speed = value; + } + Message::PrimaryButtonSelected(entity) => { + self.primary_button.activate(entity); + } + Message::ExpandInputSourcePopover(value) => { + self.expanded_source_popover = value; + } + // TODO Specially handled in app.rs + Message::OpenKeyboardShortcuts => {} + } + } +} + +impl page::Page for Page { + fn info(&self) -> page::Info { + // XXX icon? + page::Info::new("input", "input-keyboard-symbolic") + .title(fl!("input")) + .description(fl!("input", "desc")) + } +} + +impl page::AutoBind for Page { + fn sub_pages(page: page::Insert) -> page::Insert { + page.sub_page::().sub_page::() + } +} diff --git a/app/src/pages/input/mouse.rs b/app/src/pages/input/mouse.rs new file mode 100644 index 0000000..fd23251 --- /dev/null +++ b/app/src/pages/input/mouse.rs @@ -0,0 +1,120 @@ +use apply::Apply; +use cosmic::iced::{ + widget::{self, horizontal_space}, + Length, +}; +use cosmic::widget::settings; +use cosmic::Element; +use cosmic_settings_page::Section; +use cosmic_settings_page::{self as page, section}; +use slotmap::SlotMap; + +use super::Message; + +// XXX +pub fn default_primary_button() -> cosmic::widget::segmented_button::SingleSelectModel { + let mut model = cosmic::widget::segmented_button::SingleSelectModel::builder() + .insert(|b| b.text(fl!("mouse", "primary-button-left"))) + .insert(|b| b.text(fl!("mouse", "primary-button-right"))) + .build(); + model.activate_position(0); + model +} + +#[derive(Default)] +pub struct Page; + +impl page::Page for Page { + fn content( + &self, + sections: &mut SlotMap>, + ) -> Option { + Some(vec![sections.insert(mouse()), sections.insert(scrolling())]) + } + + fn info(&self) -> page::Info { + page::Info::new("mouse", "input-mouse-symbolic") + .title(fl!("mouse")) + .description(fl!("mouse", "desc")) + } +} + +impl page::AutoBind for Page {} + +fn mouse() -> Section { + Section::default() + .descriptions(vec![ + fl!("mouse", "primary-button"), + fl!("mouse", "speed"), + fl!("mouse", "acceleration"), + fl!("mouse", "acceleration-desc"), + fl!("mouse", "double-click-speed"), + fl!("mouse", "double-click-speed-desc"), + ]) + .view::(|binder, _page, section| { + let descriptions = §ion.descriptions; + + let input = binder.page::().expect("input page not found"); + + // TODO need something more custom + settings::view_section(§ion.title) + // TODO + .add(settings::item( + &descriptions[0], + cosmic::widget::segmented_selection::horizontal(&input.primary_button) + .on_activate(Message::PrimaryButtonSelected), + )) + .add( + settings::item::builder(&descriptions[1]).control(widget::slider( + 0..=100, + input.mouse_speed, + Message::SetMouseSpeed, + )), + ) + .add( + settings::item::builder(&descriptions[2]) + .description(&descriptions[3]) + .toggler(input.acceleration, Message::SetAcceleration), + ) + .add( + settings::item::builder(&descriptions[4]) + .description(&descriptions[5]) + .control(widget::slider( + 0..=100, + input.double_click_speed, + Message::SetDoubleClickSpeed, + )), + ) + .apply(Element::from) + .map(crate::pages::Message::Input) + }) +} + +fn scrolling() -> Section { + Section::default() + .title(fl!("mouse-scrolling")) + .descriptions(vec![ + fl!("mouse-scrolling", "speed"), + fl!("mouse-scrolling", "natural"), + fl!("mouse-scrolling", "natural-desc"), + ]) + .view::(|binder, _page, section| { + let descriptions = §ion.descriptions; + + let input = binder.page::().expect("input page not found"); + + settings::view_section(§ion.title) + .add(settings::item( + &descriptions[0], + // TODO show numeric value + widget::slider(0..=100, input.scroll_speed, Message::SetScrollSpeed), + )) + .add( + settings::item::builder(&descriptions[1]) + .description(&descriptions[2]) + .toggler(input.natural_scroll, Message::SetNaturalScroll), + ) + .apply(Element::from) + .map(crate::pages::Message::Input) + }) +} diff --git a/app/src/pages/mod.rs b/app/src/pages/mod.rs index 019986e..c265e0f 100644 --- a/app/src/pages/mod.rs +++ b/app/src/pages/mod.rs @@ -4,6 +4,7 @@ use cosmic_settings_page::Entity; pub mod desktop; +pub mod input; pub mod networking; pub mod sound; pub mod system; @@ -17,6 +18,7 @@ pub enum Message { Panel(desktop::panel::Message), DesktopWallpaper(desktop::wallpaper::Message), Applet(desktop::panel::applets::Message), + Input(input::Message), External { id: String, message: Vec }, Page(Entity), } diff --git a/i18n/en/cosmic_settings.ftl b/i18n/en/cosmic_settings.ftl index 99f5eef..b7e7aa4 100644 --- a/i18n/en/cosmic_settings.ftl +++ b/i18n/en/cosmic_settings.ftl @@ -221,3 +221,49 @@ firmware = Firmware users = Users .desc = Authentication and login, lock screen. + +## Input + +input = Input + .desc = Input + +## Input: Keyboard + +keyboard = Keyboard + .desc = Keyboard input + +keyboard-sources = Input Sources + .desc = Input sources can be switched using Super+Space key combination. This can be customized in the keyboard shortcut settings. + .move-up = Move up + .move-down = Move down + .settings = Settings + .view-layout = View keyboard layout + .remove = Remove + +keyboard-special-char = Special Character Entry + .alternate = Alternate characters key + .compose = Compose key + +## Input: Keyboard: Shortcuts + +keyboard-shortcuts = Keyboard Shortcuts + .desc = View and customize shortcuts + +## Input: Mouse +mouse = Mouse + .desc = Mouse speed, acceleration, natural scrolling. + .primary-button = Primary button + .primary-button-left = Left + .primary-button-right = Right + .speed = Mouse speed + .acceleration = Enable mouse acceleration + .acceleration-desc = Automatically adjusts tracking sensitivty based on speed. + .double-click-speed = Double-click speed + .double-click-speed-desc = Changes how fast double-clicks have to be to register. + +mouse-scrolling = Scrolling + .speed = Scrolling speed + .natural = Natural scrolling + .natural-desc = Scroll the content, instead of the view + +## Input: Touchpad diff --git a/page/src/binder.rs b/page/src/binder.rs index ace3e93..1947a29 100644 --- a/page/src/binder.rs +++ b/page/src/binder.rs @@ -118,19 +118,22 @@ impl Binder { self.page.get_mut(id).map(AsMut::as_mut) } + /// Get entity ID of page by its type ID. + pub fn page_id>(&self) -> Option { + self.typed_page_ids.get(&TypeId::of::

()).copied() + } + /// Obtain a reference to a page by its type ID. #[must_use] pub fn page>(&self) -> Option<&P> { - let id = self.typed_page_ids.get(&TypeId::of::

())?; - let page = self.page.get(*id)?; + let page = self.page.get(self.page_id::

()?)?; page.downcast_ref::

() } /// Obtain a reference to a page by its type ID. #[must_use] pub fn page_mut>(&mut self) -> Option<&mut P> { - let id = self.typed_page_ids.get(&TypeId::of::

())?; - let page = self.page.get_mut(*id)?; + let page = self.page.get_mut(self.page_id::

()?)?; page.downcast_mut::

() } From e4d7c90f306a3b4851e6c02b1f1a5454146ba90e Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 1 Aug 2023 16:41:24 -0700 Subject: [PATCH 2/7] input: Special characters dialog --- Cargo.lock | 55 +++---- app/Cargo.toml | 1 + app/src/app.rs | 17 ++- app/src/pages/input/keyboard/mod.rs | 168 +++++++++++++++++++--- app/src/pages/input/keyboard/shortcuts.rs | 9 +- app/src/pages/input/mod.rs | 62 +++++++- app/src/pages/input/mouse.rs | 5 +- 7 files changed, 253 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ab6c99..e2f242b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -253,7 +253,7 @@ checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -270,7 +270,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -445,7 +445,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -799,6 +799,7 @@ dependencies = [ "once_cell", "regex", "rust-embed", + "serde", "slotmap", "tokio", "tracing", @@ -1070,7 +1071,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1092,7 +1093,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" dependencies = [ "darling_core 0.20.1", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1134,7 +1135,7 @@ dependencies = [ "darling 0.20.1", "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1220,7 +1221,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1293,7 +1294,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1577,7 +1578,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -1740,7 +1741,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -2084,7 +2085,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.22", + "syn 2.0.28", "unic-langid", ] @@ -3261,7 +3262,7 @@ checksum = "3c02bfa6b3ba8af5434fa0531bf5701f750d983d4260acd6867faca51cdc4484" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -3354,7 +3355,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -3389,7 +3390,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -3738,7 +3739,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.22", + "syn 2.0.28", "walkdir", ] @@ -3861,22 +3862,22 @@ checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" [[package]] name = "serde" -version = "1.0.164" +version = "1.0.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" +checksum = "0ea67f183f058fe88a4e3ec6e2788e003840893b91bac4559cabedd00863b3ed" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.164" +version = "1.0.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" +checksum = "24e744d7782b686ab3b73267ef05697159cc0e5abbed3f47f9933165e5219036" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -3887,7 +3888,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -4206,9 +4207,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.22" +version = "2.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efbeae7acf4eabd6bcdcbd11c92f45231ddda7539edc7806bd1a04a03b24616" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" dependencies = [ "proc-macro2", "quote", @@ -4298,7 +4299,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -4488,7 +4489,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", ] [[package]] @@ -4814,7 +4815,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", "wasm-bindgen-shared", ] @@ -4848,7 +4849,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.22", + "syn 2.0.28", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/app/Cargo.toml b/app/Cargo.toml index 56ef6be..d33ed06 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -36,6 +36,7 @@ freedesktop-desktop-entry = "0.5.0" notify = "6.0.0" anyhow = "1.0" image = "0.24.6" +serde = { version = "1.0.180", features = ["derive"] } [dependencies.i18n-embed] version = "0.13.9" diff --git a/app/src/app.rs b/app/src/app.rs index 20027c6..9ee713b 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -39,7 +39,8 @@ use crate::{ applets::{self, APPLET_DND_ICON_ID}, }, }, - input, sound, system, time, + input::{self, keyboard}, + sound, system, time, }, subscription::desktop_files, widget::{page_title, parent_page_button, search_header, sub_page_button}, @@ -273,11 +274,11 @@ impl Application for SettingsApp { crate::pages::Message::Input(message) => { if matches!(message, input::Message::OpenKeyboardShortcuts) { if let Some(id) = self.pages.page_id::() { - self.activate_page(id); + return self.activate_page(id); } } if let Some(page) = self.pages.page_mut::() { - page.update(message); + return page.update(message); } } crate::pages::Message::External { .. } => { @@ -338,6 +339,16 @@ impl Application for SettingsApp { { return page.add_applet_view(); } + if let Some(Some(page)) = + (id == keyboard::ADD_INPUT_SOURCE_DIALOGUE_ID).then(|| self.pages.page::()) + { + return page.add_input_source_view(); + } + if let Some(Some(page)) = (id == keyboard::SPECIAL_CHARACTER_DIALOGUE_ID) + .then(|| self.pages.page::()) + { + return page.special_character_key_view(); + } cosmic::iced::widget::responsive(|size| { let is_condensed = (600.0 * self.scaling_factor) > size.width; diff --git a/app/src/pages/input/keyboard/mod.rs b/app/src/pages/input/keyboard/mod.rs index 9738578..8409ec8 100644 --- a/app/src/pages/input/keyboard/mod.rs +++ b/app/src/pages/input/keyboard/mod.rs @@ -1,17 +1,67 @@ use apply::Apply; -use cosmic::iced::{ - self, - widget::{self, horizontal_space}, - Length, +use cosmic::{ + iced::{ + self, + widget::{self, horizontal_space}, + window, Length, + }, + iced_style, theme, + widget::settings, }; -use cosmic::iced_style; -use cosmic::widget::settings; -use cosmic_settings_page::Section; -use cosmic_settings_page::{self as page, section}; +use cosmic_settings_page::{self as page, section, Section}; use slotmap::SlotMap; use super::Message; +pub const ADD_INPUT_SOURCE_DIALOGUE_ID: window::Id = window::Id(2000); +pub const SPECIAL_CHARACTER_DIALOGUE_ID: window::Id = window::Id(2001); + +static COMPOSE_OPTIONS: &[(&str, &str)] = &[ + // ("Left Alt", "compose:lalt"), XXX? + ("Right Alt", "compose:ralt"), + ("Left Super", "compose:lwin"), + ("Right Super", "compose:rwin"), + ("Menu key", "compose:menu"), + ("Right Ctrl", "compose:rctrl"), + ("Caps Lock", "compose:caps"), + ("Scroll Lock", "compose:sclk"), + ("Print Screen", "compose:prsc"), +]; + +static ALTERNATE_CHARACTER_OPTIONS: &[(&str, &str)] = &[ + ("Left Alt", "lv3:lalt_switch"), + ("Right Alt", "lv3:alt_switch"), + ("Left Super", "lv3:lwin_switch"), + ("Right Super", "lv3:win_switch"), + ("Menu key", "lv3:menu_switch"), + // ("Right Ctrl", "lv3:"), XXX + ("Caps Lock", "lv3:caps_switch"), + // ("Scroll Lock", "lv3:"), XXX + // ("Print Screen", "lv3"), XXX +]; + +#[derive(Copy, Clone, Debug)] +pub enum SpecialKey { + AlternateCharacters, + Compose, +} + +impl SpecialKey { + pub fn title(self) -> String { + match self { + Self::Compose => "Compose".to_string(), + Self::AlternateCharacters => "Alternate Characters".to_string(), + } + } + + pub fn prefix(self) -> &'static str { + match self { + Self::Compose => "compose:", + Self::AlternateCharacters => "lv3:", + } + } +} + fn popover_menu_row(label: String) -> cosmic::Element<'static, Message> { widget::text(label) .apply(widget::container) @@ -92,6 +142,68 @@ pub struct InputSource { label: String, } +impl super::Page { + pub fn add_input_source_view(&self) -> cosmic::Element<'static, crate::app::Message> { + widget::column![].into() + } + + pub fn special_character_key_view(&self) -> cosmic::Element<'_, crate::app::Message> { + let Some(special_key) = self.special_character_dialog else { + return widget::text("").into(); + }; + + let options = match special_key { + SpecialKey::Compose => COMPOSE_OPTIONS, + SpecialKey::AlternateCharacters => ALTERNATE_CHARACTER_OPTIONS, + }; + let prefix = special_key.prefix(); + let current = self + .xkb_options + .iter() + .find(|x| x.starts_with(prefix)) + .map(String::as_str); + + // TODO description, layout default + + let mut list = cosmic::widget::list_column(); + list = list.add(special_char_radio_row("None", None, current)); + for (desc, id) in options { + list = list.add(special_char_radio_row(desc, Some(id), current)); + } + widget::column![ + cosmic::widget::header_bar() + .title(special_key.title()) + .on_close(Message::CloseSpecialCharacterDialog), + cosmic::widget::container( + cosmic::widget::scrollable(cosmic::widget::container(list).padding(24)) + .width(Length::Fill) + .height(Length::Fill) + ) + .style(theme::Container::Background) + .width(Length::Fill) + .height(Length::Fill) + ] + .apply(cosmic::Element::from) + .map(crate::pages::Message::Input) + .map(crate::app::Message::PageMessage) + } +} + +fn special_char_radio_row<'a>( + desc: &'a str, + value: Option<&'static str>, + current_value: Option<&'a str>, +) -> cosmic::Element<'a, Message> { + settings::item_row(vec![iced::widget::radio( + desc, + value, + Some(current_value), + |_| Message::SpecialCharacterSelect(value), + ) + .into()]) + .into() +} + #[derive(Default)] pub struct Page; @@ -161,8 +273,14 @@ fn special_character_entry() -> Section { // TODO dialogs settings::view_section(§ion.title) - .add(settings::item(&descriptions[0], go_next_control())) - .add(settings::item(&descriptions[1], go_next_control())) + .add(go_next_item( + &descriptions[0], + Message::OpenSpecialCharacterDialog(SpecialKey::AlternateCharacters), + )) + .add(go_next_item( + &descriptions[1], + Message::OpenSpecialCharacterDialog(SpecialKey::Compose), + )) .apply(cosmic::Element::from) .map(crate::pages::Message::Input) }) @@ -172,21 +290,14 @@ fn keyboard_shortcuts() -> Section { Section::default() .title(fl!("keyboard-shortcuts")) .descriptions(vec![fl!("keyboard-shortcuts", "desc")]) - .view::(|binder, _page, section| { + .view::(|_binder, _page, section| { let descriptions = §ion.descriptions; settings::view_section(§ion.title) - .add( - settings::item(&descriptions[0], go_next_control()) - .apply(widget::container) - .style(cosmic::theme::Container::custom( - cosmic::widget::list::column::style, - )) - .apply(widget::button) - .style(cosmic::theme::Button::Transparent) - .padding(0) - .on_press(Message::OpenKeyboardShortcuts), - ) + .add(go_next_item( + &descriptions[0], + Message::OpenKeyboardShortcuts, + )) .apply(cosmic::Element::from) .map(crate::pages::Message::Input) }) @@ -199,3 +310,16 @@ fn go_next_control() -> cosmic::Element<'static, Message> { ) .into() } + +fn go_next_item(description: &str, msg: Message) -> cosmic::Element<'_, Message> { + settings::item(description, go_next_control()) + .apply(widget::container) + .style(cosmic::theme::Container::custom( + cosmic::widget::list::column::style, + )) + .apply(widget::button) + .style(cosmic::theme::Button::Transparent) + .padding(0) + .on_press(msg) + .into() +} diff --git a/app/src/pages/input/keyboard/shortcuts.rs b/app/src/pages/input/keyboard/shortcuts.rs index e01fe7c..4b0e036 100644 --- a/app/src/pages/input/keyboard/shortcuts.rs +++ b/app/src/pages/input/keyboard/shortcuts.rs @@ -1,8 +1,5 @@ use apply::Apply; -use cosmic::iced::{ - widget::{self, horizontal_space}, - Length, -}; +use cosmic::iced::widget; use cosmic::widget::settings; use cosmic::Element; use cosmic_settings_page::Section; @@ -35,9 +32,9 @@ fn shortcuts() -> Section { Section::default() .descriptions(vec![]) .view::(|binder, _page, section| { - let descriptions = §ion.descriptions; + let _descriptions = §ion.descriptions; - let input = binder + let _input = binder .page::() .expect("input page not found"); diff --git a/app/src/pages/input/mod.rs b/app/src/pages/input/mod.rs index 744f65a..8c072b3 100644 --- a/app/src/pages/input/mod.rs +++ b/app/src/pages/input/mod.rs @@ -1,8 +1,23 @@ +use crate::app; +use cosmic::{ + iced::{self, wayland::actions::window::SctkWindowSettings, window}, + iced_sctk::commands, + iced_widget::core::layout, +}; use cosmic_settings_page as page; pub mod keyboard; mod mouse; +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct XkbConfig { + pub rules: String, + pub model: String, + pub layout: String, + pub variant: String, + pub options: Option, +} + #[derive(Clone, Debug)] pub enum Message { SetAcceleration(bool), @@ -14,11 +29,16 @@ pub enum Message { // seperate close message, to make sure another isn't closed? ExpandInputSourcePopover(Option), OpenKeyboardShortcuts, + OpenSpecialCharacterDialog(keyboard::SpecialKey), + CloseSpecialCharacterDialog, + SpecialCharacterSelect(Option<&'static str>), } #[derive(derivative::Derivative)] #[derivative(Default)] pub struct Page { + // cosmic_config::Config + // Mouse #[derivative(Default(value = "mouse::default_primary_button()"))] primary_button: cosmic::widget::segmented_button::SingleSelectModel, @@ -32,11 +52,13 @@ pub struct Page { expanded_source_popover: Option, #[derivative(Default(value = "keyboard::default_input_sources()"))] sources: Vec, + special_character_dialog: Option, + xkb_options: Vec, } impl Page { // TODO - pub fn update(&mut self, message: Message) { + pub fn update(&mut self, message: Message) -> iced::Command { match message { Message::SetAcceleration(value) => { self.acceleration = value; @@ -59,9 +81,45 @@ impl Page { Message::ExpandInputSourcePopover(value) => { self.expanded_source_popover = value; } - // TODO Specially handled in app.rs + Message::OpenSpecialCharacterDialog(special_key) => { + self.special_character_dialog = Some(special_key); + let window_settings = SctkWindowSettings { + window_id: keyboard::SPECIAL_CHARACTER_DIALOGUE_ID, + app_id: Some("com.system76.CosmicSettings".to_string()), + title: Some(special_key.title()), + parent: Some(window::Id(0)), + autosize: false, + size_limits: layout::Limits::NONE + .min_width(300.0) + .max_width(800.0) + .min_height(200.0) + .max_height(1080.0), + size: (512, 420), + resizable: None, + client_decorations: true, + transparent: true, + }; + return commands::window::get_window(window_settings); + } + Message::CloseSpecialCharacterDialog => { + self.special_character_dialog = None; + return commands::window::close_window(keyboard::SPECIAL_CHARACTER_DIALOGUE_ID); + } + Message::SpecialCharacterSelect(id) => { + if let Some(special_key) = self.special_character_dialog { + let prefix = special_key.prefix(); + if let Some(idx) = self.xkb_options.iter().position(|x| x.starts_with(prefix)) { + self.xkb_options.remove(idx); + } + if let Some(id) = id { + self.xkb_options.push(id.to_string()); + } + // TODO set in cosmic-config + } + } Message::OpenKeyboardShortcuts => {} } + iced::Command::none() } } diff --git a/app/src/pages/input/mouse.rs b/app/src/pages/input/mouse.rs index fd23251..7341059 100644 --- a/app/src/pages/input/mouse.rs +++ b/app/src/pages/input/mouse.rs @@ -1,8 +1,5 @@ use apply::Apply; -use cosmic::iced::{ - widget::{self, horizontal_space}, - Length, -}; +use cosmic::iced::widget; use cosmic::widget::settings; use cosmic::Element; use cosmic_settings_page::Section; From a5fcd5326eacc20dfbb378662a35fc945be8bfd3 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 9 Aug 2023 15:31:30 -0700 Subject: [PATCH 3/7] keyboard: Connect special characters dialogs to cosmic-config settings --- Cargo.lock | 12 +++++- app/Cargo.toml | 1 + app/src/pages/input/keyboard/mod.rs | 7 ++-- app/src/pages/input/mod.rs | 63 +++++++++++++++++++++++------ 4 files changed, 66 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2f242b..5893362 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,6 +793,7 @@ dependencies = [ "i18n-embed", "i18n-embed-fl", "image", + "itertools 0.11.0", "libcosmic", "log", "notify", @@ -2208,7 +2209,7 @@ dependencies = [ "iced_graphics", "iced_runtime", "iced_style", - "itertools", + "itertools 0.10.5", "raw-window-handle 0.5.2", "smithay-client-toolkit 0.17.0", "smithay-clipboard", @@ -2506,6 +2507,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + [[package]] name = "jni-sys" version = "0.3.0" diff --git a/app/Cargo.toml b/app/Cargo.toml index d33ed06..5aefca9 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -18,6 +18,7 @@ derive_setters = "0.1.6" dirs = "5.0.1" generator = "0.7.4" i18n-embed-fl = "0.6.7" +itertools = "0.11.0" libcosmic = {workspace = true} once_cell = "1.17.2" regex = "1.8.3" diff --git a/app/src/pages/input/keyboard/mod.rs b/app/src/pages/input/keyboard/mod.rs index 8409ec8..91e9803 100644 --- a/app/src/pages/input/keyboard/mod.rs +++ b/app/src/pages/input/keyboard/mod.rs @@ -158,10 +158,11 @@ impl super::Page { }; let prefix = special_key.prefix(); let current = self - .xkb_options + .xkb + .options .iter() - .find(|x| x.starts_with(prefix)) - .map(String::as_str); + .flat_map(|x| x.split(',')) + .find(|x| x.starts_with(prefix)); // TODO description, layout default diff --git a/app/src/pages/input/mod.rs b/app/src/pages/input/mod.rs index 8c072b3..5b11862 100644 --- a/app/src/pages/input/mod.rs +++ b/app/src/pages/input/mod.rs @@ -1,15 +1,18 @@ use crate::app; use cosmic::{ + cosmic_config::{self, ConfigGet, ConfigSet}, iced::{self, wayland::actions::window::SctkWindowSettings, window}, iced_sctk::commands, iced_widget::core::layout, }; use cosmic_settings_page as page; +use itertools::Itertools; +use tracing::error; pub mod keyboard; mod mouse; -#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] pub struct XkbConfig { pub rules: String, pub model: String, @@ -34,13 +37,10 @@ pub enum Message { SpecialCharacterSelect(Option<&'static str>), } -#[derive(derivative::Derivative)] -#[derivative(Default)] pub struct Page { - // cosmic_config::Config + config: cosmic_config::Config, // Mouse - #[derivative(Default(value = "mouse::default_primary_button()"))] primary_button: cosmic::widget::segmented_button::SingleSelectModel, acceleration: bool, natural_scroll: bool, @@ -50,10 +50,44 @@ pub struct Page { // Keyboard expanded_source_popover: Option, - #[derivative(Default(value = "keyboard::default_input_sources()"))] sources: Vec, special_character_dialog: Option, - xkb_options: Vec, + xkb: XkbConfig, +} + +fn get_config( + config: &cosmic_config::Config, + key: &str, +) -> T { + config.get(key).unwrap_or_else(|err| { + error!(?err, "Failed to read config '{}'", key); + T::default() + }) +} + +impl Default for Page { + fn default() -> Self { + let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap(); + let xkb = get_config(&config, "xkb-config"); + + Self { + config, + + // Mouse + primary_button: mouse::default_primary_button(), + acceleration: false, + natural_scroll: false, + double_click_speed: 0, + scroll_speed: 0, + mouse_speed: 0, + + // Keyboard + expanded_source_popover: None, + sources: keyboard::default_input_sources(), + special_character_dialog: None, + xkb, + } + } } impl Page { @@ -107,14 +141,17 @@ impl Page { } Message::SpecialCharacterSelect(id) => { if let Some(special_key) = self.special_character_dialog { + let options = self.xkb.options.as_deref().unwrap_or(""); let prefix = special_key.prefix(); - if let Some(idx) = self.xkb_options.iter().position(|x| x.starts_with(prefix)) { - self.xkb_options.remove(idx); + let new_options = options + .split(',') + .filter(|x| !x.starts_with(prefix)) + .chain(id.into_iter()) + .join(","); + self.xkb.options = Some(new_options).filter(|x| !x.is_empty()); + if let Err(err) = self.config.set("xkb-config", &self.xkb) { + error!(?err, "Failed to set config 'xkb-config'"); } - if let Some(id) = id { - self.xkb_options.push(id.to_string()); - } - // TODO set in cosmic-config } } Message::OpenKeyboardShortcuts => {} From 154c67ee6a5a62188bc389684896995dbd7e44ae Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 29 Aug 2023 14:28:05 -0700 Subject: [PATCH 4/7] input: Use `cosmic-comp-config` crate --- Cargo.lock | 54 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 ++++ app/Cargo.toml | 1 + app/src/pages/input/mod.rs | 10 +------ 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5893362..bc4b83e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -732,6 +732,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "cosmic-comp-config" +version = "0.1.0" +source = "git+https://github.com/pop-os/cosmic-comp?branch=cosmic-comp-config#7955b16bd56122d021b762153e86dd90158d18e2" +dependencies = [ + "input", + "serde", +] + [[package]] name = "cosmic-config" version = "0.1.0" @@ -778,6 +787,7 @@ dependencies = [ "apply", "async-channel", "color-eyre", + "cosmic-comp-config", "cosmic-panel-config", "cosmic-settings-desktop", "cosmic-settings-page", @@ -2444,6 +2454,29 @@ dependencies = [ "libc", ] +[[package]] +name = "input" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e74cd82cedcd66db78742a8337bdc48f188c4d2c12742cbc5cd85113f0b059" +dependencies = [ + "bitflags 1.3.2", + "input-sys", + "io-lifetimes", + "libc", + "log", + "udev", +] + +[[package]] +name = "input-sys" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f6c2a17e8aba7217660e32863af87b0febad811d4b8620ef76b386603fddc2" +dependencies = [ + "libc", +] + [[package]] name = "instant" version = "0.1.12" @@ -2661,6 +2694,16 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -4579,6 +4622,17 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "udev" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebdbbd670373442a12fe9ef7aeb53aec4147a5a27a00bbc3ab639f08f48191a" +dependencies = [ + "libc", + "libudev-sys", + "pkg-config", +] + [[package]] name = "uds_windows" version = "1.0.2" diff --git a/Cargo.toml b/Cargo.toml index 71460e8..d8b5b7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,11 @@ git = "https://github.com/pop-os/libcosmic" [workspace.dependencies.cosmic-bg-config] git = "https://github.com/pop-os/cosmic-bg" +[workspace.dependencies.cosmic-comp-config] +git = "https://github.com/pop-os/cosmic-comp" +branch = "cosmic-comp-config" +# path = "../cosmic-comp/cosmic-comp-config" + [workspace.dependencies.cosmic-panel-config] git = "https://github.com/pop-os/cosmic-panel" diff --git a/app/Cargo.toml b/app/Cargo.toml index 5aefca9..87ffe88 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -26,6 +26,7 @@ rust-embed = "6.6.1" slotmap = "1.0.6" tokio = "1.28.2" downcast-rs = "1.2.0" +cosmic-comp-config = { workspace = true } # TODO: migrate this dependency to the pages/desktop crate. cosmic-panel-config = { workspace = true } tracing = "0.1.37" diff --git a/app/src/pages/input/mod.rs b/app/src/pages/input/mod.rs index 5b11862..e8653ad 100644 --- a/app/src/pages/input/mod.rs +++ b/app/src/pages/input/mod.rs @@ -5,6 +5,7 @@ use cosmic::{ iced_sctk::commands, iced_widget::core::layout, }; +use cosmic_comp_config::XkbConfig; use cosmic_settings_page as page; use itertools::Itertools; use tracing::error; @@ -12,15 +13,6 @@ use tracing::error; pub mod keyboard; mod mouse; -#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)] -pub struct XkbConfig { - pub rules: String, - pub model: String, - pub layout: String, - pub variant: String, - pub options: Option, -} - #[derive(Clone, Debug)] pub enum Message { SetAcceleration(bool), From 750cd299b29c523cd125d87109876d852cf2543e Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Tue, 29 Aug 2023 14:57:19 -0700 Subject: [PATCH 5/7] input: Use existing message to open keyboard shortcuts --- app/src/app.rs | 5 ----- app/src/pages/input/keyboard/mod.rs | 23 ++++++++++++++--------- app/src/pages/input/mod.rs | 2 -- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/src/app.rs b/app/src/app.rs index 9ee713b..0fa900d 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -272,11 +272,6 @@ impl Application for SettingsApp { page::update!(self.pages, message, desktop::wallpaper::Page); } crate::pages::Message::Input(message) => { - if matches!(message, input::Message::OpenKeyboardShortcuts) { - if let Some(id) = self.pages.page_id::() { - return self.activate_page(id); - } - } if let Some(page) = self.pages.page_mut::() { return page.update(message); } diff --git a/app/src/pages/input/keyboard/mod.rs b/app/src/pages/input/keyboard/mod.rs index 91e9803..6ffd5bb 100644 --- a/app/src/pages/input/keyboard/mod.rs +++ b/app/src/pages/input/keyboard/mod.rs @@ -291,20 +291,25 @@ fn keyboard_shortcuts() -> Section { Section::default() .title(fl!("keyboard-shortcuts")) .descriptions(vec![fl!("keyboard-shortcuts", "desc")]) - .view::(|_binder, _page, section| { + .view::(|binder, _page, section| { let descriptions = §ion.descriptions; - settings::view_section(§ion.title) - .add(go_next_item( + let mut section = settings::view_section(§ion.title); + if let Some((shortcuts_entity, _)) = binder + .info + .iter() + .find(|(_, v)| v.id == "keyboard-shortcuts") + { + section = section.add(go_next_item( &descriptions[0], - Message::OpenKeyboardShortcuts, - )) - .apply(cosmic::Element::from) - .map(crate::pages::Message::Input) + crate::pages::Message::Page(shortcuts_entity), + )); + } + section.apply(cosmic::Element::from) }) } -fn go_next_control() -> cosmic::Element<'static, Message> { +fn go_next_control() -> cosmic::Element<'static, Msg> { widget::row!( horizontal_space(Length::Fill), cosmic::widget::icon("go-next-symbolic", 20).style(cosmic::theme::Svg::Symbolic) @@ -312,7 +317,7 @@ fn go_next_control() -> cosmic::Element<'static, Message> { .into() } -fn go_next_item(description: &str, msg: Message) -> cosmic::Element<'_, Message> { +fn go_next_item(description: &str, msg: Msg) -> cosmic::Element<'_, Msg> { settings::item(description, go_next_control()) .apply(widget::container) .style(cosmic::theme::Container::custom( diff --git a/app/src/pages/input/mod.rs b/app/src/pages/input/mod.rs index e8653ad..c2b66a2 100644 --- a/app/src/pages/input/mod.rs +++ b/app/src/pages/input/mod.rs @@ -23,7 +23,6 @@ pub enum Message { PrimaryButtonSelected(cosmic::widget::segmented_button::Entity), // seperate close message, to make sure another isn't closed? ExpandInputSourcePopover(Option), - OpenKeyboardShortcuts, OpenSpecialCharacterDialog(keyboard::SpecialKey), CloseSpecialCharacterDialog, SpecialCharacterSelect(Option<&'static str>), @@ -146,7 +145,6 @@ impl Page { } } } - Message::OpenKeyboardShortcuts => {} } iced::Command::none() } From 69b5c3148f7c91027d9b16209404f0c6d29a1938 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Fri, 1 Sep 2023 15:17:47 -0700 Subject: [PATCH 6/7] input: Use `cosmic-config`/`cosmic-comp-config` for mouse settings Double click speed is not handled currently. That requires toolkit support. --- Cargo.lock | 2 +- Cargo.toml | 1 - app/src/pages/input/mod.rs | 85 +++++++++++++++++++++++++++--------- app/src/pages/input/mouse.rs | 53 +++++++++++++++++----- 4 files changed, 107 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc4b83e..ce96342 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -735,7 +735,7 @@ dependencies = [ [[package]] name = "cosmic-comp-config" version = "0.1.0" -source = "git+https://github.com/pop-os/cosmic-comp?branch=cosmic-comp-config#7955b16bd56122d021b762153e86dd90158d18e2" +source = "git+https://github.com/pop-os/cosmic-comp#1392fc7c953678a14825ba3d1e5619d38c1946c7" dependencies = [ "input", "serde", diff --git a/Cargo.toml b/Cargo.toml index d8b5b7a..6199186 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ git = "https://github.com/pop-os/cosmic-bg" [workspace.dependencies.cosmic-comp-config] git = "https://github.com/pop-os/cosmic-comp" -branch = "cosmic-comp-config" # path = "../cosmic-comp/cosmic-comp-config" [workspace.dependencies.cosmic-panel-config] diff --git a/app/src/pages/input/mod.rs b/app/src/pages/input/mod.rs index c2b66a2..a73b5f0 100644 --- a/app/src/pages/input/mod.rs +++ b/app/src/pages/input/mod.rs @@ -5,7 +5,10 @@ use cosmic::{ iced_sctk::commands, iced_widget::core::layout, }; -use cosmic_comp_config::XkbConfig; +use cosmic_comp_config::{ + input::{AccelProfile, InputConfig}, + XkbConfig, +}; use cosmic_settings_page as page; use itertools::Itertools; use tracing::error; @@ -17,9 +20,9 @@ mod mouse; pub enum Message { SetAcceleration(bool), SetNaturalScroll(bool), - SetScrollSpeed(u32), + SetScrollFactor(f64), SetDoubleClickSpeed(u32), - SetMouseSpeed(u32), + SetMouseSpeed(f64), PrimaryButtonSelected(cosmic::widget::segmented_button::Entity), // seperate close message, to make sure another isn't closed? ExpandInputSourcePopover(Option), @@ -30,14 +33,12 @@ pub enum Message { pub struct Page { config: cosmic_config::Config, + input_default: InputConfig, + #[allow(dead_code)] + input_touchpad: InputConfig, // Mouse primary_button: cosmic::widget::segmented_button::SingleSelectModel, - acceleration: bool, - natural_scroll: bool, - double_click_speed: u32, - scroll_speed: u32, - mouse_speed: u32, // Keyboard expanded_source_popover: Option, @@ -59,18 +60,25 @@ fn get_config( impl Default for Page { fn default() -> Self { let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap(); + let input_default: InputConfig = get_config(&config, "input-default"); + let input_touchpad = get_config(&config, "input-touchpad"); let xkb = get_config(&config, "xkb-config"); + let mut primary_button = mouse::default_primary_button(); + let idx = if input_default.left_handed.unwrap_or(false) { + 0 + } else { + 1 + }; + primary_button.activate_position(idx); + Self { config, + input_default, + input_touchpad, // Mouse - primary_button: mouse::default_primary_button(), - acceleration: false, - natural_scroll: false, - double_click_speed: 0, - scroll_speed: 0, - mouse_speed: 0, + primary_button, // Keyboard expanded_source_popover: None, @@ -86,22 +94,57 @@ impl Page { pub fn update(&mut self, message: Message) -> iced::Command { match message { Message::SetAcceleration(value) => { - self.acceleration = value; + let profile = if value { + AccelProfile::Adaptive + } else { + AccelProfile::Flat + }; + self.input_default + .acceleration + .get_or_insert(Default::default()) + .profile = Some(profile); + if let Err(err) = self.config.set("input-default", &self.input_default) { + error!(?err, "Failed to set config 'input-default'"); + } } Message::SetNaturalScroll(value) => { - self.natural_scroll = value; + self.input_default + .scroll_config + .get_or_insert(Default::default()) + .natural_scroll = Some(value); + if let Err(err) = self.config.set("input-default", &self.input_default) { + error!(?err, "Failed to set config 'input-default'"); + } } - Message::SetScrollSpeed(value) => { - self.scroll_speed = value; + Message::SetScrollFactor(value) => { + self.input_default + .scroll_config + .get_or_insert(Default::default()) + .scroll_factor = Some(value); + if let Err(err) = self.config.set("input-default", &self.input_default) { + error!(?err, "Failed to set config 'input-default'"); + } } - Message::SetDoubleClickSpeed(value) => { - self.double_click_speed = value; + Message::SetDoubleClickSpeed(_value) => { + // TODO } Message::SetMouseSpeed(value) => { - self.mouse_speed = value; + self.input_default + .acceleration + .get_or_insert(Default::default()) + .speed = value; + if let Err(err) = self.config.set("input-default", &self.input_default) { + error!(?err, "Failed to set config 'input-default'"); + } } Message::PrimaryButtonSelected(entity) => { self.primary_button.activate(entity); + let left_entity = self.primary_button.entity_at(1).unwrap(); + let left_handed = self.primary_button.active() == left_entity; + self.input_default.left_handed = Some(left_handed); + if let Err(err) = self.config.set("input-default", &self.input_default) { + error!(?err, "Failed to set config 'input-default'"); + } } Message::ExpandInputSourcePopover(value) => { self.expanded_source_popover = value; diff --git a/app/src/pages/input/mouse.rs b/app/src/pages/input/mouse.rs index 7341059..dea7766 100644 --- a/app/src/pages/input/mouse.rs +++ b/app/src/pages/input/mouse.rs @@ -2,6 +2,7 @@ use apply::Apply; use cosmic::iced::widget; use cosmic::widget::settings; use cosmic::Element; +use cosmic_comp_config::input::AccelProfile; use cosmic_settings_page::Section; use cosmic_settings_page::{self as page, section}; use slotmap::SlotMap; @@ -63,24 +64,33 @@ fn mouse() -> Section { )) .add( settings::item::builder(&descriptions[1]).control(widget::slider( - 0..=100, - input.mouse_speed, - Message::SetMouseSpeed, + 0.0..=100.0, + (input + .input_default + .acceleration + .as_ref() + .map_or(0.0, |x| x.speed) + + 1.0) + * 50.0, + |value| Message::SetMouseSpeed((value / 50.0) - 1.0), )), ) .add( settings::item::builder(&descriptions[2]) .description(&descriptions[3]) - .toggler(input.acceleration, Message::SetAcceleration), + .toggler( + input + .input_default + .acceleration + .as_ref() + .map_or(true, |x| x.profile == Some(AccelProfile::Adaptive)), + Message::SetAcceleration, + ), ) .add( settings::item::builder(&descriptions[4]) .description(&descriptions[5]) - .control(widget::slider( - 0..=100, - input.double_click_speed, - Message::SetDoubleClickSpeed, - )), + .control(widget::slider(0..=100, 0, Message::SetDoubleClickSpeed)), ) .apply(Element::from) .map(crate::pages::Message::Input) @@ -104,12 +114,33 @@ fn scrolling() -> Section { .add(settings::item( &descriptions[0], // TODO show numeric value - widget::slider(0..=100, input.scroll_speed, Message::SetScrollSpeed), + // TODO desired range? + widget::slider( + 1.0..=100.0, + input + .input_default + .scroll_config + .as_ref() + .and_then(|x| x.scroll_factor) + .unwrap_or(1.) + .log(2.) + * 10.0 + + 50.0, + |value| Message::SetScrollFactor(2f64.powf((value - 50.0) / 10.0)), + ), )) .add( settings::item::builder(&descriptions[1]) .description(&descriptions[2]) - .toggler(input.natural_scroll, Message::SetNaturalScroll), + .toggler( + input + .input_default + .scroll_config + .as_ref() + .and_then(|x| x.natural_scroll) + .unwrap_or(false), + Message::SetNaturalScroll, + ), ) .apply(Element::from) .map(crate::pages::Message::Input) From cafe56d86cf49bf206b1465c31e8a22748b837e0 Mon Sep 17 00:00:00 2001 From: Ian Douglas Scott Date: Wed, 6 Sep 2023 14:44:50 -0700 Subject: [PATCH 7/7] Add touchpad settings --- app/src/pages/input/mod.rs | 116 ++++++++++++++------------ app/src/pages/input/mouse.rs | 17 ++-- app/src/pages/input/touchpad.rs | 142 ++++++++++++++++++++++++++++++++ i18n/en/cosmic_settings.ftl | 11 +++ 4 files changed, 225 insertions(+), 61 deletions(-) create mode 100644 app/src/pages/input/touchpad.rs diff --git a/app/src/pages/input/mod.rs b/app/src/pages/input/mod.rs index a73b5f0..f526178 100644 --- a/app/src/pages/input/mod.rs +++ b/app/src/pages/input/mod.rs @@ -15,15 +15,16 @@ use tracing::error; pub mod keyboard; mod mouse; +mod touchpad; #[derive(Clone, Debug)] pub enum Message { - SetAcceleration(bool), - SetNaturalScroll(bool), - SetScrollFactor(f64), - SetDoubleClickSpeed(u32), - SetMouseSpeed(f64), - PrimaryButtonSelected(cosmic::widget::segmented_button::Entity), + SetAcceleration(bool, bool), + SetNaturalScroll(bool, bool), + SetScrollFactor(f64, bool), + SetDoubleClickSpeed(u32, bool), + SetMouseSpeed(f64, bool), + PrimaryButtonSelected(cosmic::widget::segmented_button::Entity, bool), // seperate close message, to make sure another isn't closed? ExpandInputSourcePopover(Option), OpenSpecialCharacterDialog(keyboard::SpecialKey), @@ -40,6 +41,9 @@ pub struct Page { // Mouse primary_button: cosmic::widget::segmented_button::SingleSelectModel, + // Touchpad + touchpad_primary_button: cosmic::widget::segmented_button::SingleSelectModel, + // Keyboard expanded_source_popover: Option, sources: Vec, @@ -61,17 +65,25 @@ impl Default for Page { fn default() -> Self { let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap(); let input_default: InputConfig = get_config(&config, "input-default"); - let input_touchpad = get_config(&config, "input-touchpad"); + let input_touchpad: InputConfig = get_config(&config, "input-touchpad"); let xkb = get_config(&config, "xkb-config"); let mut primary_button = mouse::default_primary_button(); let idx = if input_default.left_handed.unwrap_or(false) { - 0 - } else { 1 + } else { + 0 }; primary_button.activate_position(idx); + let mut touchpad_primary_button = mouse::default_primary_button(); + let idx = if input_touchpad.left_handed.unwrap_or(false) { + 1 + } else { + 0 + }; + touchpad_primary_button.activate_position(idx); + Self { config, input_default, @@ -80,6 +92,9 @@ impl Default for Page { // Mouse primary_button, + // Touchpad + touchpad_primary_button, + // Keyboard expanded_source_popover: None, sources: keyboard::default_input_sources(), @@ -90,61 +105,56 @@ impl Default for Page { } impl Page { - // TODO + fn update_input(&mut self, touchpad: bool, f: F) { + let (name, input_config) = if touchpad { + ("input-touchpad", &mut self.input_touchpad) + } else { + ("input-default", &mut self.input_default) + }; + f(input_config); + if let Err(err) = self.config.set(name, input_config) { + error!(?err, "Failed to set config '{}'", name); + } + } + pub fn update(&mut self, message: Message) -> iced::Command { match message { - Message::SetAcceleration(value) => { + Message::SetAcceleration(value, touchpad) => { let profile = if value { AccelProfile::Adaptive } else { AccelProfile::Flat }; - self.input_default - .acceleration - .get_or_insert(Default::default()) - .profile = Some(profile); - if let Err(err) = self.config.set("input-default", &self.input_default) { - error!(?err, "Failed to set config 'input-default'"); - } + self.update_input(touchpad, |x| { + x.acceleration.get_or_insert(Default::default()).profile = Some(profile); + }); } - Message::SetNaturalScroll(value) => { - self.input_default - .scroll_config + Message::SetNaturalScroll(value, touchpad) => self.update_input(touchpad, |x| { + x.scroll_config .get_or_insert(Default::default()) .natural_scroll = Some(value); - if let Err(err) = self.config.set("input-default", &self.input_default) { - error!(?err, "Failed to set config 'input-default'"); - } - } - Message::SetScrollFactor(value) => { - self.input_default - .scroll_config + }), + Message::SetScrollFactor(value, touchpad) => self.update_input(touchpad, |x| { + x.scroll_config .get_or_insert(Default::default()) - .scroll_factor = Some(value); - if let Err(err) = self.config.set("input-default", &self.input_default) { - error!(?err, "Failed to set config 'input-default'"); - } - } - Message::SetDoubleClickSpeed(_value) => { + .scroll_factor = Some(value) + }), + Message::SetDoubleClickSpeed(_value, _touchpad) => { // TODO } - Message::SetMouseSpeed(value) => { - self.input_default - .acceleration - .get_or_insert(Default::default()) - .speed = value; - if let Err(err) = self.config.set("input-default", &self.input_default) { - error!(?err, "Failed to set config 'input-default'"); - } - } - Message::PrimaryButtonSelected(entity) => { - self.primary_button.activate(entity); - let left_entity = self.primary_button.entity_at(1).unwrap(); - let left_handed = self.primary_button.active() == left_entity; - self.input_default.left_handed = Some(left_handed); - if let Err(err) = self.config.set("input-default", &self.input_default) { - error!(?err, "Failed to set config 'input-default'"); - } + Message::SetMouseSpeed(value, touchpad) => self.update_input(touchpad, |x| { + x.acceleration.get_or_insert(Default::default()).speed = value + }), + Message::PrimaryButtonSelected(entity, touchpad) => { + let select_model = if touchpad { + &mut self.touchpad_primary_button + } else { + &mut self.primary_button + }; + select_model.activate(entity); + let left_entity = select_model.entity_at(1).unwrap(); + let left_handed = select_model.active() == left_entity; + self.update_input(touchpad, |x| x.left_handed = Some(left_handed)); } Message::ExpandInputSourcePopover(value) => { self.expanded_source_popover = value; @@ -204,6 +214,8 @@ impl page::Page for Page { impl page::AutoBind for Page { fn sub_pages(page: page::Insert) -> page::Insert { - page.sub_page::().sub_page::() + page.sub_page::() + .sub_page::() + .sub_page::() } } diff --git a/app/src/pages/input/mouse.rs b/app/src/pages/input/mouse.rs index dea7766..2c88456 100644 --- a/app/src/pages/input/mouse.rs +++ b/app/src/pages/input/mouse.rs @@ -9,7 +9,6 @@ use slotmap::SlotMap; use super::Message; -// XXX pub fn default_primary_button() -> cosmic::widget::segmented_button::SingleSelectModel { let mut model = cosmic::widget::segmented_button::SingleSelectModel::builder() .insert(|b| b.text(fl!("mouse", "primary-button-left"))) @@ -54,13 +53,11 @@ fn mouse() -> Section { let input = binder.page::().expect("input page not found"); - // TODO need something more custom settings::view_section(§ion.title) - // TODO .add(settings::item( &descriptions[0], cosmic::widget::segmented_selection::horizontal(&input.primary_button) - .on_activate(Message::PrimaryButtonSelected), + .on_activate(|x| Message::PrimaryButtonSelected(x, false)), )) .add( settings::item::builder(&descriptions[1]).control(widget::slider( @@ -72,7 +69,7 @@ fn mouse() -> Section { .map_or(0.0, |x| x.speed) + 1.0) * 50.0, - |value| Message::SetMouseSpeed((value / 50.0) - 1.0), + |value| Message::SetMouseSpeed((value / 50.0) - 1.0, false), )), ) .add( @@ -84,13 +81,15 @@ fn mouse() -> Section { .acceleration .as_ref() .map_or(true, |x| x.profile == Some(AccelProfile::Adaptive)), - Message::SetAcceleration, + |x| Message::SetAcceleration(x, false), ), ) .add( settings::item::builder(&descriptions[4]) .description(&descriptions[5]) - .control(widget::slider(0..=100, 0, Message::SetDoubleClickSpeed)), + .control(widget::slider(0..=100, 0, |x| { + Message::SetDoubleClickSpeed(x, false) + })), ) .apply(Element::from) .map(crate::pages::Message::Input) @@ -126,7 +125,7 @@ fn scrolling() -> Section { .log(2.) * 10.0 + 50.0, - |value| Message::SetScrollFactor(2f64.powf((value - 50.0) / 10.0)), + |value| Message::SetScrollFactor(2f64.powf((value - 50.0) / 10.0), false), ), )) .add( @@ -139,7 +138,7 @@ fn scrolling() -> Section { .as_ref() .and_then(|x| x.natural_scroll) .unwrap_or(false), - Message::SetNaturalScroll, + |x| Message::SetNaturalScroll(x, false), ), ) .apply(Element::from) diff --git a/app/src/pages/input/touchpad.rs b/app/src/pages/input/touchpad.rs new file mode 100644 index 0000000..6e2305e --- /dev/null +++ b/app/src/pages/input/touchpad.rs @@ -0,0 +1,142 @@ +use apply::Apply; +use cosmic::iced::widget; +use cosmic::widget::settings; +use cosmic::Element; +use cosmic_comp_config::input::AccelProfile; +use cosmic_settings_page::Section; +use cosmic_settings_page::{self as page, section}; +use slotmap::SlotMap; + +use super::Message; + +#[derive(Default)] +pub struct Page; + +impl page::Page for Page { + fn content( + &self, + sections: &mut SlotMap>, + ) -> Option { + Some(vec![ + sections.insert(touchpad()), + sections.insert(scrolling()), + ]) + } + + fn info(&self) -> page::Info { + page::Info::new("touchpad", "input-touchpad-symbolic") + .title(fl!("touchpad")) + .description(fl!("touchpad", "desc")) + } +} + +impl page::AutoBind for Page {} + +fn touchpad() -> Section { + Section::default() + .descriptions(vec![ + fl!("touchpad", "primary-button"), + fl!("touchpad", "speed"), + fl!("touchpad", "acceleration"), + fl!("touchpad", "acceleration-desc"), + fl!("touchpad", "double-click-speed"), + fl!("touchpad", "double-click-speed-desc"), + ]) + .view::(|binder, _page, section| { + let descriptions = §ion.descriptions; + + let input = binder.page::().expect("input page not found"); + + settings::view_section(§ion.title) + .add(settings::item( + &descriptions[0], + cosmic::widget::segmented_selection::horizontal(&input.touchpad_primary_button) + .on_activate(|x| Message::PrimaryButtonSelected(x, true)), + )) + .add( + settings::item::builder(&descriptions[1]).control(widget::slider( + 0.0..=100.0, + (input + .input_touchpad + .acceleration + .as_ref() + .map_or(0.0, |x| x.speed) + + 1.0) + * 50.0, + |value| Message::SetMouseSpeed((value / 50.0) - 1.0, true), + )), + ) + .add( + settings::item::builder(&descriptions[2]) + .description(&descriptions[3]) + .toggler( + input + .input_touchpad + .acceleration + .as_ref() + .map_or(true, |x| x.profile == Some(AccelProfile::Adaptive)), + |x| Message::SetAcceleration(x, true), + ), + ) + // TODO disable while typing + .add( + settings::item::builder(&descriptions[4]) + .description(&descriptions[5]) + .control(widget::slider(0..=100, 0, |x| { + Message::SetDoubleClickSpeed(x, true) + })), + ) + .apply(Element::from) + .map(crate::pages::Message::Input) + }) +} + +fn scrolling() -> Section { + Section::default() + .title(fl!("mouse-scrolling")) + .descriptions(vec![ + fl!("mouse-scrolling", "speed"), + fl!("mouse-scrolling", "natural"), + fl!("mouse-scrolling", "natural-desc"), + ]) + .view::(|binder, _page, section| { + let descriptions = §ion.descriptions; + + let input = binder.page::().expect("input page not found"); + + settings::view_section(§ion.title) + .add(settings::item( + &descriptions[0], + // TODO show numeric value + // TODO desired range? + widget::slider( + 1.0..=100.0, + input + .input_touchpad + .scroll_config + .as_ref() + .and_then(|x| x.scroll_factor) + .unwrap_or(1.) + .log(2.) + * 10.0 + + 50.0, + |value| Message::SetScrollFactor(2f64.powf((value - 50.0) / 10.0), true), + ), + )) + .add( + settings::item::builder(&descriptions[1]) + .description(&descriptions[2]) + .toggler( + input + .input_touchpad + .scroll_config + .as_ref() + .and_then(|x| x.natural_scroll) + .unwrap_or(false), + |x| Message::SetNaturalScroll(x, true), + ), + ) + .apply(Element::from) + .map(crate::pages::Message::Input) + }) +} diff --git a/i18n/en/cosmic_settings.ftl b/i18n/en/cosmic_settings.ftl index b7e7aa4..f1f0822 100644 --- a/i18n/en/cosmic_settings.ftl +++ b/i18n/en/cosmic_settings.ftl @@ -267,3 +267,14 @@ mouse-scrolling = Scrolling .natural-desc = Scroll the content, instead of the view ## Input: Touchpad + +touchpad = Touchpad + .desc = Touchpad speed, click options, gestures. + .primary-button = Primary button + .primary-button-left = Left + .primary-button-right = Right + .speed = Touchpad speed + .acceleration = Enable touchpad acceleration + .acceleration-desc = Automatically adjusts tracking sensitivty based on speed. + .double-click-speed = Double-click speed + .double-click-speed-desc = Changes how fast double-clicks have to be to register.