refactor(keyboard): use context drawers for special character keys

This commit is contained in:
Michael Aaron Murphy 2024-03-27 16:02:22 +01:00 committed by Michael Murphy
parent 29f0489c8a
commit 2b88275af8
4 changed files with 143 additions and 144 deletions

View file

@ -60,6 +60,7 @@ impl SettingsApp {
PageCommands::DesktopPanel => self.pages.page_id::<desktop::options::Page>(), PageCommands::DesktopPanel => self.pages.page_id::<desktop::options::Page>(),
PageCommands::Displays => self.pages.page_id::<display::Page>(), PageCommands::Displays => self.pages.page_id::<display::Page>(),
PageCommands::Firmware => self.pages.page_id::<system::firmware::Page>(), PageCommands::Firmware => self.pages.page_id::<system::firmware::Page>(),
PageCommands::Keyboard => self.pages.page_id::<input::keyboard::Page>(),
PageCommands::Mouse => self.pages.page_id::<input::mouse::Page>(), PageCommands::Mouse => self.pages.page_id::<input::mouse::Page>(),
PageCommands::Network => None, PageCommands::Network => None,
PageCommands::Notifications => self.pages.page_id::<desktop::notifications::Page>(), PageCommands::Notifications => self.pages.page_id::<desktop::notifications::Page>(),
@ -316,6 +317,12 @@ impl cosmic::Application for SettingsApp {
} }
} }
crate::pages::Message::Keyboard(message) => {
if let Some(page) = self.pages.page_mut::<input::keyboard::Page>() {
return page.update(message).map(cosmic::app::Message::App);
}
}
crate::pages::Message::Input(message) => { crate::pages::Message::Input(message) => {
if let Some(page) = self.pages.page_mut::<input::Page>() { if let Some(page) = self.pages.page_mut::<input::Page>() {
return page.update(message).map(cosmic::app::Message::App); return page.update(message).map(cosmic::app::Message::App);
@ -498,17 +505,17 @@ impl cosmic::Application for SettingsApp {
}); });
} }
if let Some(Some(page)) = (id == *keyboard::ADD_INPUT_SOURCE_DIALOGUE_ID) // if let Some(Some(page)) = (id == *keyboard::ADD_INPUT_SOURCE_DIALOGUE_ID)
.then(|| self.pages.page::<input::Page>()) // .then(|| self.pages.page::<input::Page>())
{ // {
return page.add_input_source_view(); // return page.add_input_source_view();
} // }
if let Some(Some(page)) = (id == *keyboard::SPECIAL_CHARACTER_DIALOGUE_ID) // if let Some(Some(page)) = (id == *keyboard::SPECIAL_CHARACTER_DIALOGUE_ID)
.then(|| self.pages.page::<input::Page>()) // .then(|| self.pages.page::<input::Page>())
{ // {
return page.special_character_key_view(); // return page.special_character_key_view();
} // }
if let Some(page) = self.pages.page::<desktop::wallpaper::Page>() { if let Some(page) = self.pages.page::<desktop::wallpaper::Page>() {
if id == page.color_dialog { if id == page.color_dialog {

View file

@ -1,23 +1,20 @@
use cosmic::{ use cosmic::{
cosmic_config::{self, ConfigSet},
iced::{ iced::{
self, self,
widget::{self, horizontal_space}, widget::{self, horizontal_space},
window, Length, Length,
}, },
iced_core::Border, iced_core::Border,
iced_style, theme, iced_style, theme,
widget::{button, container, icon, radio, settings}, widget::{button, container, icon, radio, settings},
Apply, Apply, Command, Element,
}; };
use cosmic_comp_config::XkbConfig;
use cosmic_settings_page::{self as page, section, Section}; use cosmic_settings_page::{self as page, section, Section};
use once_cell::sync::Lazy; use itertools::Itertools;
use slotmap::SlotMap; use slotmap::SlotMap;
use super::Message;
pub static ADD_INPUT_SOURCE_DIALOGUE_ID: Lazy<window::Id> = Lazy::new(window::Id::unique);
pub static SPECIAL_CHARACTER_DIALOGUE_ID: Lazy<window::Id> = Lazy::new(window::Id::unique);
static COMPOSE_OPTIONS: &[(&str, &str)] = &[ static COMPOSE_OPTIONS: &[(&str, &str)] = &[
// ("Left Alt", "compose:lalt"), XXX? // ("Left Alt", "compose:lalt"), XXX?
("Right Alt", "compose:ralt"), ("Right Alt", "compose:ralt"),
@ -42,6 +39,39 @@ static ALTERNATE_CHARACTER_OPTIONS: &[(&str, &str)] = &[
// ("Print Screen", "lv3"), XXX // ("Print Screen", "lv3"), XXX
]; ];
#[derive(Clone, Debug)]
pub enum Message {
ExpandInputSourcePopover(Option<String>),
OpenSpecialCharacterContext(SpecialKey),
SpecialCharacterSelect(Option<&'static str>),
}
pub struct Page {
config: cosmic_config::Config,
context: Option<Context>,
expanded_source_popover: Option<String>,
sources: Vec<InputSource>,
xkb: XkbConfig,
}
impl Default for Page {
fn default() -> Self {
let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap();
Self {
context: None,
expanded_source_popover: None,
sources: default_input_sources(),
xkb: super::get_config(&config, "xkb_config"),
config,
}
}
}
enum Context {
SpecialCharacter(SpecialKey),
}
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug)]
pub enum SpecialKey { pub enum SpecialKey {
AlternateCharacters, AlternateCharacters,
@ -144,54 +174,6 @@ pub struct InputSource {
label: String, 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>( fn special_char_radio_row<'a>(
desc: &'a str, desc: &'a str,
value: Option<&'static str>, value: Option<&'static str>,
@ -204,9 +186,6 @@ fn special_char_radio_row<'a>(
.into() .into()
} }
#[derive(Default)]
pub struct Page;
// XXX // XXX
pub fn default_input_sources() -> Vec<InputSource> { pub fn default_input_sources() -> Vec<InputSource> {
vec![InputSource { vec![InputSource {
@ -232,6 +211,85 @@ impl page::Page<crate::pages::Message> for Page {
.title(fl!("keyboard")) .title(fl!("keyboard"))
.description(fl!("keyboard", "desc")) .description(fl!("keyboard", "desc"))
} }
fn context_drawer(&self) -> Option<Element<'_, crate::pages::Message>> {
match self.context {
Some(Context::SpecialCharacter(special_key)) => self
.special_character_key_view(special_key)
.map(crate::pages::Message::Keyboard)
.apply(Some),
None => None,
}
}
}
impl Page {
pub fn update(&mut self, message: Message) -> Command<crate::app::Message> {
match message {
Message::ExpandInputSourcePopover(value) => {
self.expanded_source_popover = value;
}
Message::OpenSpecialCharacterContext(key) => {
self.context = Some(Context::SpecialCharacter(key));
return cosmic::command::message(crate::app::Message::OpenContextDrawer(
key.title().into(),
));
}
Message::SpecialCharacterSelect(id) => {
if let Some(Context::SpecialCharacter(special_key)) = self.context {
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)
.join(",");
self.xkb.options = Some(new_options).filter(|x| !x.is_empty());
if let Err(err) = self.config.set("xkb_config", &self.xkb) {
tracing::error!(?err, "Failed to set config 'xkb_config'");
}
}
}
}
Command::none()
}
pub fn add_input_source_view(&self) -> cosmic::Element<'static, crate::app::Message> {
widget::column![].into()
}
fn special_character_key_view(&self, special_key: SpecialKey) -> cosmic::Element<'_, Message> {
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));
}
cosmic::widget::scrollable(cosmic::widget::container(list).padding(24))
.width(Length::Fill)
.height(Length::Fill)
.into()
}
} }
impl page::AutoBind<crate::pages::Message> for Page { impl page::AutoBind<crate::pages::Message> for Page {
@ -244,20 +302,18 @@ fn input_sources() -> Section<crate::pages::Message> {
// TODO desc // TODO desc
Section::default() Section::default()
.title(fl!("keyboard-sources")) .title(fl!("keyboard-sources"))
.view::<Page>(|binder, _page, section| { .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 // TODO Need something more custom, with drag and drop
let mut section = settings::view_section(&section.title); let mut section = settings::view_section(&section.title);
let expanded_source = input.expanded_source_popover.as_deref(); let expanded_source = page.expanded_source_popover.as_deref();
for source in &input.sources { for source in &page.sources {
section = section.add(input_source(source, expanded_source)); section = section.add(input_source(source, expanded_source));
} }
section section
.apply(cosmic::Element::from) .apply(cosmic::Element::from)
.map(crate::pages::Message::Input) .map(crate::pages::Message::Keyboard)
}) })
} }
@ -271,18 +327,17 @@ fn special_character_entry() -> Section<crate::pages::Message> {
.view::<Page>(|_binder, _page, section| { .view::<Page>(|_binder, _page, section| {
let descriptions = &section.descriptions; let descriptions = &section.descriptions;
// TODO dialogs
settings::view_section(&section.title) settings::view_section(&section.title)
.add(go_next_item( .add(go_next_item(
&*descriptions[0], &*descriptions[0],
Message::OpenSpecialCharacterDialog(SpecialKey::AlternateCharacters), Message::OpenSpecialCharacterContext(SpecialKey::AlternateCharacters),
)) ))
.add(go_next_item( .add(go_next_item(
&*descriptions[1], &*descriptions[1],
Message::OpenSpecialCharacterDialog(SpecialKey::Compose), Message::OpenSpecialCharacterContext(SpecialKey::Compose),
)) ))
.apply(cosmic::Element::from) .apply(cosmic::Element::from)
.map(crate::pages::Message::Input) .map(crate::pages::Message::Keyboard)
}) })
} }

View file

@ -33,11 +33,8 @@ crate::cache_dynamic_lazy! {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Message { pub enum Message {
CloseSpecialCharacterDialog,
// seperate close message, to make sure another isn't closed? // seperate close message, to make sure another isn't closed?
DisableWhileTyping(bool, bool), DisableWhileTyping(bool, bool),
ExpandInputSourcePopover(Option<String>),
OpenSpecialCharacterDialog(keyboard::SpecialKey),
PrimaryButtonSelected(cosmic::widget::segmented_button::Entity, bool), PrimaryButtonSelected(cosmic::widget::segmented_button::Entity, bool),
SetAcceleration(bool, bool), SetAcceleration(bool, bool),
SetMouseSpeed(f64, bool), SetMouseSpeed(f64, bool),
@ -45,7 +42,6 @@ pub enum Message {
SetSecondaryClickBehavior(Option<ClickMethod>, bool), SetSecondaryClickBehavior(Option<ClickMethod>, bool),
SetScrollFactor(f64, bool), SetScrollFactor(f64, bool),
SetScrollMethod(Option<ScrollMethod>, bool), SetScrollMethod(Option<ScrollMethod>, bool),
SpecialCharacterSelect(Option<&'static str>),
TapToClick(bool), TapToClick(bool),
} }
@ -60,12 +56,6 @@ pub struct Page {
// Touchpad // Touchpad
touchpad_primary_button: cosmic::widget::segmented_button::SingleSelectModel, 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>( fn get_config<T: Default + serde::de::DeserializeOwned>(
@ -83,7 +73,6 @@ impl Default for Page {
let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap(); let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap();
let input_default: InputConfig = get_config(&config, "input_default"); let input_default: InputConfig = get_config(&config, "input_default");
let input_touchpad: InputConfig = 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 mut primary_button = mouse::default_primary_button();
let idx = input_default.left_handed.unwrap_or(false) as u16; let idx = input_default.left_handed.unwrap_or(false) as u16;
@ -103,12 +92,6 @@ impl Default for Page {
// Touchpad // Touchpad
touchpad_primary_button, touchpad_primary_button,
// Keyboard
expanded_source_popover: None,
sources: keyboard::default_input_sources(),
special_character_dialog: None,
xkb,
} }
} }
} }
@ -160,7 +143,7 @@ impl Page {
Message::SetSecondaryClickBehavior(click_method, touchpad) => { Message::SetSecondaryClickBehavior(click_method, touchpad) => {
self.update_input(touchpad, |x| { self.update_input(touchpad, |x| {
x.click_method = click_method; x.click_method = click_method;
}) });
} }
Message::SetScrollFactor(value, touchpad) => self.update_input(touchpad, |x| { Message::SetScrollFactor(value, touchpad) => self.update_input(touchpad, |x| {
@ -193,53 +176,6 @@ impl Page {
self.update_input(touchpad, |x| x.left_handed = Some(left_handed)); 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::MAIN),
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,
..Default::default()
};
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)
.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'");
}
}
}
Message::TapToClick(enabled) => { Message::TapToClick(enabled) => {
self.update_input(true, |conf| { self.update_input(true, |conf| {
conf.tap_config conf.tap_config

View file

@ -23,6 +23,7 @@ pub enum Message {
Dock(desktop::dock::Message), Dock(desktop::dock::Message),
DockApplet(desktop::dock::applets::Message), DockApplet(desktop::dock::applets::Message),
External { id: String, message: Vec<u8> }, External { id: String, message: Vec<u8> },
Keyboard(input::keyboard::Message),
Input(input::Message), Input(input::Message),
Page(Entity), Page(Entity),
Panel(desktop::panel::Message), Panel(desktop::panel::Message),