feat(shortcuts): runtime configurable keyboard shortcuts
This commit is contained in:
parent
6f051b2456
commit
cf322fdb5e
47 changed files with 3305 additions and 416 deletions
|
|
@ -3,7 +3,6 @@ name = "cosmic-settings"
|
|||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "GPL-3.0"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
|
|
@ -14,9 +13,11 @@ clap = { version = "4.4.18", features = ["derive"] }
|
|||
color-eyre = "0.6.2"
|
||||
cosmic-bg-config.workspace = true
|
||||
cosmic-comp-config.workspace = true
|
||||
cosmic-config.workspace = true
|
||||
cosmic-panel-config.workspace = true
|
||||
cosmic-randr-shell.workspace = true
|
||||
cosmic-randr.workspace = true
|
||||
cosmic-settings-config = { git = "https://github.com/pop-os/cosmic-settings-daemon" }
|
||||
cosmic-settings-page = { path = "../page" }
|
||||
cosmic-settings-system = { path = "../pages/system" }
|
||||
cosmic-settings-time = { path = "../pages/time" }
|
||||
|
|
|
|||
|
|
@ -312,6 +312,10 @@ impl cosmic::Application for SettingsApp {
|
|||
page::update!(self.pages, message, desktop::Page);
|
||||
}
|
||||
|
||||
crate::pages::Message::DesktopOptions(message) => {
|
||||
page::update!(self.pages, message, desktop::options::Page);
|
||||
}
|
||||
|
||||
crate::pages::Message::DesktopWallpaper(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<desktop::wallpaper::Page>() {
|
||||
return page.update(message).map(Into::into);
|
||||
|
|
@ -334,6 +338,66 @@ impl cosmic::Application for SettingsApp {
|
|||
}
|
||||
}
|
||||
|
||||
crate::pages::Message::KeyboardShortcuts(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<input::keyboard::shortcuts::Page>() {
|
||||
return page.update(message).map(Into::into);
|
||||
}
|
||||
}
|
||||
|
||||
crate::pages::Message::CustomShortcuts(message) => {
|
||||
if let Some(page) = self
|
||||
.pages
|
||||
.page_mut::<input::keyboard::shortcuts::custom::Page>()
|
||||
{
|
||||
return page.update(message).map(Into::into);
|
||||
}
|
||||
}
|
||||
|
||||
crate::pages::Message::ManageWindowShortcuts(message) => {
|
||||
if let Some(page) = self
|
||||
.pages
|
||||
.page_mut::<input::keyboard::shortcuts::manage_windows::Page>()
|
||||
{
|
||||
return page.update(message).map(Into::into);
|
||||
}
|
||||
}
|
||||
|
||||
crate::pages::Message::MoveWindowShortcuts(message) => {
|
||||
if let Some(page) = self
|
||||
.pages
|
||||
.page_mut::<input::keyboard::shortcuts::move_window::Page>()
|
||||
{
|
||||
return page.update(message).map(Into::into);
|
||||
}
|
||||
}
|
||||
|
||||
crate::pages::Message::NavShortcuts(message) => {
|
||||
if let Some(page) = self
|
||||
.pages
|
||||
.page_mut::<input::keyboard::shortcuts::nav::Page>()
|
||||
{
|
||||
return page.update(message).map(Into::into);
|
||||
}
|
||||
}
|
||||
|
||||
crate::pages::Message::SystemShortcuts(message) => {
|
||||
if let Some(page) = self
|
||||
.pages
|
||||
.page_mut::<input::keyboard::shortcuts::system::Page>()
|
||||
{
|
||||
return page.update(message).map(Into::into);
|
||||
}
|
||||
}
|
||||
|
||||
crate::pages::Message::TilingShortcuts(message) => {
|
||||
if let Some(page) = self
|
||||
.pages
|
||||
.page_mut::<input::keyboard::shortcuts::tiling::Page>()
|
||||
{
|
||||
return page.update(message).map(Into::into);
|
||||
}
|
||||
}
|
||||
|
||||
crate::pages::Message::Input(message) => {
|
||||
if let Some(page) = self.pages.page_mut::<input::Page>() {
|
||||
return page.update(message).map(Into::into);
|
||||
|
|
@ -641,7 +705,9 @@ impl SettingsApp {
|
|||
let page_info = &self.pages.info[self.active_page];
|
||||
let mut column_widgets = Vec::with_capacity(1 + content.len());
|
||||
|
||||
column_widgets.push(if let Some(parent) = page_info.parent {
|
||||
column_widgets.push(if let Some(custom_header) = page.header() {
|
||||
custom_header.map(Message::from)
|
||||
} else if let Some(parent) = page_info.parent {
|
||||
let page_header = crate::widget::sub_page_header(
|
||||
page_info.title.as_str(),
|
||||
self.pages.info[parent].title.as_str(),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#![allow(clippy::cast_sign_loss)]
|
||||
#![allow(clippy::cast_possible_truncation)]
|
||||
#![allow(clippy::cast_lossless)]
|
||||
#![allow(clippy::too_many_lines)]
|
||||
|
||||
pub mod app;
|
||||
use std::str::FromStr;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ enum ContextView {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
struct IconTheme {
|
||||
pub struct IconTheme {
|
||||
// COSMIC uses the file name of the folder containing the theme
|
||||
id: String,
|
||||
// GTK uses the name of the theme as specified in its index file
|
||||
|
|
@ -1073,7 +1073,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
fn on_enter(
|
||||
&mut self,
|
||||
_: page::Entity,
|
||||
sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
command::future(fetch_icon_themes()).map(crate::pages::Message::Appearance)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +1,60 @@
|
|||
// Copyright 2023 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use super::Message;
|
||||
use cosmic::{
|
||||
iced::Length,
|
||||
theme,
|
||||
widget::{button, container, horizontal_space, icon, row, settings, toggler},
|
||||
widget::{self, button, container, horizontal_space, icon, row, settings, toggler},
|
||||
Apply, Element,
|
||||
};
|
||||
|
||||
use cosmic_config::{ConfigGet, ConfigSet};
|
||||
use cosmic_settings_config::{shortcuts, Action, Binding, Shortcuts};
|
||||
use cosmic_settings_page::Section;
|
||||
use cosmic_settings_page::{self as page, section};
|
||||
use slab::Slab;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Page;
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Message {
|
||||
SuperKey(usize),
|
||||
}
|
||||
|
||||
pub struct Page {
|
||||
pub super_key_selections: Vec<String>,
|
||||
pub super_key_active: Option<usize>,
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Self {
|
||||
Page {
|
||||
super_key_selections: vec![
|
||||
fl!("super-key", "launcher"),
|
||||
fl!("super-key", "workspaces"),
|
||||
fl!("super-key", "applications"),
|
||||
],
|
||||
super_key_active: super_key_active_config(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) {
|
||||
match message {
|
||||
Message::SuperKey(id) => {
|
||||
let action = match id {
|
||||
0 => shortcuts::action::System::Launcher,
|
||||
1 => shortcuts::action::System::WorkspaceOverview,
|
||||
2 => shortcuts::action::System::AppLibrary,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
self.super_key_active = Some(id);
|
||||
super_key_set(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
#[allow(clippy::too_many_lines)]
|
||||
|
|
@ -47,30 +86,26 @@ impl page::AutoBind<crate::pages::Message> for Page {
|
|||
pub fn super_key_action() -> Section<crate::pages::Message> {
|
||||
let mut descriptions = Slab::new();
|
||||
|
||||
let launcher = descriptions.insert(fl!("super-key-action", "launcher"));
|
||||
let workspaces = descriptions.insert(fl!("super-key-action", "workspaces"));
|
||||
let applications = descriptions.insert(fl!("super-key-action", "applications"));
|
||||
let super_key = descriptions.insert(fl!("super-key"));
|
||||
let _launcher = descriptions.insert(fl!("super-key", "launcher"));
|
||||
let _workspaces = descriptions.insert(fl!("super-key", "workspaces"));
|
||||
let _applications = descriptions.insert(fl!("super-key", "applications"));
|
||||
|
||||
Section::default()
|
||||
.title(fl!("super-key-action"))
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, _page, section| {
|
||||
.view::<Page>(move |_binder, page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
settings::view_section(§ion.title)
|
||||
.add(settings::item(
|
||||
&descriptions[launcher],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[workspaces],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.add(settings::item(
|
||||
&descriptions[applications],
|
||||
horizontal_space(Length::Fill),
|
||||
))
|
||||
.into()
|
||||
.add(
|
||||
settings::item::builder(&descriptions[super_key]).control(widget::dropdown(
|
||||
&page.super_key_selections,
|
||||
page.super_key_active,
|
||||
Message::SuperKey,
|
||||
)),
|
||||
)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::DesktopOptions)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -95,7 +130,7 @@ pub fn window_controls() -> Section<crate::pages::Message> {
|
|||
toggler(
|
||||
None,
|
||||
desktop.cosmic_tk.show_minimize,
|
||||
Message::ShowMinimizeButton,
|
||||
super::Message::ShowMinimizeButton,
|
||||
),
|
||||
))
|
||||
.add(settings::flex_item(
|
||||
|
|
@ -103,7 +138,7 @@ pub fn window_controls() -> Section<crate::pages::Message> {
|
|||
toggler(
|
||||
None,
|
||||
desktop.cosmic_tk.show_maximize,
|
||||
Message::ShowMaximizeButton,
|
||||
super::Message::ShowMaximizeButton,
|
||||
),
|
||||
))
|
||||
.apply(Element::from)
|
||||
|
|
@ -117,7 +152,8 @@ pub fn panel_dock_links() -> Section<crate::pages::Message> {
|
|||
.view::<Page>(move |binder, _page, section| {
|
||||
// TODO probably a way of getting the entity and its info
|
||||
let mut settings = settings::view_section(§ion.title);
|
||||
settings = if let Some((panel_entity, panel_info)) =
|
||||
|
||||
if let Some((panel_entity, panel_info)) =
|
||||
binder.info.iter().find(|(_, v)| v.id == "panel")
|
||||
{
|
||||
let control = row::with_children(vec![
|
||||
|
|
@ -125,7 +161,7 @@ pub fn panel_dock_links() -> Section<crate::pages::Message> {
|
|||
icon::from_name("go-next-symbolic").size(16).into(),
|
||||
]);
|
||||
|
||||
settings.add(
|
||||
settings = settings.add(
|
||||
settings::item::builder(panel_info.title.clone())
|
||||
.description(panel_info.description.clone())
|
||||
.control(control)
|
||||
|
|
@ -135,10 +171,8 @@ pub fn panel_dock_links() -> Section<crate::pages::Message> {
|
|||
.apply(button)
|
||||
.style(theme::Button::Transparent)
|
||||
.on_press(crate::pages::Message::Page(panel_entity)),
|
||||
)
|
||||
} else {
|
||||
settings
|
||||
};
|
||||
);
|
||||
}
|
||||
|
||||
settings = if let Some((dock_entity, dock_info)) =
|
||||
binder.info.iter().find(|(_, v)| v.id == "dock")
|
||||
|
|
@ -166,3 +200,39 @@ pub fn panel_dock_links() -> Section<crate::pages::Message> {
|
|||
Element::from(settings)
|
||||
})
|
||||
}
|
||||
|
||||
fn super_key_active_config() -> Option<usize> {
|
||||
let super_binding = Binding::new(shortcuts::Modifiers::new().logo(), None);
|
||||
|
||||
let config = shortcuts::context().ok()?;
|
||||
let shortcuts = shortcuts::shortcuts(&config);
|
||||
|
||||
let new_id = shortcuts
|
||||
.iter()
|
||||
.find(|(binding, _action)| binding == &&super_binding)
|
||||
.and_then(|(_, action)| match action {
|
||||
Action::System(shortcuts::action::System::Launcher) => Some(0),
|
||||
Action::System(shortcuts::action::System::WorkspaceOverview) => Some(1),
|
||||
Action::System(shortcuts::action::System::AppLibrary) => Some(2),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
new_id
|
||||
}
|
||||
|
||||
fn super_key_set(action: shortcuts::action::System) {
|
||||
let Ok(config) = shortcuts::context() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(mut shortcuts) = config.get::<Shortcuts>("custom") else {
|
||||
return;
|
||||
};
|
||||
|
||||
shortcuts.0.insert(
|
||||
Binding::new(shortcuts::Modifiers::new().logo(), None),
|
||||
Action::System(action),
|
||||
);
|
||||
|
||||
_ = config.set("custom", &shortcuts);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
fn on_enter(
|
||||
&mut self,
|
||||
_page: page::Entity,
|
||||
sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
let current_folder = self.config.current_folder().to_owned();
|
||||
|
||||
|
|
|
|||
|
|
@ -311,8 +311,8 @@ impl Page {
|
|||
|
||||
Message::Mirroring(mirroring) => match mirroring {
|
||||
Mirroring::Disable => (),
|
||||
Mirroring::Mirror(target_display) => (),
|
||||
Mirroring::Project(target_display) => (),
|
||||
Mirroring::Mirror(_target_display) => (),
|
||||
Mirroring::Project(_target_display) => (),
|
||||
Mirroring::ProjectToAll => (),
|
||||
},
|
||||
|
||||
|
|
@ -411,12 +411,12 @@ impl Page {
|
|||
}
|
||||
|
||||
/// Changes the color depth of the active display.
|
||||
pub fn set_color_depth(&mut self, depth: ColorDepth) -> Command<app::Message> {
|
||||
pub fn set_color_depth(&mut self, _depth: ColorDepth) -> Command<app::Message> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
/// Changes the color profile of the active display.
|
||||
pub fn set_color_profile(&mut self, profile: usize) -> Command<app::Message> {
|
||||
pub fn set_color_profile(&mut self, _profile: usize) -> Command<app::Message> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
pub mod shortcuts;
|
||||
|
||||
use std::cmp;
|
||||
|
||||
use cosmic::{
|
||||
|
|
@ -404,11 +406,11 @@ impl Page {
|
|||
}
|
||||
}
|
||||
|
||||
SourceContext::Settings(id) => {
|
||||
SourceContext::Settings(_id) => {
|
||||
eprintln!("settings not implemented");
|
||||
}
|
||||
|
||||
SourceContext::ViewLayout(id) => {
|
||||
SourceContext::ViewLayout(_id) => {
|
||||
eprintln!("view layout not implemented");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +0,0 @@
|
|||
use cosmic::widget::{column, settings};
|
||||
use cosmic::{Apply, Element};
|
||||
use cosmic_settings_page::Section;
|
||||
use cosmic_settings_page::{self as page, section};
|
||||
use slab::Slab;
|
||||
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> {
|
||||
let descriptions = Slab::new();
|
||||
|
||||
Section::default()
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, _page, section| {
|
||||
// TODO need something more custom
|
||||
/*
|
||||
settings::view_section(§ion.title)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Input)
|
||||
*/
|
||||
column()
|
||||
.push(settings::view_section(§ion.title))
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::Input)
|
||||
})
|
||||
}
|
||||
588
cosmic-settings/src/pages/input/keyboard/shortcuts/common.rs
Normal file
588
cosmic-settings/src/pages/input/keyboard/shortcuts/common.rs
Normal file
|
|
@ -0,0 +1,588 @@
|
|||
use cosmic::iced::alignment::Horizontal;
|
||||
use cosmic::iced::{Alignment, Length};
|
||||
use cosmic::prelude::CollectionWidget;
|
||||
use cosmic::widget::{self, button, icon, settings, text};
|
||||
use cosmic::{command, theme, Apply, Command, Element};
|
||||
use cosmic_config::{ConfigGet, ConfigSet};
|
||||
use cosmic_settings_config::shortcuts::{self, Action, Binding, Shortcuts};
|
||||
use slab::Slab;
|
||||
use std::borrow::Cow;
|
||||
use std::io;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ShortcutMessage {
|
||||
AddKeybinding,
|
||||
ApplyReplace,
|
||||
CancelReplace,
|
||||
DeleteBinding(usize),
|
||||
DeleteShortcut(usize),
|
||||
EditBinding(usize, bool),
|
||||
InputBinding(usize, String),
|
||||
ResetBindings,
|
||||
ShowShortcut(usize, String),
|
||||
SubmitBinding(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ShortcutBinding {
|
||||
pub id: widget::Id,
|
||||
pub binding: Binding,
|
||||
pub input: String,
|
||||
pub editing: bool,
|
||||
pub is_default: bool,
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct ShortcutModel {
|
||||
pub action: Action,
|
||||
pub bindings: Slab<ShortcutBinding>,
|
||||
pub description: String,
|
||||
pub modified: u16,
|
||||
}
|
||||
|
||||
impl ShortcutModel {
|
||||
pub fn new(defaults: &Shortcuts, shortcuts: &Shortcuts, action: Action) -> Self {
|
||||
let (bindings, modified) =
|
||||
shortcuts
|
||||
.shortcuts(&action)
|
||||
.fold((Slab::new(), 0), |(mut slab, modified), binding| {
|
||||
let is_default = defaults.0.get(binding) == Some(&action);
|
||||
|
||||
slab.insert(ShortcutBinding {
|
||||
id: widget::Id::unique(),
|
||||
binding: binding.clone(),
|
||||
input: String::new(),
|
||||
editing: false,
|
||||
is_default,
|
||||
});
|
||||
|
||||
(slab, if is_default { modified } else { modified + 1 })
|
||||
});
|
||||
|
||||
Self {
|
||||
description: super::localize_action(&action),
|
||||
modified: defaults.0.iter().filter(|(_, a)| **a == action).fold(
|
||||
modified,
|
||||
|modified, (binding, _)| {
|
||||
if bindings.iter().any(|(_, model)| model.binding == *binding) {
|
||||
modified
|
||||
} else {
|
||||
modified + 1
|
||||
}
|
||||
},
|
||||
),
|
||||
action,
|
||||
bindings,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub struct Model {
|
||||
pub defaults: Shortcuts,
|
||||
pub replace_dialog: Option<(usize, Binding, Action, String)>,
|
||||
pub shortcut_models: Slab<ShortcutModel>,
|
||||
pub shortcut_context: Option<usize>,
|
||||
pub config: cosmic_config::Config,
|
||||
pub custom: bool,
|
||||
pub actions: fn(&Shortcuts, &Shortcuts) -> Slab<ShortcutModel>,
|
||||
}
|
||||
|
||||
impl Default for Model {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
defaults: Shortcuts::default(),
|
||||
replace_dialog: None,
|
||||
shortcut_models: Slab::new(),
|
||||
shortcut_context: None,
|
||||
config: shortcuts::context().unwrap(),
|
||||
custom: false,
|
||||
actions: |_, _| Slab::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn actions(mut self, actions: fn(&Shortcuts, &Shortcuts) -> Slab<ShortcutModel>) -> Self {
|
||||
self.actions = actions;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn custom(mut self) -> Self {
|
||||
self.custom = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a new binding to the shortcuts config
|
||||
pub(super) fn config_add(&self, action: Action, binding: Binding) {
|
||||
let mut shortcuts = self.shortcuts_config();
|
||||
shortcuts.0.insert(binding, action);
|
||||
self.shortcuts_config_set(shortcuts);
|
||||
}
|
||||
|
||||
/// Check if a binding is already set
|
||||
pub(super) fn config_contains(&self, binding: &Binding) -> Option<Action> {
|
||||
self.shortcuts_system_config()
|
||||
.0
|
||||
.get(binding)
|
||||
.cloned()
|
||||
.filter(|action| *action != Action::Disable)
|
||||
}
|
||||
|
||||
/// Removes a binding from the shortcuts config
|
||||
pub(super) fn config_remove(&self, binding: &Binding) {
|
||||
let mut shortcuts = self.shortcuts_config();
|
||||
shortcuts.0.retain(|b, _| b != binding);
|
||||
self.shortcuts_config_set(shortcuts);
|
||||
}
|
||||
|
||||
pub(super) fn context_drawer(&self) -> Option<Element<'_, ShortcutMessage>> {
|
||||
self.shortcut_context
|
||||
.as_ref()
|
||||
.map(|id| context_drawer(&self.shortcut_models, *id, self.custom))
|
||||
}
|
||||
|
||||
pub(super) fn dialog(&self) -> Option<Element<'_, ShortcutMessage>> {
|
||||
if let Some(&(id, _, _, ref action)) = self.replace_dialog.as_ref() {
|
||||
if let Some(short_id) = self.shortcut_context {
|
||||
if let Some(model) = self.shortcut_models.get(short_id) {
|
||||
if let Some(shortcut) = model.bindings.get(id) {
|
||||
let primary_action = button::suggested(fl!("replace"))
|
||||
.on_press(ShortcutMessage::ApplyReplace);
|
||||
|
||||
let secondary_action = button::standard(fl!("cancel"))
|
||||
.on_press(ShortcutMessage::CancelReplace);
|
||||
|
||||
let dialog = widget::dialog(fl!("replace-shortcut-dialog"))
|
||||
.icon(icon::from_name("dialog-warning").size(64))
|
||||
.body(fl!(
|
||||
"replace-shortcut-dialog",
|
||||
"desc",
|
||||
shortcut = shortcut.input.clone(),
|
||||
name = shortcut
|
||||
.binding
|
||||
.description
|
||||
.as_ref()
|
||||
.unwrap_or(action)
|
||||
.to_owned()
|
||||
))
|
||||
.primary_action(primary_action)
|
||||
.secondary_action(secondary_action);
|
||||
|
||||
return Some(dialog.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn on_enter(&mut self) {
|
||||
let mut shortcuts = self.config.get::<Shortcuts>("defaults").unwrap_or_default();
|
||||
|
||||
self.defaults = shortcuts.clone();
|
||||
|
||||
if let Ok(custom) = self.config.get::<Shortcuts>("custom") {
|
||||
for (binding, action) in custom.0 {
|
||||
shortcuts.0.remove(&binding);
|
||||
shortcuts.0.insert(binding, action);
|
||||
}
|
||||
}
|
||||
|
||||
self.shortcut_models = (self.actions)(&self.defaults, &shortcuts);
|
||||
}
|
||||
|
||||
pub(super) fn on_clear(&mut self) {
|
||||
self.shortcut_models.clear();
|
||||
}
|
||||
|
||||
/// Gets the custom configuration for keyboard shortcuts.
|
||||
pub(super) fn shortcuts_config(&self) -> Shortcuts {
|
||||
match self.config.get::<Shortcuts>("custom") {
|
||||
Ok(shortcuts) => shortcuts,
|
||||
Err(cosmic_config::Error::GetKey(_, why)) if why.kind() == io::ErrorKind::NotFound => {
|
||||
Shortcuts::default()
|
||||
}
|
||||
Err(why) => {
|
||||
tracing::error!(?why, "unable to get the current shortcuts config");
|
||||
Shortcuts::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the system configuration for keyboard shortcuts.
|
||||
pub(super) fn shortcuts_system_config(&self) -> Shortcuts {
|
||||
let mut shortcuts = self.config.get::<Shortcuts>("defaults").unwrap_or_default();
|
||||
|
||||
if let Ok(custom) = self.config.get::<Shortcuts>("custom") {
|
||||
shortcuts.0.extend(custom.0);
|
||||
}
|
||||
|
||||
shortcuts
|
||||
}
|
||||
|
||||
/// Writes a new configuration to the keyboard shortcuts config file.
|
||||
pub(super) fn shortcuts_config_set(&self, shortcuts: Shortcuts) {
|
||||
if let Err(why) = self.config.set("custom", shortcuts) {
|
||||
tracing::error!(?why, "failed to write shortcuts config");
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub(super) fn update(&mut self, message: ShortcutMessage) -> Command<crate::app::Message> {
|
||||
match message {
|
||||
ShortcutMessage::AddKeybinding => {
|
||||
if let Some(short_id) = self.shortcut_context {
|
||||
if let Some(model) = self.shortcut_models.get_mut(short_id) {
|
||||
// If an empty entry exists, focus it instead of creating a new input.
|
||||
for (_, shortcut) in &mut model.bindings {
|
||||
if shortcut.binding.is_set()
|
||||
|| Binding::from_str(&shortcut.input).is_ok()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
shortcut.input.clear();
|
||||
|
||||
return widget::text_input::focus(shortcut.id.clone());
|
||||
}
|
||||
|
||||
// Create a new input and focus it.
|
||||
let id = widget::Id::unique();
|
||||
model.bindings.insert(ShortcutBinding {
|
||||
id: id.clone(),
|
||||
binding: Binding::default(),
|
||||
input: String::new(),
|
||||
editing: true,
|
||||
is_default: false,
|
||||
});
|
||||
|
||||
return widget::text_input::focus(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutMessage::ApplyReplace => {
|
||||
if let Some((id, new_binding, ..)) = self.replace_dialog.take() {
|
||||
if let Some(short_id) = self.shortcut_context {
|
||||
// Remove conflicting bindings that are saved on disk.
|
||||
self.config_remove(&new_binding);
|
||||
|
||||
// Clear any binding that matches this in the current model
|
||||
for (_, model) in &mut self.shortcut_models {
|
||||
if let Some(id) = model
|
||||
.bindings
|
||||
.iter()
|
||||
.find(|(_, shortcut)| shortcut.binding == new_binding)
|
||||
.map(|(id, _)| id)
|
||||
{
|
||||
model.bindings.remove(id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the current model and save the binding to disk.
|
||||
if let Some(model) = self.shortcut_models.get_mut(short_id) {
|
||||
if let Some(shortcut) = model.bindings.get_mut(id) {
|
||||
let prev_binding = shortcut.binding.clone();
|
||||
|
||||
shortcut.binding = new_binding.clone();
|
||||
shortcut.input.clear();
|
||||
shortcut.editing = false;
|
||||
|
||||
let action = model.action.clone();
|
||||
self.config_remove(&prev_binding);
|
||||
self.config_add(action, new_binding);
|
||||
}
|
||||
}
|
||||
|
||||
self.on_enter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutMessage::CancelReplace => self.replace_dialog = None,
|
||||
|
||||
ShortcutMessage::DeleteBinding(id) => {
|
||||
if let Some(short_id) = self.shortcut_context {
|
||||
if let Some(model) = self.shortcut_models.get_mut(short_id) {
|
||||
let shortcut = model.bindings.remove(id);
|
||||
if shortcut.is_default {
|
||||
self.config_add(Action::Disable, shortcut.binding.clone());
|
||||
} else {
|
||||
self.config_remove(&shortcut.binding);
|
||||
}
|
||||
|
||||
self.on_enter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutMessage::DeleteShortcut(id) => {
|
||||
let model = self.shortcut_models.remove(id);
|
||||
for (_, shortcut) in model.bindings {
|
||||
self.config_remove(&shortcut.binding);
|
||||
self.on_enter();
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutMessage::EditBinding(id, enable) => {
|
||||
if let Some(short_id) = self.shortcut_context {
|
||||
if let Some(model) = self.shortcut_models.get_mut(short_id) {
|
||||
if let Some(shortcut) = model.bindings.get_mut(id) {
|
||||
shortcut.editing = enable;
|
||||
if enable {
|
||||
shortcut.input = shortcut.binding.to_string();
|
||||
return widget::text_input::select_all(shortcut.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutMessage::InputBinding(id, text) => {
|
||||
if let Some(short_id) = self.shortcut_context {
|
||||
if let Some(model) = self.shortcut_models.get_mut(short_id) {
|
||||
if let Some(shortcut) = model.bindings.get_mut(id) {
|
||||
shortcut.input = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removes all bindings from the active shortcut context, and reloads the shortcuts model.
|
||||
ShortcutMessage::ResetBindings => {
|
||||
if let Some(short_id) = self.shortcut_context {
|
||||
if let Some(model) = self.shortcut_models.get(short_id) {
|
||||
for (_, shortcut) in &model.bindings {
|
||||
self.config_remove(&shortcut.binding);
|
||||
}
|
||||
|
||||
if let Ok(defaults) = self.config.get::<Shortcuts>("defaults") {
|
||||
for (binding, action) in defaults.0 {
|
||||
if action == model.action {
|
||||
self.config_remove(&binding);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.on_enter();
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutMessage::ShowShortcut(id, description) => {
|
||||
self.shortcut_context = Some(id);
|
||||
self.replace_dialog = None;
|
||||
|
||||
let mut commands = vec![command::message(crate::app::Message::OpenContextDrawer(
|
||||
description.into(),
|
||||
))];
|
||||
|
||||
if let Some(model) = self.shortcut_models.get(0) {
|
||||
if let Some(shortcut) = model.bindings.get(0) {
|
||||
commands.push(widget::text_input::focus(shortcut.id.clone()));
|
||||
commands.push(widget::text_input::select_all(shortcut.id.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
return Command::batch(commands);
|
||||
}
|
||||
|
||||
ShortcutMessage::SubmitBinding(id) => {
|
||||
if let Some(short_id) = self.shortcut_context {
|
||||
let mut apply_binding = None;
|
||||
|
||||
// Check for conflicts with the new binding.
|
||||
if let Some(model) = self.shortcut_models.get_mut(short_id) {
|
||||
if let Some(shortcut) = model.bindings.get_mut(id) {
|
||||
match Binding::from_str(&shortcut.input) {
|
||||
Ok(new_binding) => {
|
||||
if !new_binding.is_set() {
|
||||
shortcut.input.clear();
|
||||
return Command::none();
|
||||
}
|
||||
|
||||
if let Some(action) = self.config_contains(&new_binding) {
|
||||
let action_str = super::localize_action(&action);
|
||||
self.replace_dialog =
|
||||
Some((id, new_binding, action, action_str));
|
||||
return Command::none();
|
||||
}
|
||||
|
||||
apply_binding = Some(new_binding);
|
||||
}
|
||||
|
||||
Err(why) => {
|
||||
tracing::error!(why, "keybinding input invalid");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply if no conflict was found.
|
||||
if let Some(new_binding) = apply_binding {
|
||||
if let Some(model) = self.shortcut_models.get_mut(short_id) {
|
||||
if let Some(shortcut) = model.bindings.get_mut(id) {
|
||||
let prev_binding = shortcut.binding.clone();
|
||||
|
||||
shortcut.binding = new_binding.clone();
|
||||
shortcut.input.clear();
|
||||
shortcut.editing = false;
|
||||
|
||||
let action = model.action.clone();
|
||||
self.config_remove(&prev_binding);
|
||||
self.config_add(action, new_binding);
|
||||
self.on_enter();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
pub(super) fn view(&self) -> Element<ShortcutMessage> {
|
||||
self.shortcut_models
|
||||
.iter()
|
||||
.map(|(id, shortcut)| shortcut_item(self.custom, id, shortcut))
|
||||
.fold(widget::list_column(), widget::ListColumn::add)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
fn context_drawer(
|
||||
shortcuts: &Slab<ShortcutModel>,
|
||||
id: usize,
|
||||
show_action: bool,
|
||||
) -> Element<ShortcutMessage> {
|
||||
let model = &shortcuts[id];
|
||||
|
||||
let action = show_action.then(|| {
|
||||
let description = if let Action::Spawn(command) = &model.action {
|
||||
Cow::Borrowed(command.as_str())
|
||||
} else {
|
||||
Cow::Owned(super::localize_action(&model.action))
|
||||
};
|
||||
|
||||
text::body(description)
|
||||
});
|
||||
|
||||
let bindings = model.bindings.iter().enumerate().fold(
|
||||
widget::list_column().spacing(8),
|
||||
|section, (_, (bind_id, shortcut))| {
|
||||
let text: Cow<'_, str> = if !shortcut.editing && shortcut.binding.is_set() {
|
||||
Cow::Owned(shortcut.binding.to_string())
|
||||
} else {
|
||||
Cow::Borrowed(&shortcut.input)
|
||||
};
|
||||
|
||||
let input = widget::editable_input("", text, shortcut.editing, move |enable| {
|
||||
ShortcutMessage::EditBinding(bind_id, enable)
|
||||
})
|
||||
.select_on_focus(true)
|
||||
.on_input(move |text| ShortcutMessage::InputBinding(bind_id, text))
|
||||
.on_submit(ShortcutMessage::SubmitBinding(bind_id))
|
||||
.padding([0, 12])
|
||||
.id(shortcut.id.clone())
|
||||
.into();
|
||||
|
||||
let delete_button = widget::button::icon(icon::from_name("edit-delete-symbolic"))
|
||||
.on_press(ShortcutMessage::DeleteBinding(bind_id))
|
||||
.into();
|
||||
|
||||
let flex_control =
|
||||
settings::flex_item_row(vec![input, delete_button]).align_items(Alignment::Center);
|
||||
|
||||
section.add(flex_control)
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: Detect when it is necessary
|
||||
let reset_keybinding_button = if show_action {
|
||||
None
|
||||
} else {
|
||||
let button = widget::button::standard(fl!("reset-to-default"))
|
||||
.on_press(ShortcutMessage::ResetBindings);
|
||||
Some(button)
|
||||
};
|
||||
|
||||
let add_keybinding_button =
|
||||
widget::button::standard(fl!("add-keybinding")).on_press(ShortcutMessage::AddKeybinding);
|
||||
|
||||
let button_container = widget::row::with_capacity(2)
|
||||
.push_maybe(reset_keybinding_button)
|
||||
.push(add_keybinding_button)
|
||||
.spacing(12)
|
||||
.apply(widget::container)
|
||||
.width(Length::Fill)
|
||||
.align_x(Horizontal::Right);
|
||||
|
||||
widget::column::with_capacity(if show_action { 3 } else { 2 })
|
||||
.spacing(32)
|
||||
.push_maybe(action)
|
||||
.push(bindings)
|
||||
.push(button_container)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Display a shortcut as a list item
|
||||
fn shortcut_item(custom: bool, id: usize, data: &ShortcutModel) -> Element<ShortcutMessage> {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
enum LocalMessage {
|
||||
Remove,
|
||||
Show,
|
||||
}
|
||||
|
||||
let bindings = data
|
||||
.bindings
|
||||
.iter()
|
||||
.take(3)
|
||||
.filter(|(_, shortcut)| shortcut.binding.is_set())
|
||||
.map(|(_, shortcut)| widget::text::body(shortcut.binding.to_string()).into())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let shortcuts: Element<LocalMessage> = if bindings.is_empty() {
|
||||
widget::text::body(fl!("disabled")).into()
|
||||
} else {
|
||||
widget::column::with_children(bindings)
|
||||
.align_items(Alignment::End)
|
||||
.into()
|
||||
};
|
||||
|
||||
let modified = if data.modified == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(widget::text::body(fl!("modified", count = data.modified)))
|
||||
};
|
||||
|
||||
let control = widget::row::with_capacity(4)
|
||||
.push_maybe(modified)
|
||||
.push(shortcuts)
|
||||
.push(icon::from_name("go-next-symbolic").size(16))
|
||||
.push_maybe(custom.then(|| {
|
||||
widget::button::icon(icon::from_name("edit-delete-symbolic"))
|
||||
.on_press(LocalMessage::Remove)
|
||||
}))
|
||||
.align_items(Alignment::Center)
|
||||
.spacing(8);
|
||||
|
||||
settings::item::builder(&data.description)
|
||||
.flex_control(control)
|
||||
.spacing(16)
|
||||
.apply(widget::container)
|
||||
.style(theme::Container::List)
|
||||
.apply(widget::button)
|
||||
.style(theme::Button::Transparent)
|
||||
.on_press(LocalMessage::Show)
|
||||
.apply(Element::from)
|
||||
.map(move |message| match message {
|
||||
LocalMessage::Show => ShortcutMessage::ShowShortcut(id, data.description.clone()),
|
||||
LocalMessage::Remove => ShortcutMessage::DeleteShortcut(id),
|
||||
})
|
||||
}
|
||||
440
cosmic-settings/src/pages/input/keyboard/shortcuts/custom.rs
Normal file
440
cosmic-settings/src/pages/input/keyboard/shortcuts/custom.rs
Normal file
|
|
@ -0,0 +1,440 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use super::{ShortcutBinding, ShortcutMessage, ShortcutModel};
|
||||
use cosmic::iced::alignment::Horizontal;
|
||||
use cosmic::iced::Length;
|
||||
use cosmic::widget::{self, button, icon};
|
||||
use cosmic::{Apply, Command, Element};
|
||||
use cosmic_settings_config::shortcuts::{Action, Shortcuts};
|
||||
use cosmic_settings_config::Binding;
|
||||
use cosmic_settings_page::{self as page, section, Section};
|
||||
use slab::Slab;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
pub struct Page {
|
||||
model: super::Model,
|
||||
add_shortcut: AddShortcut,
|
||||
replace_dialog: Vec<(Binding, Action, String)>,
|
||||
command_id: widget::Id,
|
||||
name_id: widget::Id,
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
model: super::Model::default().custom().actions(bindings),
|
||||
add_shortcut: AddShortcut::default(),
|
||||
replace_dialog: Vec::new(),
|
||||
command_id: widget::Id::unique(),
|
||||
name_id: widget::Id::unique(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
/// Adds a new key binding input
|
||||
AddKeybinding,
|
||||
/// Add a new custom shortcut to the config
|
||||
AddShortcut,
|
||||
/// Update the command text input
|
||||
CommandInput(String),
|
||||
/// Toggle editing of the key text input
|
||||
EditCombination,
|
||||
/// Toggle editability of the key text input
|
||||
KeyEditing(usize, bool),
|
||||
/// Update the key text input
|
||||
KeyInput(usize, String),
|
||||
/// Update the name text input
|
||||
NameInput(String),
|
||||
/// Enter key pressed in the name text input
|
||||
NameSubmit,
|
||||
/// Apply a requested shortcut replace operation
|
||||
ReplaceApply,
|
||||
/// Cancel a requested shortcut replace operation
|
||||
ReplaceCancel,
|
||||
/// Emit a generic shortcut message
|
||||
Shortcut(ShortcutMessage),
|
||||
/// Open the add shortcut context drawer
|
||||
ShortcutContext,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AddShortcut {
|
||||
pub active: bool,
|
||||
pub name: String,
|
||||
pub command: String,
|
||||
pub keys: Slab<(String, widget::Id, bool)>,
|
||||
}
|
||||
|
||||
impl AddShortcut {
|
||||
pub fn enable(&mut self) {
|
||||
self.active = true;
|
||||
self.name.clear();
|
||||
self.command.clear();
|
||||
|
||||
if self.keys.is_empty() {
|
||||
self.keys
|
||||
.insert((String::new(), widget::Id::unique(), false));
|
||||
} else {
|
||||
while self.keys.len() > 1 {
|
||||
self.keys.remove(self.keys.len() - 1);
|
||||
}
|
||||
|
||||
self.keys[0].0.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) -> Command<crate::app::Message> {
|
||||
match message {
|
||||
Message::CommandInput(text) => {
|
||||
self.add_shortcut.command = text;
|
||||
}
|
||||
|
||||
Message::KeyInput(id, text) => {
|
||||
self.add_shortcut.keys[id].0 = text;
|
||||
}
|
||||
|
||||
Message::KeyEditing(id, enable) => {
|
||||
self.add_shortcut.keys[id].2 = enable;
|
||||
}
|
||||
|
||||
Message::NameInput(text) => {
|
||||
self.add_shortcut.name = text;
|
||||
}
|
||||
|
||||
Message::AddKeybinding => {
|
||||
// If an empty entry exists, focus it instead of creating a new input.
|
||||
for (_, (binding, id, _)) in &mut self.add_shortcut.keys {
|
||||
if Binding::from_str(binding).is_ok() {
|
||||
continue;
|
||||
}
|
||||
|
||||
binding.clear();
|
||||
|
||||
return widget::text_input::focus(id.clone());
|
||||
}
|
||||
|
||||
let new_id = widget::Id::unique();
|
||||
self.add_shortcut
|
||||
.keys
|
||||
.insert((String::new(), new_id.clone(), true));
|
||||
return Command::batch(vec![
|
||||
widget::text_input::focus(new_id.clone()),
|
||||
widget::text_input::select_all(new_id),
|
||||
]);
|
||||
}
|
||||
|
||||
Message::AddShortcut => {
|
||||
let name = self.add_shortcut.name.trim();
|
||||
let command = self.add_shortcut.command.trim();
|
||||
|
||||
if name.is_empty() || command.is_empty() {
|
||||
return Command::none();
|
||||
}
|
||||
|
||||
let mut addable_bindings = Vec::new();
|
||||
|
||||
for (_, (keys, ..)) in &self.add_shortcut.keys {
|
||||
if keys.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(binding) = Binding::from_str(keys) else {
|
||||
return Command::none();
|
||||
};
|
||||
|
||||
if !binding.is_set() {
|
||||
return Command::none();
|
||||
}
|
||||
|
||||
if let Some(action) = self.model.config_contains(&binding) {
|
||||
let action_str = super::localize_action(&action);
|
||||
self.replace_dialog.push((binding, action, action_str));
|
||||
continue;
|
||||
}
|
||||
|
||||
addable_bindings.push(binding);
|
||||
}
|
||||
|
||||
for binding in addable_bindings {
|
||||
self.add_shortcut(binding);
|
||||
}
|
||||
|
||||
self.model.on_enter();
|
||||
}
|
||||
|
||||
Message::EditCombination => {
|
||||
let (_, id, editing) = &mut self.add_shortcut.keys[0];
|
||||
*editing = true;
|
||||
return Command::batch(vec![
|
||||
widget::text_input::focus(id.clone()),
|
||||
widget::text_input::select_all(id.clone()),
|
||||
]);
|
||||
}
|
||||
|
||||
Message::NameSubmit => {
|
||||
if !self.add_shortcut.name.trim().is_empty() {
|
||||
return widget::text_input::focus(self.command_id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
Message::ReplaceApply => {
|
||||
if let Some((binding, ..)) = self.replace_dialog.pop() {
|
||||
self.model.config_remove(&binding);
|
||||
self.add_shortcut(binding);
|
||||
|
||||
if self.replace_dialog.is_empty() {
|
||||
self.model.on_enter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Message::ReplaceCancel => {
|
||||
_ = self.replace_dialog.pop();
|
||||
if self.replace_dialog.is_empty() {
|
||||
self.model.on_enter();
|
||||
}
|
||||
}
|
||||
|
||||
Message::Shortcut(message) => {
|
||||
if let ShortcutMessage::ShowShortcut(..) = message {
|
||||
self.add_shortcut.active = false;
|
||||
}
|
||||
|
||||
return self.model.update(message);
|
||||
}
|
||||
|
||||
Message::ShortcutContext => {
|
||||
self.add_shortcut.enable();
|
||||
return Command::batch(vec![
|
||||
cosmic::command::message(crate::app::Message::OpenContextDrawer(
|
||||
fl!("custom-shortcuts", "context").into(),
|
||||
)),
|
||||
widget::text_input::focus(self.name_id.clone()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn add_keybinding_context(&self) -> Element<'_, Message> {
|
||||
let name_input = widget::text_input("", &self.add_shortcut.name)
|
||||
.padding([6, 12])
|
||||
.on_input(Message::NameInput)
|
||||
.on_submit(Message::NameSubmit)
|
||||
.id(self.name_id.clone());
|
||||
|
||||
let command_input = widget::text_input("", &self.add_shortcut.command)
|
||||
.padding([6, 12])
|
||||
.on_input(Message::CommandInput)
|
||||
.on_submit(Message::EditCombination)
|
||||
.id(self.command_id.clone());
|
||||
|
||||
let name_control = widget::column()
|
||||
.spacing(4)
|
||||
.push(widget::text::body(fl!("shortcut-name")))
|
||||
.push(name_input);
|
||||
|
||||
let command_control = widget::column()
|
||||
.spacing(4)
|
||||
.push(widget::text::body(fl!("command")))
|
||||
.push(command_input);
|
||||
|
||||
let input_fields = widget::column()
|
||||
.spacing(12)
|
||||
.push(name_control)
|
||||
.push(command_control)
|
||||
.padding([16, 24]);
|
||||
|
||||
let keys = self.add_shortcut.keys.iter().fold(
|
||||
widget::list_column().spacing(0),
|
||||
|column, (id, (text, widget_id, editing))| {
|
||||
let key_combination = widget::editable_input(
|
||||
fl!("type-key-combination"),
|
||||
text,
|
||||
*editing,
|
||||
move |enable| Message::KeyEditing(id, enable),
|
||||
)
|
||||
.padding([0, 12])
|
||||
.on_input(move |input| Message::KeyInput(id, input))
|
||||
.on_submit(Message::AddKeybinding)
|
||||
.id(widget_id.clone())
|
||||
.apply(widget::container)
|
||||
.padding([8, 24]);
|
||||
|
||||
column.add(key_combination)
|
||||
},
|
||||
);
|
||||
|
||||
let controls = widget::list_column().add(input_fields).add(keys).spacing(0);
|
||||
|
||||
let add_keybinding_button = widget::button::standard(fl!("add-keybinding"))
|
||||
.on_press(Message::AddShortcut)
|
||||
.apply(widget::container)
|
||||
.width(Length::Fill)
|
||||
.align_x(Horizontal::Right);
|
||||
|
||||
widget::column()
|
||||
.spacing(32)
|
||||
.push(controls)
|
||||
.push(add_keybinding_button)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn add_shortcut(&mut self, mut binding: Binding) {
|
||||
self.add_shortcut.active = !self.replace_dialog.is_empty();
|
||||
binding.description = Some(self.add_shortcut.name.clone());
|
||||
let new_action = Action::Spawn(self.add_shortcut.command.clone());
|
||||
self.model.config_add(new_action, binding);
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("custom-shortcuts", "input-keyboard-symbolic")
|
||||
.title(fl!("custom-shortcuts"))
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
Some(vec![sections.insert(shortcuts())])
|
||||
}
|
||||
|
||||
fn dialog(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
// Check if a new shortcut is being added that requires a replace dialog.
|
||||
if let Some((binding, _action, action_str)) = self.replace_dialog.last() {
|
||||
let primary_action = button::suggested(fl!("replace")).on_press(Message::ReplaceApply);
|
||||
|
||||
let secondary_action = button::standard(fl!("cancel")).on_press(Message::ReplaceCancel);
|
||||
|
||||
let dialog = widget::dialog(fl!("replace-shortcut-dialog"))
|
||||
.icon(icon::from_name("dialog-warning").size(64))
|
||||
.body(fl!(
|
||||
"replace-shortcut-dialog",
|
||||
"desc",
|
||||
shortcut = binding.to_string(),
|
||||
name = action_str.clone()
|
||||
))
|
||||
.primary_action(primary_action)
|
||||
.secondary_action(secondary_action)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::CustomShortcuts);
|
||||
|
||||
return Some(dialog);
|
||||
}
|
||||
|
||||
// Check if a keybinding is being added that requires a replace dialog.
|
||||
self.model
|
||||
.dialog()
|
||||
.map(|el| el.map(|m| crate::pages::Message::CustomShortcuts(Message::Shortcut(m))))
|
||||
}
|
||||
|
||||
fn context_drawer(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
if self.add_shortcut.active {
|
||||
Some(self.add_keybinding_context())
|
||||
} else {
|
||||
self.model
|
||||
.context_drawer()
|
||||
.map(|el| el.map(Message::Shortcut))
|
||||
}
|
||||
.map(|el| el.map(crate::pages::Message::CustomShortcuts))
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
_page: cosmic_settings_page::Entity,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Command<crate::pages::Message> {
|
||||
self.model.on_clear();
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
fn bindings(_defaults: &Shortcuts, keybindings: &Shortcuts) -> Slab<ShortcutModel> {
|
||||
keybindings
|
||||
.iter()
|
||||
.fold(Slab::new(), |mut slab, (binding, action)| {
|
||||
if let Action::Spawn(command) = action {
|
||||
let description = binding
|
||||
.description
|
||||
.clone()
|
||||
.unwrap_or_else(|| command.to_owned());
|
||||
|
||||
let new_binding = ShortcutBinding {
|
||||
id: widget::Id::unique(),
|
||||
binding: binding.clone(),
|
||||
input: String::new(),
|
||||
editing: false,
|
||||
is_default: false,
|
||||
};
|
||||
|
||||
if let Some((_, existing_model)) =
|
||||
slab.iter_mut().find(|(_, m)| &m.action == action)
|
||||
{
|
||||
existing_model.description = description;
|
||||
existing_model.bindings.insert(new_binding);
|
||||
} else {
|
||||
slab.insert(ShortcutModel {
|
||||
action: action.clone(),
|
||||
bindings: {
|
||||
let mut slab = Slab::new();
|
||||
slab.insert(new_binding);
|
||||
slab
|
||||
},
|
||||
description,
|
||||
modified: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
slab
|
||||
})
|
||||
}
|
||||
|
||||
fn shortcuts() -> Section<crate::pages::Message> {
|
||||
let descriptions = Slab::new();
|
||||
|
||||
// TODO: Add shortcuts to descriptions
|
||||
|
||||
Section::default()
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, _section| {
|
||||
let content = if page.model.shortcut_models.is_empty() {
|
||||
widget::settings::view_section("")
|
||||
.add(widget::settings::item_row(vec![widget::text::body(fl!(
|
||||
"custom-shortcuts",
|
||||
"none"
|
||||
))
|
||||
.into()]))
|
||||
.into()
|
||||
} else {
|
||||
page.model.view().map(Message::Shortcut)
|
||||
};
|
||||
|
||||
let add_shortcut = widget::button::standard(fl!("custom-shortcuts", "add"))
|
||||
.on_press(Message::ShortcutContext)
|
||||
.apply(widget::container)
|
||||
.width(Length::Fill)
|
||||
.align_x(Horizontal::Right);
|
||||
|
||||
widget::column()
|
||||
.push(content)
|
||||
.push(add_shortcut)
|
||||
.spacing(24)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::CustomShortcuts)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
use super::{ShortcutMessage, ShortcutModel};
|
||||
use cosmic::{Command, Element};
|
||||
use cosmic_settings_config::shortcuts::action::ResizeDirection;
|
||||
use cosmic_settings_config::shortcuts::Action;
|
||||
use cosmic_settings_page::{self as page, section, Section};
|
||||
use slab::Slab;
|
||||
|
||||
pub struct Page {
|
||||
model: super::Model,
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
model: super::Model::default().actions(|defaults, keybindings| {
|
||||
actions().iter().fold(Slab::new(), |mut slab, action| {
|
||||
slab.insert(ShortcutModel::new(defaults, keybindings, action.clone()));
|
||||
slab
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: ShortcutMessage) -> Command<crate::app::Message> {
|
||||
self.model.update(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("manage-windows", "input-keyboard-symbolic").title(fl!("manage-windows"))
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut slotmap::SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
Some(vec![sections.insert(shortcuts())])
|
||||
}
|
||||
|
||||
fn context_drawer(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.context_drawer()
|
||||
.map(|el| el.map(crate::pages::Message::ManageWindowShortcuts))
|
||||
}
|
||||
|
||||
fn dialog(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.dialog()
|
||||
.map(|el| el.map(crate::pages::Message::ManageWindowShortcuts))
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
_page: cosmic_settings_page::Entity,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Command<crate::pages::Message> {
|
||||
self.model.on_clear();
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
#[must_use]
|
||||
pub const fn actions() -> &'static [Action] {
|
||||
&[
|
||||
Action::Close,
|
||||
Action::Maximize,
|
||||
Action::Minimize,
|
||||
Action::Resizing(ResizeDirection::Inwards),
|
||||
Action::Resizing(ResizeDirection::Outwards),
|
||||
Action::ToggleSticky,
|
||||
]
|
||||
}
|
||||
fn shortcuts() -> Section<crate::pages::Message> {
|
||||
let mut descriptions = Slab::new();
|
||||
|
||||
// Make these searchable in the global settings search.
|
||||
for action in actions() {
|
||||
descriptions.insert(super::localize_action(action));
|
||||
}
|
||||
|
||||
Section::default()
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, _section| {
|
||||
page.model
|
||||
.view()
|
||||
.map(crate::pages::Message::ManageWindowShortcuts)
|
||||
})
|
||||
}
|
||||
620
cosmic-settings/src/pages/input/keyboard/shortcuts/mod.rs
Normal file
620
cosmic-settings/src/pages/input/keyboard/shortcuts/mod.rs
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
mod common;
|
||||
pub use common::{Model, ShortcutBinding, ShortcutMessage, ShortcutModel};
|
||||
|
||||
pub mod custom;
|
||||
pub mod manage_windows;
|
||||
pub mod move_window;
|
||||
pub mod nav;
|
||||
pub mod system;
|
||||
pub mod tiling;
|
||||
|
||||
use cosmic::iced::Length;
|
||||
use cosmic::widget::{self, icon, settings, text};
|
||||
use cosmic::{command, theme, Apply, Command, Element};
|
||||
use cosmic_config::ConfigGet;
|
||||
use cosmic_settings_config::shortcuts::action::{
|
||||
Direction, FocusDirection, Orientation, ResizeDirection,
|
||||
};
|
||||
use cosmic_settings_config::shortcuts::{self, Action, Shortcuts};
|
||||
use cosmic_settings_page::Section;
|
||||
use cosmic_settings_page::{self as page, section};
|
||||
use shortcuts::action::System as SystemAction;
|
||||
use slab::Slab;
|
||||
use slotmap::{DefaultKey, Key, SecondaryMap, SlotMap};
|
||||
|
||||
pub struct Page {
|
||||
modified: Modified,
|
||||
search: Search,
|
||||
search_model: Model,
|
||||
shortcuts_context: Option<cosmic_config::Config>,
|
||||
sub_pages: SubPages,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Modified {
|
||||
manage_windows: u16,
|
||||
move_windows: u16,
|
||||
nav: u16,
|
||||
system: u16,
|
||||
window_tiling: u16,
|
||||
custom: u16,
|
||||
}
|
||||
|
||||
struct SubPages {
|
||||
custom: page::Entity,
|
||||
manage_window: page::Entity,
|
||||
move_window: page::Entity,
|
||||
nav: page::Entity,
|
||||
system: page::Entity,
|
||||
window_tiling: page::Entity,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Search {
|
||||
input: String,
|
||||
actions: SlotMap<DefaultKey, Action>,
|
||||
localized: SecondaryMap<DefaultKey, String>,
|
||||
shortcuts: Shortcuts,
|
||||
defaults: Shortcuts,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Message {
|
||||
Category(Category),
|
||||
Search(String),
|
||||
SearchShortcut(ShortcutMessage),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum Category {
|
||||
Custom,
|
||||
ManageWindow,
|
||||
MoveWindow,
|
||||
Nav,
|
||||
System,
|
||||
WindowTiling,
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
modified: Modified::default(),
|
||||
search: Search::default(),
|
||||
search_model: Model::default(),
|
||||
shortcuts_context: None,
|
||||
sub_pages: SubPages {
|
||||
custom: page::Entity::null(),
|
||||
manage_window: page::Entity::null(),
|
||||
move_window: page::Entity::null(),
|
||||
nav: page::Entity::null(),
|
||||
system: page::Entity::null(),
|
||||
window_tiling: page::Entity::null(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
|
||||
fn context_drawer(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
if self.search_model.shortcut_models.is_empty() {
|
||||
None
|
||||
} else {
|
||||
self.search_model.context_drawer().map(|el| {
|
||||
el.map(|msg| crate::pages::Message::KeyboardShortcuts(Message::SearchShortcut(msg)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn dialog(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
if self.search_model.shortcut_models.is_empty() {
|
||||
None
|
||||
} else {
|
||||
self.search_model.dialog().map(|el| {
|
||||
el.map(|msg| crate::pages::Message::KeyboardShortcuts(Message::SearchShortcut(msg)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
_page: cosmic_settings_page::Entity,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
if self.shortcuts_context.is_none() {
|
||||
self.shortcuts_context = cosmic_settings_config::shortcuts::context().ok();
|
||||
}
|
||||
|
||||
if let Some(context) = self.shortcuts_context.as_ref() {
|
||||
let mut defaults = context.get::<Shortcuts>("defaults").unwrap_or_default();
|
||||
let custom = context.get::<Shortcuts>("custom").unwrap_or_default();
|
||||
|
||||
for (custom_binding, custom_action) in &custom.0 {
|
||||
// Skip bindings for the super key
|
||||
if custom_binding.is_super() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if a custom binding overrides a default binding, or is in addition to it.
|
||||
match defaults.0.get(custom_binding) {
|
||||
Some(default_action) if default_action == custom_action => continue,
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match action_category(custom_action) {
|
||||
Some(Category::ManageWindow) => self.modified.manage_windows += 1,
|
||||
Some(Category::MoveWindow) => self.modified.move_windows += 1,
|
||||
Some(Category::Nav) => self.modified.nav += 1,
|
||||
Some(Category::System) => self.modified.system += 1,
|
||||
Some(Category::WindowTiling) => self.modified.window_tiling += 1,
|
||||
None | Some(Category::Custom) => (),
|
||||
}
|
||||
}
|
||||
|
||||
// Check if default bindings are missing
|
||||
for (binding, action) in &defaults.0 {
|
||||
if binding.is_super() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match custom.0.get(binding) {
|
||||
Some(custom_action) if action != custom_action => (),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
match action_category(action) {
|
||||
Some(Category::ManageWindow) => self.modified.manage_windows += 1,
|
||||
Some(Category::MoveWindow) => self.modified.move_windows += 1,
|
||||
Some(Category::Nav) => self.modified.nav += 1,
|
||||
Some(Category::System) => self.modified.system += 1,
|
||||
Some(Category::WindowTiling) => self.modified.window_tiling += 1,
|
||||
None | Some(Category::Custom) => (),
|
||||
}
|
||||
}
|
||||
|
||||
self.search.defaults = defaults.clone();
|
||||
defaults.0.extend(custom.0);
|
||||
self.search.shortcuts = defaults;
|
||||
}
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Command<crate::pages::Message> {
|
||||
self.search.actions.clear();
|
||||
self.search.localized.clear();
|
||||
self.search.input.clear();
|
||||
self.search_model.on_clear();
|
||||
self.modified.custom = 0;
|
||||
self.modified.manage_windows = 0;
|
||||
self.modified.move_windows = 0;
|
||||
self.modified.nav = 0;
|
||||
self.modified.system = 0;
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: Message) -> Command<crate::app::Message> {
|
||||
match message {
|
||||
Message::Category(category) => match category {
|
||||
Category::Custom => {
|
||||
command::message(crate::app::Message::Page(self.sub_pages.custom))
|
||||
}
|
||||
|
||||
Category::ManageWindow => {
|
||||
command::message(crate::app::Message::Page(self.sub_pages.manage_window))
|
||||
}
|
||||
|
||||
Category::MoveWindow => {
|
||||
command::message(crate::app::Message::Page(self.sub_pages.move_window))
|
||||
}
|
||||
|
||||
Category::Nav => command::message(crate::app::Message::Page(self.sub_pages.nav)),
|
||||
|
||||
Category::System => {
|
||||
command::message(crate::app::Message::Page(self.sub_pages.system))
|
||||
}
|
||||
|
||||
Category::WindowTiling => {
|
||||
command::message(crate::app::Message::Page(self.sub_pages.window_tiling))
|
||||
}
|
||||
},
|
||||
|
||||
Message::Search(input) => {
|
||||
self.search(input);
|
||||
Command::none()
|
||||
}
|
||||
|
||||
Message::SearchShortcut(message) => self.search_model.update(message),
|
||||
}
|
||||
}
|
||||
|
||||
fn search(&mut self, input: String) {
|
||||
self.search.input = input;
|
||||
if self.search.input.is_empty() {
|
||||
self.search_model.on_clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if self.search.actions.is_empty() {
|
||||
self.search.cache_localized_actions();
|
||||
}
|
||||
|
||||
self.search_model.shortcut_models = self.search.shortcut_models();
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {
|
||||
fn sub_pages(
|
||||
mut page: cosmic_settings_page::Insert<crate::pages::Message>,
|
||||
) -> cosmic_settings_page::Insert<crate::pages::Message> {
|
||||
let custom = page.sub_page_with_id::<custom::Page>();
|
||||
let manage_window = page.sub_page_with_id::<manage_windows::Page>();
|
||||
let move_window = page.sub_page_with_id::<move_window::Page>();
|
||||
let nav = page.sub_page_with_id::<nav::Page>();
|
||||
let system = page.sub_page_with_id::<system::Page>();
|
||||
let window_tiling = page.sub_page_with_id::<tiling::Page>();
|
||||
|
||||
let model = page.model.page_mut::<Page>().unwrap();
|
||||
model.sub_pages.custom = custom;
|
||||
model.sub_pages.manage_window = manage_window;
|
||||
model.sub_pages.move_window = move_window;
|
||||
model.sub_pages.nav = nav;
|
||||
model.sub_pages.system = system;
|
||||
model.sub_pages.window_tiling = window_tiling;
|
||||
|
||||
page
|
||||
}
|
||||
}
|
||||
|
||||
impl Search {
|
||||
fn cache_localized_actions(&mut self) {
|
||||
self.actions.clear();
|
||||
self.localized.clear();
|
||||
|
||||
for action in all_actions() {
|
||||
let localized = localize_action(action);
|
||||
let id = self.actions.insert(action.clone());
|
||||
self.localized.insert(id, localized);
|
||||
}
|
||||
}
|
||||
|
||||
fn shortcut_models(&mut self) -> Slab<ShortcutModel> {
|
||||
let input = self.input.to_lowercase();
|
||||
self.actions
|
||||
.iter()
|
||||
.filter(|(id, _)| self.localized[*id].to_lowercase().contains(&input))
|
||||
.fold(Slab::new(), |mut slab, (_, action)| {
|
||||
slab.insert(ShortcutModel::new(
|
||||
&self.defaults,
|
||||
&self.shortcuts,
|
||||
action.clone(),
|
||||
));
|
||||
|
||||
slab
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn shortcuts() -> Section<crate::pages::Message> {
|
||||
let mut descriptions = Slab::new();
|
||||
|
||||
let custom_label = descriptions.insert(fl!("custom"));
|
||||
let manage_window_label = descriptions.insert(fl!("manage-windows"));
|
||||
let move_window_label = descriptions.insert(fl!("move-windows"));
|
||||
let nav_label = descriptions.insert(fl!("nav-shortcuts"));
|
||||
let system_label = descriptions.insert(fl!("system-shortcut"));
|
||||
let window_tiling_label = descriptions.insert(fl!("window-tiling"));
|
||||
|
||||
Section::default()
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, section| {
|
||||
let descriptions = §ion.descriptions;
|
||||
|
||||
let search = widget::search_input(fl!("type-to-search"), &page.search.input)
|
||||
.width(314)
|
||||
.on_clear(Message::Search(String::new()))
|
||||
.on_input(Message::Search)
|
||||
.apply(widget::container)
|
||||
.center_x()
|
||||
.width(Length::Fill);
|
||||
|
||||
// If the search input is not empty, show the category view, else the search results.
|
||||
let content = if page.search.input.is_empty() {
|
||||
settings::view_section("")
|
||||
.add(category_item(
|
||||
Category::ManageWindow,
|
||||
&descriptions[manage_window_label],
|
||||
page.modified.manage_windows,
|
||||
))
|
||||
.add(category_item(
|
||||
Category::MoveWindow,
|
||||
&descriptions[move_window_label],
|
||||
page.modified.move_windows,
|
||||
))
|
||||
.add(category_item(
|
||||
Category::Nav,
|
||||
&descriptions[nav_label],
|
||||
page.modified.nav,
|
||||
))
|
||||
.add(category_item(
|
||||
Category::System,
|
||||
&descriptions[system_label],
|
||||
page.modified.system,
|
||||
))
|
||||
.add(category_item(
|
||||
Category::WindowTiling,
|
||||
&descriptions[window_tiling_label],
|
||||
page.modified.window_tiling,
|
||||
))
|
||||
.add(category_item(
|
||||
Category::Custom,
|
||||
&descriptions[custom_label],
|
||||
page.modified.custom,
|
||||
))
|
||||
.apply(Element::from)
|
||||
} else {
|
||||
page.search_model.view().map(Message::SearchShortcut)
|
||||
};
|
||||
|
||||
widget::column::with_capacity(2)
|
||||
.spacing(32)
|
||||
.push(search)
|
||||
.push(content)
|
||||
.apply(Element::from)
|
||||
.map(crate::pages::Message::KeyboardShortcuts)
|
||||
})
|
||||
}
|
||||
|
||||
/// Display a category as a list item
|
||||
fn category_item(category: Category, name: &str, modified: u16) -> Element<Message> {
|
||||
let icon = icon::from_name("go-next-symbolic").size(16);
|
||||
|
||||
let control = if modified == 0 {
|
||||
Element::from(icon)
|
||||
} else {
|
||||
widget::row()
|
||||
.push(text::body(fl!("modified", count = modified)))
|
||||
.push(icon)
|
||||
.into()
|
||||
};
|
||||
|
||||
settings::item::builder(name)
|
||||
.control(control)
|
||||
.spacing(16)
|
||||
.apply(widget::container)
|
||||
.style(theme::Container::List)
|
||||
.apply(widget::button)
|
||||
.style(theme::Button::Transparent)
|
||||
.on_press(Message::Category(category))
|
||||
.into()
|
||||
}
|
||||
|
||||
fn action_category(action: &Action) -> Option<Category> {
|
||||
Some(if manage_windows::actions().contains(action) {
|
||||
Category::ManageWindow
|
||||
} else if move_window::actions().contains(action) {
|
||||
Category::MoveWindow
|
||||
} else if nav::actions().contains(action) {
|
||||
Category::Nav
|
||||
} else if system::actions().contains(action) {
|
||||
Category::System
|
||||
} else {
|
||||
return None;
|
||||
})
|
||||
}
|
||||
|
||||
fn all_actions() -> &'static [Action] {
|
||||
&[
|
||||
Action::Close,
|
||||
Action::Debug,
|
||||
Action::Focus(FocusDirection::Down),
|
||||
Action::Focus(FocusDirection::In),
|
||||
Action::Focus(FocusDirection::Left),
|
||||
Action::Focus(FocusDirection::Out),
|
||||
Action::Focus(FocusDirection::Right),
|
||||
Action::Focus(FocusDirection::Up),
|
||||
Action::LastWorkspace,
|
||||
Action::Maximize,
|
||||
Action::MigrateWorkspaceToNextOutput,
|
||||
Action::MigrateWorkspaceToOutput(Direction::Down),
|
||||
Action::MigrateWorkspaceToOutput(Direction::Left),
|
||||
Action::MigrateWorkspaceToOutput(Direction::Right),
|
||||
Action::MigrateWorkspaceToOutput(Direction::Up),
|
||||
Action::MigrateWorkspaceToPreviousOutput,
|
||||
Action::Minimize,
|
||||
Action::Move(Direction::Down),
|
||||
Action::Move(Direction::Left),
|
||||
Action::Move(Direction::Right),
|
||||
Action::Move(Direction::Up),
|
||||
Action::MoveToLastWorkspace,
|
||||
Action::MoveToNextOutput,
|
||||
Action::MoveToNextWorkspace,
|
||||
Action::MoveToOutput(Direction::Down),
|
||||
Action::MoveToOutput(Direction::Left),
|
||||
Action::MoveToOutput(Direction::Right),
|
||||
Action::MoveToOutput(Direction::Up),
|
||||
Action::MoveToPreviousOutput,
|
||||
Action::MoveToPreviousWorkspace,
|
||||
Action::MoveToWorkspace(1),
|
||||
Action::MoveToWorkspace(2),
|
||||
Action::MoveToWorkspace(3),
|
||||
Action::MoveToWorkspace(4),
|
||||
Action::MoveToWorkspace(5),
|
||||
Action::MoveToWorkspace(6),
|
||||
Action::MoveToWorkspace(7),
|
||||
Action::MoveToWorkspace(8),
|
||||
Action::MoveToWorkspace(9),
|
||||
Action::NextOutput,
|
||||
Action::NextWorkspace,
|
||||
Action::Orientation(Orientation::Horizontal),
|
||||
Action::Orientation(Orientation::Vertical),
|
||||
Action::PreviousOutput,
|
||||
Action::PreviousWorkspace,
|
||||
Action::Resizing(ResizeDirection::Inwards),
|
||||
Action::Resizing(ResizeDirection::Outwards),
|
||||
Action::SwapWindow,
|
||||
Action::SwitchOutput(Direction::Down),
|
||||
Action::SwitchOutput(Direction::Left),
|
||||
Action::SwitchOutput(Direction::Right),
|
||||
Action::SwitchOutput(Direction::Up),
|
||||
Action::System(SystemAction::AppLibrary),
|
||||
Action::System(SystemAction::BrightnessDown),
|
||||
Action::System(SystemAction::BrightnessUp),
|
||||
Action::System(SystemAction::HomeFolder),
|
||||
Action::System(SystemAction::KeyboardBrightnessDown),
|
||||
Action::System(SystemAction::KeyboardBrightnessUp),
|
||||
Action::System(SystemAction::Launcher),
|
||||
Action::System(SystemAction::LockScreen),
|
||||
Action::System(SystemAction::Mute),
|
||||
Action::System(SystemAction::MuteMic),
|
||||
Action::System(SystemAction::Screenshot),
|
||||
Action::System(SystemAction::Terminal),
|
||||
Action::System(SystemAction::VolumeLower),
|
||||
Action::System(SystemAction::VolumeRaise),
|
||||
Action::System(SystemAction::WebBrowser),
|
||||
Action::System(SystemAction::WindowSwitcher),
|
||||
Action::System(SystemAction::WorkspaceOverview),
|
||||
Action::Terminate,
|
||||
Action::ToggleOrientation,
|
||||
Action::ToggleStacking,
|
||||
Action::ToggleSticky,
|
||||
Action::ToggleTiling,
|
||||
Action::ToggleWindowFloating,
|
||||
Action::Workspace(1),
|
||||
Action::Workspace(2),
|
||||
Action::Workspace(3),
|
||||
Action::Workspace(4),
|
||||
Action::Workspace(5),
|
||||
Action::Workspace(6),
|
||||
Action::Workspace(7),
|
||||
Action::Workspace(8),
|
||||
Action::Workspace(9),
|
||||
]
|
||||
}
|
||||
|
||||
fn localize_action(action: &Action) -> String {
|
||||
match action {
|
||||
Action::Close => fl!("manage-windows", "close"),
|
||||
Action::Disable => fl!("disabled"),
|
||||
Action::Focus(FocusDirection::Down) => fl!("nav-shortcuts", "focus", direction = "down"),
|
||||
Action::Focus(FocusDirection::In) => fl!("nav-shortcuts", "focus", direction = "in"),
|
||||
Action::Focus(FocusDirection::Left) => fl!("nav-shortcuts", "focus", direction = "left"),
|
||||
Action::Focus(FocusDirection::Out) => fl!("nav-shortcuts", "focus", direction = "out"),
|
||||
Action::Focus(FocusDirection::Right) => fl!("nav-shortcuts", "focus", direction = "right"),
|
||||
Action::Focus(FocusDirection::Up) => fl!("nav-shortcuts", "focus", direction = "up"),
|
||||
Action::Workspace(i) => fl!("nav-shortcuts", "workspace", num = (*i as usize)),
|
||||
Action::LastWorkspace => fl!("nav-shortcuts", "last-workspace"),
|
||||
Action::Maximize => fl!("manage-windows", "maximize"),
|
||||
Action::Minimize => fl!("manage-windows", "minimize"),
|
||||
Action::Move(Direction::Down) => fl!("move-windows", "direction", direction = "down"),
|
||||
Action::Move(Direction::Right) => fl!("move-windows", "direction", direction = "right"),
|
||||
Action::Move(Direction::Left) => fl!("move-windows", "direction", direction = "left"),
|
||||
Action::Move(Direction::Up) => fl!("move-windows", "direction", direction = "up"),
|
||||
Action::MoveToLastWorkspace | Action::SendToLastWorkspace => {
|
||||
fl!("move-windows", "last-workspace")
|
||||
}
|
||||
Action::MoveToNextOutput | Action::SendToNextOutput => fl!("move-windows", "next-display"),
|
||||
Action::MoveToNextWorkspace | Action::SendToNextWorkspace => {
|
||||
fl!("move-windows", "next-workspace")
|
||||
}
|
||||
Action::MoveToPreviousWorkspace | Action::SendToPreviousWorkspace => {
|
||||
fl!("move-windows", "prev-workspace")
|
||||
}
|
||||
Action::MoveToOutput(Direction::Down) | Action::SendToOutput(Direction::Down) => {
|
||||
fl!("move-windows", "display", direction = "down")
|
||||
}
|
||||
Action::MoveToOutput(Direction::Left) | Action::SendToOutput(Direction::Left) => {
|
||||
fl!("move-windows", "display", direction = "left")
|
||||
}
|
||||
Action::MoveToOutput(Direction::Right) | Action::SendToOutput(Direction::Right) => {
|
||||
fl!("move-windows", "display", direction = "right")
|
||||
}
|
||||
Action::MoveToOutput(Direction::Up) | Action::SendToOutput(Direction::Up) => {
|
||||
fl!("move-windows", "display", direction = "up")
|
||||
}
|
||||
Action::MoveToPreviousOutput | Action::SendToPreviousOutput => {
|
||||
fl!("move-windows", "prev-display")
|
||||
}
|
||||
Action::MoveToWorkspace(i) | Action::SendToWorkspace(i) => {
|
||||
fl!("move-windows", "workspace-num", num = (*i as usize))
|
||||
}
|
||||
Action::NextOutput => fl!("nav-shortcuts", "next-output"),
|
||||
Action::NextWorkspace => fl!("nav-shortcuts", "next-workspace"),
|
||||
Action::Orientation(Orientation::Horizontal) => fl!("window-tiling", "horizontal"),
|
||||
Action::Orientation(Orientation::Vertical) => fl!("window-tiling", "vertical"),
|
||||
Action::PreviousOutput => fl!("nav-shortcuts", "prev-output"),
|
||||
Action::PreviousWorkspace => fl!("nav-shortcuts", "prev-workspace"),
|
||||
Action::Resizing(ResizeDirection::Inwards) => fl!("manage-windows", "resize-inwards"),
|
||||
Action::Resizing(ResizeDirection::Outwards) => fl!("manage-windows", "resize-outwards"),
|
||||
Action::SwapWindow => fl!("window-tiling", "swap-window"),
|
||||
Action::SwitchOutput(Direction::Down) => fl!("nav-shortcuts", "output", direction = "down"),
|
||||
Action::SwitchOutput(Direction::Left) => fl!("nav-shortcuts", "output", direction = "left"),
|
||||
Action::SwitchOutput(Direction::Right) => {
|
||||
fl!("nav-shortcuts", "output", direction = "right")
|
||||
}
|
||||
Action::SwitchOutput(Direction::Up) => fl!("nav-shortcuts", "output", direction = "up"),
|
||||
Action::ToggleOrientation => fl!("window-tiling", "toggle-orientation"),
|
||||
Action::ToggleStacking => fl!("window-tiling", "toggle-stacking"),
|
||||
Action::ToggleSticky => fl!("manage-windows", "toggle-sticky"),
|
||||
Action::ToggleTiling => fl!("window-tiling", "toggle-tiling"),
|
||||
Action::ToggleWindowFloating => fl!("window-tiling", "toggle-floating"),
|
||||
|
||||
// Currently unused by any settings pages
|
||||
Action::Debug => fl!("debug"),
|
||||
|
||||
Action::MigrateWorkspaceToNextOutput => fl!("migrate-workspace-next"),
|
||||
Action::MigrateWorkspaceToOutput(Direction::Down) => {
|
||||
fl!("migrate-workspace", direction = "down")
|
||||
}
|
||||
Action::MigrateWorkspaceToOutput(Direction::Left) => {
|
||||
fl!("migrate-workspace", direction = "left")
|
||||
}
|
||||
Action::MigrateWorkspaceToOutput(Direction::Right) => {
|
||||
fl!("migrate-workspace", direction = "right")
|
||||
}
|
||||
Action::MigrateWorkspaceToOutput(Direction::Up) => {
|
||||
fl!("migrate-workspace", direction = "up")
|
||||
}
|
||||
Action::MigrateWorkspaceToPreviousOutput => fl!("migrate-workspace-prev"),
|
||||
|
||||
Action::Terminate => fl!("terminate"),
|
||||
|
||||
Action::System(system) => match system {
|
||||
SystemAction::AppLibrary => fl!("system-shortcut", "app-library"),
|
||||
SystemAction::BrightnessDown => fl!("system-shortcut", "brightness-down"),
|
||||
SystemAction::BrightnessUp => fl!("system-shortcut", "brightness-up"),
|
||||
SystemAction::HomeFolder => fl!("system-shortcut", "home-folder"),
|
||||
SystemAction::KeyboardBrightnessDown => {
|
||||
fl!("system-shortcut", "keyboard-brightness-down")
|
||||
}
|
||||
SystemAction::KeyboardBrightnessUp => fl!("system-shortcut", "keyboard-brightness-up"),
|
||||
SystemAction::Launcher => fl!("system-shortcut", "launcher"),
|
||||
SystemAction::LockScreen => fl!("system-shortcut", "lock-screen"),
|
||||
SystemAction::Mute => fl!("system-shortcut", "mute"),
|
||||
SystemAction::MuteMic => fl!("system-shortcut", "mute-mic"),
|
||||
SystemAction::Screenshot => fl!("system-shortcut", "screenshot"),
|
||||
SystemAction::Terminal => fl!("system-shortcut", "terminal"),
|
||||
SystemAction::VolumeLower => fl!("system-shortcut", "volume-lower"),
|
||||
SystemAction::VolumeRaise => fl!("system-shortcut", "volume-raise"),
|
||||
SystemAction::WebBrowser => fl!("system-shortcut", "web-browser"),
|
||||
SystemAction::WindowSwitcher => fl!("system-shortcut", "window-switcher"),
|
||||
SystemAction::WorkspaceOverview => fl!("system-shortcut", "workspace-overview"),
|
||||
},
|
||||
|
||||
Action::Spawn(command) => command.clone(),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
use super::{ShortcutMessage, ShortcutModel};
|
||||
use cosmic::{Command, Element};
|
||||
use cosmic_settings_config::shortcuts::action::Direction;
|
||||
use cosmic_settings_config::shortcuts::Action;
|
||||
use cosmic_settings_page::{self as page, section, Section};
|
||||
use slab::Slab;
|
||||
|
||||
pub struct Page {
|
||||
model: super::Model,
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
model: super::Model::default().actions(|defaults, keybindings| {
|
||||
actions().iter().fold(Slab::new(), |mut slab, action| {
|
||||
slab.insert(ShortcutModel::new(defaults, keybindings, action.clone()));
|
||||
slab
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: ShortcutMessage) -> Command<crate::app::Message> {
|
||||
self.model.update(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("move-windows", "input-keyboard-symbolic").title(fl!("move-windows"))
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut slotmap::SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
Some(vec![sections.insert(shortcuts())])
|
||||
}
|
||||
|
||||
fn context_drawer(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.context_drawer()
|
||||
.map(|el| el.map(crate::pages::Message::MoveWindowShortcuts))
|
||||
}
|
||||
|
||||
fn dialog(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.dialog()
|
||||
.map(|el| el.map(crate::pages::Message::MoveWindowShortcuts))
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
_page: cosmic_settings_page::Entity,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Command<crate::pages::Message> {
|
||||
self.model.on_clear();
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
#[must_use]
|
||||
pub const fn actions() -> &'static [Action] {
|
||||
&[
|
||||
Action::Move(Direction::Down),
|
||||
Action::Move(Direction::Left),
|
||||
Action::Move(Direction::Right),
|
||||
Action::Move(Direction::Up),
|
||||
Action::MoveToPreviousWorkspace,
|
||||
Action::MoveToNextWorkspace,
|
||||
Action::MoveToLastWorkspace,
|
||||
Action::MoveToWorkspace(1),
|
||||
Action::MoveToWorkspace(2),
|
||||
Action::MoveToWorkspace(3),
|
||||
Action::MoveToWorkspace(4),
|
||||
Action::MoveToWorkspace(5),
|
||||
Action::MoveToWorkspace(6),
|
||||
Action::MoveToWorkspace(7),
|
||||
Action::MoveToWorkspace(8),
|
||||
Action::MoveToWorkspace(9),
|
||||
Action::MoveToPreviousOutput,
|
||||
Action::MoveToNextOutput,
|
||||
Action::MoveToOutput(Direction::Down),
|
||||
Action::MoveToOutput(Direction::Left),
|
||||
Action::MoveToOutput(Direction::Right),
|
||||
Action::MoveToOutput(Direction::Up),
|
||||
]
|
||||
}
|
||||
|
||||
fn shortcuts() -> Section<crate::pages::Message> {
|
||||
let mut descriptions = Slab::new();
|
||||
|
||||
// Make these searchable in the global settings search.
|
||||
for action in actions() {
|
||||
descriptions.insert(super::localize_action(action));
|
||||
}
|
||||
|
||||
Section::default()
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, _section| {
|
||||
page.model
|
||||
.view()
|
||||
.map(crate::pages::Message::MoveWindowShortcuts)
|
||||
})
|
||||
}
|
||||
107
cosmic-settings/src/pages/input/keyboard/shortcuts/nav.rs
Normal file
107
cosmic-settings/src/pages/input/keyboard/shortcuts/nav.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
use super::{ShortcutMessage, ShortcutModel};
|
||||
use cosmic::{Command, Element};
|
||||
use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection};
|
||||
use cosmic_settings_config::shortcuts::Action;
|
||||
use cosmic_settings_page::{self as page, section, Section};
|
||||
use slab::Slab;
|
||||
|
||||
pub struct Page {
|
||||
model: super::Model,
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
model: super::Model::default().actions(|defaults, keybindings| {
|
||||
actions().iter().fold(Slab::new(), |mut slab, action| {
|
||||
slab.insert(ShortcutModel::new(defaults, keybindings, action.clone()));
|
||||
slab
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: ShortcutMessage) -> Command<crate::app::Message> {
|
||||
self.model.update(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("nav-shortcuts", "input-keyboard-symbolic").title(fl!("nav-shortcuts"))
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut slotmap::SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
Some(vec![sections.insert(shortcuts())])
|
||||
}
|
||||
|
||||
fn context_drawer(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.context_drawer()
|
||||
.map(|el| el.map(crate::pages::Message::NavShortcuts))
|
||||
}
|
||||
|
||||
fn dialog(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.dialog()
|
||||
.map(|el| el.map(crate::pages::Message::NavShortcuts))
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
_page: cosmic_settings_page::Entity,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Command<crate::pages::Message> {
|
||||
self.model.on_clear();
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
#[must_use]
|
||||
pub const fn actions() -> &'static [Action] {
|
||||
&[
|
||||
Action::Focus(FocusDirection::Left),
|
||||
Action::Focus(FocusDirection::Right),
|
||||
Action::Focus(FocusDirection::Up),
|
||||
Action::Focus(FocusDirection::Down),
|
||||
Action::Focus(FocusDirection::In),
|
||||
Action::Focus(FocusDirection::Out),
|
||||
Action::PreviousWorkspace,
|
||||
Action::NextWorkspace,
|
||||
Action::LastWorkspace,
|
||||
Action::PreviousOutput,
|
||||
Action::NextOutput,
|
||||
Action::SwitchOutput(Direction::Left),
|
||||
Action::SwitchOutput(Direction::Right),
|
||||
Action::SwitchOutput(Direction::Up),
|
||||
Action::SwitchOutput(Direction::Down),
|
||||
]
|
||||
}
|
||||
|
||||
fn shortcuts() -> Section<crate::pages::Message> {
|
||||
let mut descriptions = Slab::new();
|
||||
|
||||
// Make these searchable in the global settings search.
|
||||
for action in actions() {
|
||||
descriptions.insert(super::localize_action(action));
|
||||
}
|
||||
|
||||
Section::default()
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, _section| {
|
||||
page.model.view().map(crate::pages::Message::NavShortcuts)
|
||||
})
|
||||
}
|
||||
111
cosmic-settings/src/pages/input/keyboard/shortcuts/system.rs
Normal file
111
cosmic-settings/src/pages/input/keyboard/shortcuts/system.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
use super::{ShortcutMessage, ShortcutModel};
|
||||
use cosmic::{Command, Element};
|
||||
use cosmic_settings_config::shortcuts::action::System as SystemAction;
|
||||
use cosmic_settings_config::shortcuts::Action;
|
||||
use cosmic_settings_page::{self as page, section, Section};
|
||||
use slab::Slab;
|
||||
|
||||
pub struct Page {
|
||||
model: super::Model,
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
model: super::Model::default().actions(|defaults, keybindings| {
|
||||
actions().iter().fold(Slab::new(), |mut slab, action| {
|
||||
slab.insert(ShortcutModel::new(defaults, keybindings, action.clone()));
|
||||
slab
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: ShortcutMessage) -> Command<crate::app::Message> {
|
||||
self.model.update(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("system-shortcut", "input-keyboard-symbolic").title(fl!("system-shortcut"))
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut slotmap::SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
Some(vec![sections.insert(shortcuts())])
|
||||
}
|
||||
|
||||
fn context_drawer(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.context_drawer()
|
||||
.map(|el| el.map(crate::pages::Message::SystemShortcuts))
|
||||
}
|
||||
|
||||
fn dialog(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.dialog()
|
||||
.map(|el| el.map(crate::pages::Message::SystemShortcuts))
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
_page: cosmic_settings_page::Entity,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Command<crate::pages::Message> {
|
||||
self.model.on_clear();
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
#[must_use]
|
||||
pub const fn actions() -> &'static [Action] {
|
||||
&[
|
||||
Action::System(SystemAction::AppLibrary),
|
||||
Action::System(SystemAction::Launcher),
|
||||
Action::System(SystemAction::WorkspaceOverview),
|
||||
Action::System(SystemAction::WindowSwitcher),
|
||||
Action::System(SystemAction::LockScreen),
|
||||
Action::System(SystemAction::VolumeLower),
|
||||
Action::System(SystemAction::VolumeRaise),
|
||||
Action::System(SystemAction::Mute),
|
||||
Action::System(SystemAction::MuteMic),
|
||||
Action::System(SystemAction::BrightnessDown),
|
||||
Action::System(SystemAction::BrightnessUp),
|
||||
Action::System(SystemAction::KeyboardBrightnessDown),
|
||||
Action::System(SystemAction::KeyboardBrightnessUp),
|
||||
Action::System(SystemAction::Screenshot),
|
||||
Action::System(SystemAction::Terminal),
|
||||
Action::System(SystemAction::HomeFolder),
|
||||
Action::System(SystemAction::WebBrowser),
|
||||
]
|
||||
}
|
||||
|
||||
fn shortcuts() -> Section<crate::pages::Message> {
|
||||
let mut descriptions = Slab::new();
|
||||
|
||||
// Make these searchable in the global settings search.
|
||||
for action in actions() {
|
||||
descriptions.insert(super::localize_action(action));
|
||||
}
|
||||
|
||||
Section::default()
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, _section| {
|
||||
page.model
|
||||
.view()
|
||||
.map(crate::pages::Message::SystemShortcuts)
|
||||
})
|
||||
}
|
||||
101
cosmic-settings/src/pages/input/keyboard/shortcuts/tiling.rs
Normal file
101
cosmic-settings/src/pages/input/keyboard/shortcuts/tiling.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
use super::{ShortcutMessage, ShortcutModel};
|
||||
use cosmic::{Command, Element};
|
||||
use cosmic_settings_config::shortcuts::action::Orientation;
|
||||
use cosmic_settings_config::shortcuts::Action;
|
||||
use cosmic_settings_page::{self as page, section, Section};
|
||||
use slab::Slab;
|
||||
|
||||
pub struct Page {
|
||||
model: super::Model,
|
||||
}
|
||||
|
||||
impl Default for Page {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
model: super::Model::default().actions(|defaults, keybindings| {
|
||||
actions().iter().fold(Slab::new(), |mut slab, action| {
|
||||
slab.insert(ShortcutModel::new(defaults, keybindings, action.clone()));
|
||||
slab
|
||||
})
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn update(&mut self, message: ShortcutMessage) -> Command<crate::app::Message> {
|
||||
self.model.update(message)
|
||||
}
|
||||
}
|
||||
|
||||
impl page::Page<crate::pages::Message> for Page {
|
||||
fn info(&self) -> page::Info {
|
||||
page::Info::new("window-tiling", "input-keyboard-symbolic").title(fl!("window-tiling"))
|
||||
}
|
||||
|
||||
fn content(
|
||||
&self,
|
||||
sections: &mut slotmap::SlotMap<section::Entity, Section<crate::pages::Message>>,
|
||||
) -> Option<page::Content> {
|
||||
Some(vec![sections.insert(shortcuts())])
|
||||
}
|
||||
|
||||
fn context_drawer(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.context_drawer()
|
||||
.map(|el| el.map(crate::pages::Message::TilingShortcuts))
|
||||
}
|
||||
|
||||
fn dialog(&self) -> Option<Element<'_, crate::pages::Message>> {
|
||||
self.model
|
||||
.dialog()
|
||||
.map(|el| el.map(crate::pages::Message::TilingShortcuts))
|
||||
}
|
||||
|
||||
fn on_enter(
|
||||
&mut self,
|
||||
_page: cosmic_settings_page::Entity,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Command::none()
|
||||
}
|
||||
|
||||
fn on_leave(&mut self) -> Command<crate::pages::Message> {
|
||||
self.model.on_clear();
|
||||
Command::none()
|
||||
}
|
||||
}
|
||||
|
||||
impl page::AutoBind<crate::pages::Message> for Page {}
|
||||
|
||||
#[must_use]
|
||||
pub fn actions() -> &'static [Action] {
|
||||
&[
|
||||
Action::ToggleTiling,
|
||||
Action::ToggleStacking,
|
||||
Action::ToggleWindowFloating,
|
||||
Action::ToggleOrientation,
|
||||
Action::Orientation(Orientation::Horizontal),
|
||||
Action::Orientation(Orientation::Horizontal),
|
||||
Action::SwapWindow,
|
||||
]
|
||||
}
|
||||
|
||||
fn shortcuts() -> Section<crate::pages::Message> {
|
||||
let mut descriptions = Slab::new();
|
||||
|
||||
// Make these searchable in the global settings search.
|
||||
for action in actions() {
|
||||
descriptions.insert(super::localize_action(action));
|
||||
}
|
||||
|
||||
Section::default()
|
||||
.descriptions(descriptions)
|
||||
.view::<Page>(move |_binder, page, _section| {
|
||||
page.model
|
||||
.view()
|
||||
.map(crate::pages::Message::TilingShortcuts)
|
||||
})
|
||||
}
|
||||
|
|
@ -16,21 +16,28 @@ pub mod time;
|
|||
pub enum Message {
|
||||
About(system::about::Message),
|
||||
Appearance(desktop::appearance::Message),
|
||||
CustomShortcuts(input::keyboard::shortcuts::custom::Message),
|
||||
DateAndTime(time::date::Message),
|
||||
Power(power::Message),
|
||||
Desktop(desktop::Message),
|
||||
DesktopOptions(desktop::options::Message),
|
||||
DesktopWallpaper(desktop::wallpaper::Message),
|
||||
DesktopWorkspaces(desktop::workspaces::Message),
|
||||
Displays(display::Message),
|
||||
Dock(desktop::dock::Message),
|
||||
DockApplet(desktop::dock::applets::Message),
|
||||
External { id: String, message: Vec<u8> },
|
||||
Input(input::Message),
|
||||
Keyboard(input::keyboard::Message),
|
||||
KeyboardShortcuts(input::keyboard::shortcuts::Message),
|
||||
Input(input::Message),
|
||||
ManageWindowShortcuts(input::keyboard::shortcuts::ShortcutMessage),
|
||||
MoveWindowShortcuts(input::keyboard::shortcuts::ShortcutMessage),
|
||||
NavShortcuts(input::keyboard::shortcuts::ShortcutMessage),
|
||||
Page(Entity),
|
||||
Panel(desktop::panel::Message),
|
||||
PanelApplet(desktop::panel::applets_inner::Message),
|
||||
Power(power::Message),
|
||||
SystemShortcuts(input::keyboard::shortcuts::ShortcutMessage),
|
||||
TilingShortcuts(input::keyboard::shortcuts::ShortcutMessage),
|
||||
}
|
||||
|
||||
impl From<Message> for crate::Message {
|
||||
|
|
|
|||
|
|
@ -99,12 +99,9 @@ impl PowerBackend for S76Backend {}
|
|||
|
||||
impl SetPowerProfile for S76Backend {
|
||||
async fn set_power_profile(&self, profile: PowerProfile) {
|
||||
let daemon = match get_s76power_daemon_proxy().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
tracing::error!("Problem while setting power profile.");
|
||||
return;
|
||||
}
|
||||
let Ok(daemon) = get_s76power_daemon_proxy().await else {
|
||||
tracing::error!("Problem while setting power profile.");
|
||||
return;
|
||||
};
|
||||
|
||||
match profile {
|
||||
|
|
@ -126,19 +123,15 @@ impl SetPowerProfile for S76Backend {
|
|||
|
||||
impl GetCurrentPowerProfile for S76Backend {
|
||||
async fn get_current_power_profile(&self) -> PowerProfile {
|
||||
let daemon = match get_s76power_daemon_proxy().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
tracing::error!("Problem while getting power profile.");
|
||||
//Default
|
||||
return PowerProfile::Balanced;
|
||||
}
|
||||
let Ok(daemon) = get_s76power_daemon_proxy().await else {
|
||||
tracing::error!("Problem while getting power profile.");
|
||||
return PowerProfile::Balanced;
|
||||
};
|
||||
|
||||
match daemon.get_profile().await {
|
||||
Ok(p) => PowerProfile::from_string(p.as_str()),
|
||||
//Default
|
||||
Err(_) => {
|
||||
Err(_why) => {
|
||||
tracing::error!("Problem while getting power profile.");
|
||||
//Default
|
||||
PowerProfile::Balanced
|
||||
|
|
@ -173,7 +166,7 @@ impl SetPowerProfile for PPBackend {
|
|||
async fn set_power_profile(&self, profile: PowerProfile) {
|
||||
let daemon = match get_power_profiles_proxy().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
Err(()) => {
|
||||
tracing::error!("Problem while setting power profile.");
|
||||
return;
|
||||
}
|
||||
|
|
@ -198,18 +191,14 @@ impl SetPowerProfile for PPBackend {
|
|||
|
||||
impl GetCurrentPowerProfile for PPBackend {
|
||||
async fn get_current_power_profile(&self) -> PowerProfile {
|
||||
let daemon = match get_power_profiles_proxy().await {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
tracing::error!("Problem while getting power profile.");
|
||||
//Default
|
||||
return PowerProfile::Balanced;
|
||||
}
|
||||
let Ok(daemon) = get_power_profiles_proxy().await else {
|
||||
tracing::error!("Problem while getting power profile.");
|
||||
return PowerProfile::Balanced;
|
||||
};
|
||||
|
||||
let profile = match daemon.active_profile().await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
Err(_e) => {
|
||||
tracing::error!("Problem while getting power profile.");
|
||||
//Default
|
||||
return PowerProfile::Balanced;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use zbus::{proxy, Connection};
|
||||
use zbus::proxy;
|
||||
|
||||
#[proxy(
|
||||
interface = "org.freedesktop.UPower.PowerProfiles",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use zbus::{proxy, Connection};
|
||||
use zbus::proxy;
|
||||
|
||||
#[proxy(
|
||||
interface = "com.system76.PowerDaemon",
|
||||
default_path = "/com/system76/PowerDaemon",
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
fn on_enter(
|
||||
&mut self,
|
||||
_page: page::Entity,
|
||||
sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
_sender: tokio::sync::mpsc::Sender<crate::pages::Message>,
|
||||
) -> Command<crate::pages::Message> {
|
||||
command::future(async move {
|
||||
crate::pages::Message::About(Message::Info(Box::new(Info::load())))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue