feat(shortcuts): runtime configurable keyboard shortcuts
This commit is contained in:
parent
6f051b2456
commit
cf322fdb5e
47 changed files with 3305 additions and 416 deletions
939
Cargo.lock
generated
939
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
12
Cargo.toml
12
Cargo.toml
|
|
@ -2,8 +2,9 @@
|
|||
members = ["cosmic-settings", "page", "pages/*"]
|
||||
default-members = ["cosmic-settings"]
|
||||
resolver = "2"
|
||||
rust-version = "1.71.0"
|
||||
sunrise_sunset = "1.0.1"
|
||||
|
||||
[workspace.package]
|
||||
rust-version = "1.75.0"
|
||||
|
||||
[workspace.dependencies]
|
||||
cosmic-randr = { git = "https://github.com/pop-os/cosmic-randr" }
|
||||
|
|
@ -11,7 +12,7 @@ tokio = { version = "1.37.0", features = ["macros"] }
|
|||
|
||||
[workspace.dependencies.libcosmic]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
features = ["dbus-config", "single-instance", "tokio", "wayland", "wgpu", "xdg-portal"]
|
||||
features = ["dbus-config", "single-instance", "multi-window", "tokio", "wayland", "wgpu", "xdg-portal"]
|
||||
|
||||
[workspace.dependencies.cosmic-config]
|
||||
git = "https://github.com/pop-os/libcosmic"
|
||||
|
|
@ -34,8 +35,13 @@ git = "https://github.com/smithay/client-toolkit/"
|
|||
package = "smithay-client-toolkit"
|
||||
rev = "3bed072"
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
lto = false
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = "thin"
|
||||
|
||||
[patch.'https://github.com/smithay/client-toolkit/']
|
||||
smithay-client-toolkit = { git = "https://github.com/smithay/client-toolkit//", rev = "3bed072" }
|
||||
|
|
|
|||
|
|
@ -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())))
|
||||
|
|
|
|||
|
|
@ -135,11 +135,6 @@ dock = Док
|
|||
hot-corner = Гарачы вугал
|
||||
.top-left-corner = Уключыць верхні левы гарачы вугал для працоўных прастораў
|
||||
|
||||
super-key-action = Дзеянне клавішы Super
|
||||
.launcher = Запускальнік
|
||||
.workspaces = Працоўныя прасторы
|
||||
.applications = Праграмы
|
||||
|
||||
top-panel = Верхняя панэль
|
||||
.workspaces = Паказаць кнопку Працоўныя прасторы
|
||||
.applications = Паказаць кнопку Праграмы
|
||||
|
|
|
|||
|
|
@ -23,11 +23,6 @@ notifications = Oznámení
|
|||
desktop-panel-options = Plocha a Panel
|
||||
.desc = Činnost super klávesy, roh obrazovky, nastavení ovládání oken.
|
||||
|
||||
super-key-action = Činnost super klávesy
|
||||
.launcher = Spouštěč
|
||||
.workspaces = Pracovní plochy
|
||||
.applications = Applikace
|
||||
|
||||
hot-corner = Rohy
|
||||
.top-left-corner = Povolit použití levého horního rohu pro otevření pracovních ploch
|
||||
|
||||
|
|
|
|||
|
|
@ -26,11 +26,6 @@ notifications = Benachrichtigungen
|
|||
desktop-options = Desktopoptionen
|
||||
.desc = Super Key Aktion, hot corners, Fenstersteuerung.
|
||||
|
||||
super-key-action = Super Key Aktion
|
||||
.launcher = Launcher
|
||||
.workspaces = Arbeitsbereiche
|
||||
.applications = Apps
|
||||
|
||||
hot-corner = Hot Corner
|
||||
.top-left-corner = Enable top-left hot corner for Workspaces
|
||||
|
||||
|
|
|
|||
|
|
@ -136,10 +136,10 @@ dock = Dock
|
|||
hot-corner = Hot Corner
|
||||
.top-left-corner = Enable top-left hot corner for Workspaces
|
||||
|
||||
super-key-action = Super Key Action
|
||||
.launcher = Launcher
|
||||
.workspaces = Workspaces
|
||||
.applications = Applications
|
||||
super-key = Super key
|
||||
.launcher = Open Launcher
|
||||
.workspaces = Open Workspaces
|
||||
.applications = Open Applications
|
||||
|
||||
top-panel = Top Panel
|
||||
.workspaces = Show Workspaces Button
|
||||
|
|
@ -401,6 +401,125 @@ type-to-search = Type to search...
|
|||
keyboard-shortcuts = Keyboard Shortcuts
|
||||
.desc = View and customize shortcuts
|
||||
|
||||
add-keybinding = Add keybinding
|
||||
cancel = Cancel
|
||||
command = Command
|
||||
custom = Custom
|
||||
debug = Debug
|
||||
disabled = Disabled
|
||||
migrate-workspace-prev = Migrate workspace to previous output
|
||||
migrate-workspace-next = Migrate workspace to next output
|
||||
migrate-workspace = Migrate workspace to output { $direction ->
|
||||
*[down] down
|
||||
[left] left
|
||||
[right] right
|
||||
[up] up
|
||||
}
|
||||
navigate = Navigate
|
||||
replace = Replace
|
||||
shortcut-name = Shortcut name
|
||||
system-controls = System controls
|
||||
terminate = Terminate
|
||||
toggle-stacking = Toggle window stacking
|
||||
type-key-combination = Type key combination
|
||||
unknown = Unknown
|
||||
|
||||
custom-shortcuts = Custom Shortcuts
|
||||
.add = Add shortcut
|
||||
.context = Add Custom Shortcut
|
||||
.none = No custom shortcuts
|
||||
|
||||
modified = { $count } modified
|
||||
|
||||
nav-shortcuts = Navigation
|
||||
.prev-output = Focus previous output
|
||||
.next-output = Focus next output
|
||||
.last-workspace = Focus last workspace
|
||||
.prev-workspace = Focus previous workspace
|
||||
.next-workspace = Focus next workspace
|
||||
.focus = Focus window { $direction ->
|
||||
*[down] down
|
||||
[in] in
|
||||
[left] left
|
||||
[out] out
|
||||
[right] right
|
||||
[up] up
|
||||
}
|
||||
.output = Switch to output { $direction ->
|
||||
*[down] down
|
||||
[left] left
|
||||
[right] right
|
||||
[up] up
|
||||
}
|
||||
.workspace = Switch to workspace { $num }
|
||||
|
||||
manage-windows = Manage windows
|
||||
.close = Close window
|
||||
.maximize = Maximize window
|
||||
.minimize = Minimize window
|
||||
.resize-inwards = Resize window inwards
|
||||
.resize-outwards = Resize window outwards
|
||||
.toggle-sticky = Toggle sticky window
|
||||
|
||||
move-windows = Move Windows
|
||||
.direction = Move window { $direction ->
|
||||
*[down] down
|
||||
[left] left
|
||||
[right] right
|
||||
[up] up
|
||||
}
|
||||
.display = Move window one monitor { $direction ->
|
||||
*[down] down
|
||||
[left] left
|
||||
[right] right
|
||||
[up] up
|
||||
}
|
||||
.workspace = Move window one workspace { $direction ->
|
||||
*[below] below
|
||||
[left] left
|
||||
[right] right
|
||||
[above] above
|
||||
}
|
||||
.workspace-num = Move window to workspace { $num }
|
||||
.prev-workspace = Move window to prev workspace
|
||||
.next-workspace = Move window to next workspace
|
||||
.last-workspace = Move window to last workspace
|
||||
.next-display = Move window to next display
|
||||
.prev-display = Move window to prev display
|
||||
.send-to-prev-workspace = Move window to previous workspace
|
||||
.send-to-next-workspace = Move window to next workspace
|
||||
|
||||
system-shortcut = System
|
||||
.app-library = Open the app library
|
||||
.brightness-down = Decrease display brightness
|
||||
.brightness-up = Increase display brightness
|
||||
.home-folder = Open home folder
|
||||
.keyboard-brightness-down = Decrease keyboard brightness
|
||||
.keyboard-brightness-up = Increase keyboard brightness
|
||||
.launcher = Open the launcher
|
||||
.lock-screen = Lock the screen
|
||||
.mute = Mute audio output
|
||||
.mute-mic = Mutes microphone input
|
||||
.screenshot = Take a screenshot
|
||||
.terminal = Open a terminal
|
||||
.volume-lower = Decrease audio output volume
|
||||
.volume-raise = Increase audio output volume
|
||||
.web-browser = Opens a web browser
|
||||
.window-switcher = Switch between open windows
|
||||
.workspace-overview = Open the workspace overview
|
||||
|
||||
window-tiling = Window tiling
|
||||
.horizontal = Set horizontal orientation
|
||||
.vertical = Set vertical orientation
|
||||
.swap-window = Swap window
|
||||
.toggle-tiling = Toggle window tiling
|
||||
.toggle-stacking = Toggle window stacking
|
||||
.toggle-floating = Toggle window floating
|
||||
.toggle-orientation = Toggle orientation
|
||||
|
||||
replace-shortcut-dialog = Replace Shortcut?
|
||||
.desc = { $shortcut } is used by { $name }. If you replace it, { $name } will be disabled.
|
||||
|
||||
## Input: Mouse
|
||||
|
||||
mouse = Mouse
|
||||
|
|
@ -443,13 +562,13 @@ open-workspaces-view = Open Workspaces Overview
|
|||
## Power
|
||||
|
||||
power = Power
|
||||
.desc = Manage power settings
|
||||
.desc = Manage power settings
|
||||
|
||||
power-mode = Power Mode
|
||||
.performance = High performance
|
||||
.balanced = Balanced
|
||||
.battery = Extended battery life
|
||||
.performance-desc = Peak performance and power usage.
|
||||
.balanced-desc = Quiet performance and moderate power usage.
|
||||
.battery-desc = Reduced power usage and silent performance.
|
||||
.nobackend = Backend not found. Install system76-power or power-profiles-daemon.
|
||||
.performance = High performance
|
||||
.balanced = Balanced
|
||||
.battery = Extended battery life
|
||||
.performance-desc = Peak performance and power usage.
|
||||
.balanced-desc = Quiet performance and moderate power usage.
|
||||
.battery-desc = Reduced power usage and silent performance.
|
||||
.nobackend = Backend not found. Install system76-power or power-profiles-daemon.
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@ dock = Dock
|
|||
hot-corner = Esquina Activa
|
||||
.top-left-corner = Habilitar esquina superior izquierda para Espacios de Trabajo
|
||||
|
||||
super-key-action = Acción de la tecla Super
|
||||
.launcher = Lanzador
|
||||
.workspaces = Espacios de Trabajo
|
||||
.applications = Aplicaciones
|
||||
|
||||
top-panel = Panel Superior
|
||||
.workspaces = Mostrar botón de Espacios de Trabajo
|
||||
.applications = Mostrar botón de Aplicaciones
|
||||
|
|
|
|||
|
|
@ -23,11 +23,6 @@ notifications = اعلانات
|
|||
desktop-panel-options = تنظیمات پنل میزکار
|
||||
.desc = عملکرد کلید سوپر، گوشههای داغ، گزینههای کنترل پنجره.
|
||||
|
||||
super-key-action = عملکرد کلید سوپر
|
||||
.launcher = راه انداز
|
||||
.workspaces = فضاهای کاری
|
||||
.applications = برنامهها
|
||||
|
||||
hot-corner = گوشهداغ
|
||||
.top-left-corner = فعال سازی گوشهداغ بالا چپ برای فضاهای کاری
|
||||
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@ dock = Dock
|
|||
hot-corner = Coin Actif
|
||||
.top-left-corner = Activer le coin actif supérieur gauche pour les Espaces de Travail
|
||||
|
||||
super-key-action = Action de la touche Super
|
||||
.launcher = Lanceur
|
||||
.workspaces = Espaces de Travail
|
||||
.applications = Applications
|
||||
|
||||
top-panel = Panneau supérieur
|
||||
.workspaces = Afficher le bouton Espace de Travail
|
||||
.applications = Afficher le bouton Applications
|
||||
|
|
|
|||
|
|
@ -27,11 +27,6 @@ notifications = सूचनाएं
|
|||
desktop-options = डेस्कटॉप विकल्प
|
||||
.desc = सुपर की एक्शन, हॉट कॉर्नर, विंडो कंट्रोल विकल्प।
|
||||
|
||||
super-key-action = सुपर की एक्शन
|
||||
.launcher = लांचर
|
||||
.workspaces = कार्यस्थानों
|
||||
.applications = अनुप्रयोग
|
||||
|
||||
hot-corner = गर्म कोना
|
||||
.top-left-corner = कार्यस्थानों के लिए शीर्ष-बाएँ हॉट कॉर्नर को सक्षम करें
|
||||
|
||||
|
|
|
|||
|
|
@ -125,11 +125,6 @@ dock = Barra delle applicazioni
|
|||
hot-corner = Bordi reattivi
|
||||
.top-left-corner = Abilita bordo reattivo in alto a sinistra per gli spazi di lavoro
|
||||
|
||||
super-key-action = Azione tasto Super
|
||||
.launcher = Launcher
|
||||
.workspaces = Spazi di lavoro
|
||||
.applications = Applicazioni
|
||||
|
||||
top-panel = Pannello superiore
|
||||
.workspaces = Pulsante "mostra spazi di lavoro"
|
||||
.applications = Pulsante "mostra applicazioni"
|
||||
|
|
|
|||
|
|
@ -134,11 +134,6 @@ dock = ドック
|
|||
hot-corner = ホットコーナー
|
||||
.top-left-corner = ワークスペースための左上のホットコーナーを有効にする
|
||||
|
||||
super-key-action = スーパーキーの行動
|
||||
.launcher = ランチャー
|
||||
.workspaces = ワークスペース
|
||||
.applications = アプリケーション
|
||||
|
||||
top-panel = トップパネル
|
||||
.workspaces = ワークスペースボタンを表示
|
||||
.applications = アプリケーションボタンを表示
|
||||
|
|
|
|||
|
|
@ -141,11 +141,6 @@ dock = Dok
|
|||
hot-corner = Narożniki Funkcyjne
|
||||
.top-left-corner = Włącz Obszary Robocze w lewym górnym narożniku funkcyjnym.
|
||||
|
||||
super-key-action = Akcje Klawisza Super
|
||||
.launcher = Program Startowy
|
||||
.workspaces = Obszary Robocze
|
||||
.applications = Aplikacje
|
||||
|
||||
top-panel = Górny Panel
|
||||
.workspaces = Pokaż Przycisk Obszarów Roboczych
|
||||
.applications = Pokaż Przycisk Aplikacji
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@ dock = Dock
|
|||
hot-corner = Canto ativo
|
||||
.top-left-corner = Ativar o canto superior esquerdo para áreas de trabalho
|
||||
|
||||
super-key-action = Ações da tecla Super
|
||||
.launcher = Inicializador
|
||||
.workspaces = Áreas de Trabalho
|
||||
.applications = Aplicações
|
||||
|
||||
top-panel = Painel superior
|
||||
.workspaces = Mostrar Botão de Áreas de Trabalho
|
||||
.applications = Mostrar Botão de Aplicações
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@ dock = Doca
|
|||
hot-corner = Canto ativo
|
||||
.top-left-corner = Ativar o canto superior esquerdo para as áreas de trabalho
|
||||
|
||||
super-key-action = Ação da tecla Super
|
||||
.launcher = Lançador
|
||||
.workspaces = Áreas de trabalho
|
||||
.applications = Aplicações
|
||||
|
||||
top-panel = Painel superior
|
||||
.workspaces = Mostrar o botão das áreas de trabalho
|
||||
.applications = Mostrar o botão das aplicações
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@ dock = Dock
|
|||
hot-corner = Marginea ecranului
|
||||
.top-left-corner = Activați marginea din stânga sus pentru Spații de lucru
|
||||
|
||||
super-key-action = Acțiunea butonului Super
|
||||
.launcher = Lansator
|
||||
.workspaces = Spații de lucru
|
||||
.applications = Aplicații
|
||||
|
||||
top-panel = Panoul de sus
|
||||
.workspaces = Afișați butonul pentru Spații de lucru
|
||||
.applications = Afișați butonul pentru aplicații
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@ dock = Док
|
|||
hot-corner = Активные углы
|
||||
.top-left-corner = Открывать рабочие места при наведении в левый верхний угол
|
||||
|
||||
super-key-action = Действие кнопки Super
|
||||
.launcher = Панель запуска
|
||||
.workspaces = Рабочие места
|
||||
.applications = Приложения
|
||||
|
||||
top-panel = Верхняя панель
|
||||
.workspaces = Отображать кнопку «Рабочие места»
|
||||
.applications = Отображать кнопку «Приложения»
|
||||
|
|
|
|||
|
|
@ -121,11 +121,6 @@ dock = Док
|
|||
hot-corner = Лепљиви углови
|
||||
.top-left-corner = Укључити горњи леви лепљиви угао за радне просторе
|
||||
|
||||
super-key-action = Улога Super тастера
|
||||
.launcher = Покретач апликација
|
||||
.workspaces = Радни простори
|
||||
.applications = Апликације
|
||||
|
||||
top-panel = Горњи панел
|
||||
.workspaces = Дугме за приказивање радних простора
|
||||
.applications = Дугме за приказивање апликација
|
||||
|
|
|
|||
|
|
@ -121,11 +121,6 @@ dock = Dok
|
|||
hot-corner = Lepljivi uglovi
|
||||
.top-left-corner = Uključiti gornji levi lepljivi ugao za radne prostore
|
||||
|
||||
super-key-action = Uloga Super tastera
|
||||
.launcher = Pokretač aplikacija
|
||||
.workspaces = Radni prostori
|
||||
.applications = Aplikacije
|
||||
|
||||
top-panel = Gornji panel
|
||||
.workspaces = Dugme za prikazivanje radnih prostora
|
||||
.applications = Dugme za prikazivanje aplikacija
|
||||
|
|
|
|||
|
|
@ -118,11 +118,6 @@ dock = Docka
|
|||
hot-corner = Het hörn
|
||||
.top-left-corner = Aktivera det övre vänstra hörnet för arbetsytor
|
||||
|
||||
super-key-action = Supertangent åtgärd
|
||||
.launcher = Programstartare
|
||||
.workspaces = Arbetsytor
|
||||
.applications = Applikationer
|
||||
|
||||
top-panel = Övre Panel
|
||||
.workspaces = Visa knappen arbetsytor
|
||||
.applications = Visa knappen applikationer
|
||||
|
|
|
|||
|
|
@ -24,11 +24,6 @@ notifications = Bildirimler
|
|||
desktop-panel-options = Masaüstü ve Panel
|
||||
.desc = Logo Tuşu eylemi, hızlı köşeler, pencere kontrol seçenekleri.
|
||||
|
||||
super-key-action = Logo Tuşu Eylemi
|
||||
.launcher = Başlatıcı
|
||||
.workspaces = Çalışma Alanları
|
||||
.applications = Uygulamalar
|
||||
|
||||
hot-corner = Hızlı Köşe
|
||||
.top-left-corner = Çalışma Alanları için sol-üst hızlı köşeyi etkinleştir
|
||||
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@ dock = 程序坞
|
|||
hot-corner = 热区
|
||||
.top-left-corner = 启用左上角热区来查看工作区概览
|
||||
|
||||
super-key-action = Super 键行为
|
||||
.launcher = 启动器
|
||||
.workspaces = 工作区概览
|
||||
.applications = 应用程序库
|
||||
|
||||
top-panel = 顶部面板
|
||||
.workspaces = 显示工作区概览按钮
|
||||
.applications = 显示应用程序库按钮
|
||||
|
|
|
|||
|
|
@ -136,11 +136,6 @@ dock = Dock
|
|||
hot-corner = 螢幕角落熱點
|
||||
.top-left-corner = 為工作區啟用位於左上方的螢幕角落熱點
|
||||
|
||||
super-key-action = Super 按鍵行為
|
||||
.launcher = 啟動器
|
||||
.workspaces = 工作區
|
||||
.applications = 應用程式
|
||||
|
||||
top-panel = 頂部面板
|
||||
.workspaces = 顯示工作區按鈕
|
||||
.applications = 顯示應用程式按鈕
|
||||
|
|
|
|||
|
|
@ -38,4 +38,21 @@ impl<'a, Message: 'static> Insert<'a, Message> {
|
|||
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(clippy::return_self_not_must_use)]
|
||||
#[allow(clippy::must_use_candidate)]
|
||||
pub fn sub_page_with_id<P: AutoBind<Message>>(&mut self) -> Entity {
|
||||
let sub_page = self.model.register::<P>().id();
|
||||
|
||||
self.model.info[sub_page].parent = Some(self.id);
|
||||
|
||||
self.model
|
||||
.sub_pages
|
||||
.entry(self.id)
|
||||
.expect("parent page missing")
|
||||
.and_modify(|v| v.push(sub_page))
|
||||
.or_insert_with(|| vec![sub_page]);
|
||||
|
||||
sub_page
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ pub trait Page<Message: 'static>: Downcast {
|
|||
None
|
||||
}
|
||||
|
||||
/// Set a custom page header
|
||||
fn header(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Display an inner app dialog for the page.
|
||||
fn dialog(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
|
|
@ -116,6 +121,7 @@ impl Info {
|
|||
#[macro_export]
|
||||
macro_rules! update {
|
||||
($binder:expr, $message:expr, $page:ty) => {{
|
||||
#[allow(unused_must_use)]
|
||||
if let Some(page) = $binder.page_mut::<$page>() {
|
||||
page.update($message);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue