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.
This commit is contained in:
parent
a4c567a954
commit
2a77cdacb4
10 changed files with 526 additions and 5 deletions
|
|
@ -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::<accessibility::Page>();
|
||||
// app.insert_page::<applications::Page>();
|
||||
//
|
||||
app.insert_page::<input::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::<input::keyboard::shortcuts::Page>() {
|
||||
self.activate_page(id);
|
||||
}
|
||||
}
|
||||
if let Some(page) = self.pages.page_mut::<input::Page>() {
|
||||
page.update(message);
|
||||
}
|
||||
}
|
||||
crate::pages::Message::External { .. } => {
|
||||
todo!("external plugins not supported yet");
|
||||
}
|
||||
|
|
|
|||
201
app/src/pages/input/keyboard/mod.rs
Normal file
201
app/src/pages/input/keyboard/mod.rs
Normal file
|
|
@ -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<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(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<crate::pages::Message> {
|
||||
Section::default()
|
||||
.title(fl!("keyboard-shortcuts"))
|
||||
.descriptions(vec![fl!("keyboard-shortcuts", "desc")])
|
||||
.view::<Page>(|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()
|
||||
}
|
||||
54
app/src/pages/input/keyboard/shortcuts.rs
Normal file
54
app/src/pages/input/keyboard/shortcuts.rs
Normal file
|
|
@ -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<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)
|
||||
})
|
||||
}
|
||||
81
app/src/pages/input/mod.rs
Normal file
81
app/src/pages/input/mod.rs
Normal file
|
|
@ -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<String>),
|
||||
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<String>,
|
||||
#[derivative(Default(value = "keyboard::default_input_sources()"))]
|
||||
sources: Vec<keyboard::InputSource>,
|
||||
}
|
||||
|
||||
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<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>()
|
||||
}
|
||||
}
|
||||
120
app/src/pages/input/mouse.rs
Normal file
120
app/src/pages/input/mouse.rs
Normal file
|
|
@ -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<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");
|
||||
|
||||
// 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<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
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue