Merge pull request #61 from pop-os/input-settings
Add input settings, with mouse, keyboard, keyboard shortcuts sub-pages
This commit is contained in:
commit
c8148c8f48
12 changed files with 1078 additions and 32 deletions
122
Cargo.lock
generated
122
Cargo.lock
generated
|
|
@ -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]]
|
||||
|
|
@ -732,6 +732,15 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-comp-config"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/pop-os/cosmic-comp#1392fc7c953678a14825ba3d1e5619d38c1946c7"
|
||||
dependencies = [
|
||||
"input",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmic-config"
|
||||
version = "0.1.0"
|
||||
|
|
@ -778,11 +787,13 @@ dependencies = [
|
|||
"apply",
|
||||
"async-channel",
|
||||
"color-eyre",
|
||||
"cosmic-comp-config",
|
||||
"cosmic-panel-config",
|
||||
"cosmic-settings-desktop",
|
||||
"cosmic-settings-page",
|
||||
"cosmic-settings-system",
|
||||
"cosmic-settings-time",
|
||||
"derivative",
|
||||
"derive_setters",
|
||||
"dirs 5.0.1",
|
||||
"downcast-rs",
|
||||
|
|
@ -792,12 +803,14 @@ dependencies = [
|
|||
"i18n-embed",
|
||||
"i18n-embed-fl",
|
||||
"image",
|
||||
"itertools 0.11.0",
|
||||
"libcosmic",
|
||||
"log",
|
||||
"notify",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"slotmap",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
|
@ -1069,7 +1082,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1091,7 +1104,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
|
|||
dependencies = [
|
||||
"darling_core 0.20.1",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1133,7 +1146,7 @@ dependencies = [
|
|||
"darling 0.20.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1219,7 +1232,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1292,7 +1305,7 @@ checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1576,7 +1589,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1739,7 +1752,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2083,7 +2096,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
"unic-langid",
|
||||
]
|
||||
|
||||
|
|
@ -2206,7 +2219,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",
|
||||
|
|
@ -2441,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"
|
||||
|
|
@ -2504,6 +2540,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"
|
||||
|
|
@ -2649,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"
|
||||
|
|
@ -3260,7 +3315,7 @@ checksum = "3c02bfa6b3ba8af5434fa0531bf5701f750d983d4260acd6867faca51cdc4484"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3353,7 +3408,7 @@ dependencies = [
|
|||
"phf_shared",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3388,7 +3443,7 @@ checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3737,7 +3792,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
|
|
@ -3860,22 +3915,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]]
|
||||
|
|
@ -3886,7 +3941,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4205,9 +4260,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",
|
||||
|
|
@ -4297,7 +4352,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4487,7 +4542,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4567,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"
|
||||
|
|
@ -4813,7 +4879,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
|
|
@ -4847,7 +4913,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
|||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.22",
|
||||
"syn 2.0.28",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -16,6 +16,10 @@ 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"
|
||||
# path = "../cosmic-comp/cosmic-comp-config"
|
||||
|
||||
[workspace.dependencies.cosmic-panel-config]
|
||||
git = "https://github.com/pop-os/cosmic-panel"
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,12 @@ 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"
|
||||
i18n-embed-fl = "0.6.7"
|
||||
itertools = "0.11.0"
|
||||
libcosmic = {workspace = true}
|
||||
once_cell = "1.17.2"
|
||||
regex = "1.8.3"
|
||||
|
|
@ -24,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"
|
||||
|
|
@ -35,6 +38,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"
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ use crate::{
|
|||
applets::{self, APPLET_DND_ICON_ID},
|
||||
},
|
||||
},
|
||||
input::{self, keyboard},
|
||||
sound, system, time,
|
||||
},
|
||||
subscription::desktop_files,
|
||||
|
|
@ -142,6 +143,8 @@ impl Application for SettingsApp {
|
|||
|
||||
// app.insert_page::<accessibility::Page>();
|
||||
// app.insert_page::<applications::Page>();
|
||||
//
|
||||
app.insert_page::<input::Page>();
|
||||
|
||||
let active_id = app
|
||||
.pages
|
||||
|
|
@ -268,6 +271,11 @@ impl Application for SettingsApp {
|
|||
crate::pages::Message::DesktopWallpaper(message) => {
|
||||
page::update!(self.pages, message, desktop::wallpaper::Page);
|
||||
}
|
||||
crate::pages::Message::Input(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<input::Page>() {
|
||||
return page.update(message);
|
||||
}
|
||||
}
|
||||
crate::pages::Message::External { .. } => {
|
||||
todo!("external plugins not supported yet");
|
||||
}
|
||||
|
|
@ -326,6 +334,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::<input::Page>())
|
||||
{
|
||||
return page.add_input_source_view();
|
||||
}
|
||||
if let Some(Some(page)) = (id == keyboard::SPECIAL_CHARACTER_DIALOGUE_ID)
|
||||
.then(|| self.pages.page::<input::Page>())
|
||||
{
|
||||
return page.special_character_key_view();
|
||||
}
|
||||
|
||||
cosmic::iced::widget::responsive(|size| {
|
||||
let is_condensed = (600.0 * self.scaling_factor) > size.width;
|
||||
|
|
|
|||
331
app/src/pages/input/keyboard/mod.rs
Normal file
331
app/src/pages/input/keyboard/mod.rs
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
use apply::Apply;
|
||||
use cosmic::{
|
||||
iced::{
|
||||
self,
|
||||
widget::{self, horizontal_space},
|
||||
window, Length,
|
||||
},
|
||||
iced_style, theme,
|
||||
widget::settings,
|
||||
};
|
||||
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)
|
||||
.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,
|
||||
}
|
||||
|
||||
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()
|
||||
.flat_map(|x| x.split(','))
|
||||
.find(|x| x.starts_with(prefix));
|
||||
|
||||
// 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;
|
||||
|
||||
// XXX
|
||||
pub fn default_input_sources() -> Vec<InputSource> {
|
||||
vec![InputSource {
|
||||
id: "us".to_string(),
|
||||
label: "English (US)".to_string(),
|
||||
}]
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
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<crate::pages::Message> for Page {
|
||||
fn sub_pages(page: page::Insert<crate::pages::Message>) -> page::Insert<crate::pages::Message> {
|
||||
page.sub_page::<shortcuts::Page>()
|
||||
}
|
||||
}
|
||||
|
||||
fn input_sources() -> Section<crate::pages::Message> {
|
||||
// TODO desc
|
||||
Section::default()
|
||||
.title(fl!("keyboard-sources"))
|
||||
.view::<Page>(|binder, _page, section| {
|
||||
let input = binder.page::<super::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<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("keyboard-special-char"))
|
||||
.descriptions(vec![
|
||||
fl!("keyboard-special-char", "alternate"),
|
||||
fl!("keyboard-special-char", "compose"),
|
||||
])
|
||||
.view::<Page>(|_binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
// TODO dialogs
|
||||
settings::view_section(§ion.title)
|
||||
.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)
|
||||
})
|
||||
}
|
||||
|
||||
fn keyboard_shortcuts() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("keyboard-shortcuts"))
|
||||
.descriptions(vec![fl!("keyboard-shortcuts", "desc")])
|
||||
.view::<Page>(|binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
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],
|
||||
crate::pages::Message::Page(shortcuts_entity),
|
||||
));
|
||||
}
|
||||
section.apply(cosmic::Element::from)
|
||||
})
|
||||
}
|
||||
|
||||
fn go_next_control<Msg: Clone + 'static>() -> cosmic::Element<'static, Msg> {
|
||||
widget::row!(
|
||||
horizontal_space(Length::Fill),
|
||||
cosmic::widget::icon("go-next-symbolic", 20).style(cosmic::theme::Svg::Symbolic)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn go_next_item<Msg: Clone + 'static>(description: &str, msg: Msg) -> cosmic::Element<'_, Msg> {
|
||||
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()
|
||||
}
|
||||
51
app/src/pages/input/keyboard/shortcuts.rs
Normal file
51
app/src/pages/input/keyboard/shortcuts.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
use apply::Apply;
|
||||
use cosmic::iced::widget;
|
||||
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<crate::pages::Message> for Page {
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
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<crate::pages::Message> for Page {}
|
||||
|
||||
fn shortcuts() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.descriptions(vec![])
|
||||
.view::<Page>(|binder, _page, section| {
|
||||
let _descriptions = §ion.descriptions;
|
||||
|
||||
let _input = binder
|
||||
.page::<super::super::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)
|
||||
})
|
||||
}
|
||||
221
app/src/pages/input/mod.rs
Normal file
221
app/src/pages/input/mod.rs
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
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_comp_config::{
|
||||
input::{AccelProfile, InputConfig},
|
||||
XkbConfig,
|
||||
};
|
||||
use cosmic_settings_page as page;
|
||||
use itertools::Itertools;
|
||||
use tracing::error;
|
||||
|
||||
pub mod keyboard;
|
||||
mod mouse;
|
||||
mod touchpad;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
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<String>),
|
||||
OpenSpecialCharacterDialog(keyboard::SpecialKey),
|
||||
CloseSpecialCharacterDialog,
|
||||
SpecialCharacterSelect(Option<&'static str>),
|
||||
}
|
||||
|
||||
pub struct Page {
|
||||
config: cosmic_config::Config,
|
||||
input_default: InputConfig,
|
||||
#[allow(dead_code)]
|
||||
input_touchpad: InputConfig,
|
||||
|
||||
// Mouse
|
||||
primary_button: cosmic::widget::segmented_button::SingleSelectModel,
|
||||
|
||||
// Touchpad
|
||||
touchpad_primary_button: cosmic::widget::segmented_button::SingleSelectModel,
|
||||
|
||||
// Keyboard
|
||||
expanded_source_popover: Option<String>,
|
||||
sources: Vec<keyboard::InputSource>,
|
||||
special_character_dialog: Option<keyboard::SpecialKey>,
|
||||
xkb: XkbConfig,
|
||||
}
|
||||
|
||||
fn get_config<T: Default + serde::de::DeserializeOwned>(
|
||||
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 input_default: InputConfig = get_config(&config, "input-default");
|
||||
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) {
|
||||
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,
|
||||
input_touchpad,
|
||||
|
||||
// Mouse
|
||||
primary_button,
|
||||
|
||||
// Touchpad
|
||||
touchpad_primary_button,
|
||||
|
||||
// Keyboard
|
||||
expanded_source_popover: None,
|
||||
sources: keyboard::default_input_sources(),
|
||||
special_character_dialog: None,
|
||||
xkb,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
fn update_input<F: Fn(&mut InputConfig)>(&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<app::Message> {
|
||||
match message {
|
||||
Message::SetAcceleration(value, touchpad) => {
|
||||
let profile = if value {
|
||||
AccelProfile::Adaptive
|
||||
} else {
|
||||
AccelProfile::Flat
|
||||
};
|
||||
self.update_input(touchpad, |x| {
|
||||
x.acceleration.get_or_insert(Default::default()).profile = Some(profile);
|
||||
});
|
||||
}
|
||||
Message::SetNaturalScroll(value, touchpad) => self.update_input(touchpad, |x| {
|
||||
x.scroll_config
|
||||
.get_or_insert(Default::default())
|
||||
.natural_scroll = Some(value);
|
||||
}),
|
||||
Message::SetScrollFactor(value, touchpad) => self.update_input(touchpad, |x| {
|
||||
x.scroll_config
|
||||
.get_or_insert(Default::default())
|
||||
.scroll_factor = Some(value)
|
||||
}),
|
||||
Message::SetDoubleClickSpeed(_value, _touchpad) => {
|
||||
// TODO
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 options = self.xkb.options.as_deref().unwrap_or("");
|
||||
let prefix = special_key.prefix();
|
||||
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'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
iced::Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> 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<crate::pages::Message> for Page {
|
||||
fn sub_pages(page: page::Insert<crate::pages::Message>) -> page::Insert<crate::pages::Message> {
|
||||
page.sub_page::<keyboard::Page>()
|
||||
.sub_page::<mouse::Page>()
|
||||
.sub_page::<touchpad::Page>()
|
||||
}
|
||||
}
|
||||
147
app/src/pages/input/mouse.rs
Normal file
147
app/src/pages/input/mouse.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
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;
|
||||
|
||||
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<crate::pages::Message> for Page {
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
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<crate::pages::Message> for Page {}
|
||||
|
||||
fn mouse() -> Section<crate::pages::Message> {
|
||||
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::<Page>(|binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
let input = binder.page::<super::Page>().expect("input page not found");
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[0],
|
||||
cosmic::widget::segmented_selection::horizontal(&input.primary_button)
|
||||
.on_activate(|x| Message::PrimaryButtonSelected(x, false)),
|
||||
))
|
||||
.add(
|
||||
settings::item::builder(&descriptions[1]).control(widget::slider(
|
||||
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, false),
|
||||
)),
|
||||
)
|
||||
.add(
|
||||
settings::item::builder(&descriptions[2])
|
||||
.description(&descriptions[3])
|
||||
.toggler(
|
||||
input
|
||||
.input_default
|
||||
.acceleration
|
||||
.as_ref()
|
||||
.map_or(true, |x| x.profile == Some(AccelProfile::Adaptive)),
|
||||
|x| Message::SetAcceleration(x, false),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
settings::item::builder(&descriptions[4])
|
||||
.description(&descriptions[5])
|
||||
.control(widget::slider(0..=100, 0, |x| {
|
||||
Message::SetDoubleClickSpeed(x, false)
|
||||
})),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Input)
|
||||
})
|
||||
}
|
||||
|
||||
fn scrolling() -> Section<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("mouse-scrolling"))
|
||||
.descriptions(vec![
|
||||
fl!("mouse-scrolling", "speed"),
|
||||
fl!("mouse-scrolling", "natural"),
|
||||
fl!("mouse-scrolling", "natural-desc"),
|
||||
])
|
||||
.view::<Page>(|binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
let input = binder.page::<super::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_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), false),
|
||||
),
|
||||
))
|
||||
.add(
|
||||
settings::item::builder(&descriptions[1])
|
||||
.description(&descriptions[2])
|
||||
.toggler(
|
||||
input
|
||||
.input_default
|
||||
.scroll_config
|
||||
.as_ref()
|
||||
.and_then(|x| x.natural_scroll)
|
||||
.unwrap_or(false),
|
||||
|x| Message::SetNaturalScroll(x, false),
|
||||
),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Input)
|
||||
})
|
||||
}
|
||||
142
app/src/pages/input/touchpad.rs
Normal file
142
app/src/pages/input/touchpad.rs
Normal file
|
|
@ -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<crate::pages::Message> for Page {
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
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<crate::pages::Message> for Page {}
|
||||
|
||||
fn touchpad() -> Section<crate::pages::Message> {
|
||||
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::<Page>(|binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
let input = binder.page::<super::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<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("mouse-scrolling"))
|
||||
.descriptions(vec![
|
||||
fl!("mouse-scrolling", "speed"),
|
||||
fl!("mouse-scrolling", "natural"),
|
||||
fl!("mouse-scrolling", "natural-desc"),
|
||||
])
|
||||
.view::<Page>(|binder, _page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
let input = binder.page::<super::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)
|
||||
})
|
||||
}
|
||||
|
|
@ -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<u8> },
|
||||
Page(Entity),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -221,3 +221,60 @@ 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
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -118,19 +118,22 @@ impl<Message: 'static> Binder<Message> {
|
|||
self.page.get_mut(id).map(AsMut::as_mut)
|
||||
}
|
||||
|
||||
/// Get entity ID of page by its type ID.
|
||||
pub fn page_id<P: Page<Message>>(&self) -> Option<crate::Entity> {
|
||||
self.typed_page_ids.get(&TypeId::of::<P>()).copied()
|
||||
}
|
||||
|
||||
/// Obtain a reference to a page by its type ID.
|
||||
#[must_use]
|
||||
pub fn page<P: Page<Message>>(&self) -> Option<&P> {
|
||||
let id = self.typed_page_ids.get(&TypeId::of::<P>())?;
|
||||
let page = self.page.get(*id)?;
|
||||
let page = self.page.get(self.page_id::<P>()?)?;
|
||||
page.downcast_ref::<P>()
|
||||
}
|
||||
|
||||
/// Obtain a reference to a page by its type ID.
|
||||
#[must_use]
|
||||
pub fn page_mut<P: Page<Message>>(&mut self) -> Option<&mut P> {
|
||||
let id = self.typed_page_ids.get(&TypeId::of::<P>())?;
|
||||
let page = self.page.get_mut(*id)?;
|
||||
let page = self.page.get_mut(self.page_id::<P>()?)?;
|
||||
page.downcast_mut::<P>()
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue