improv(keyboard): shortcuts UI improvements
This commit is contained in:
parent
bcd8293c3e
commit
48e14d4add
20 changed files with 703 additions and 497 deletions
682
Cargo.lock
generated
682
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -12,8 +12,8 @@ ashpd = { version = "0.9", default-features = false, features = [
|
|||
"tokio",
|
||||
], optional = true }
|
||||
async-channel = "2.3.1"
|
||||
chrono = "0.4.39"
|
||||
clap = { version = "4.5.29", features = ["derive"] }
|
||||
chrono = "0.4.40"
|
||||
clap = { version = "4.5.32", features = ["derive"] }
|
||||
color-eyre = "0.6.3"
|
||||
cosmic-bg-config.workspace = true
|
||||
cosmic-comp-config = { workspace = true, optional = true }
|
||||
|
|
@ -34,7 +34,7 @@ derive_setters = "0.1.6"
|
|||
dirs = "5.0.1"
|
||||
downcast-rs = "1.2.1"
|
||||
eyre = "0.6.12"
|
||||
freedesktop-desktop-entry = "0.7.8"
|
||||
freedesktop-desktop-entry = "0.7.9"
|
||||
futures = "0.3.31"
|
||||
hostname-validator = "1.1.1"
|
||||
hostname1-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
|
|
@ -46,24 +46,24 @@ image = { version = "0.25", default-features = false, features = [
|
|||
"webp",
|
||||
"hdr",
|
||||
] }
|
||||
indexmap = "2.7.1"
|
||||
indexmap = "2.8.0"
|
||||
itertools = "0.13.0"
|
||||
itoa = "1.0.14"
|
||||
itoa = "1.0.15"
|
||||
libcosmic.workspace = true
|
||||
locale1 = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
mime-apps = { package = "cosmic-mime-apps", git = "https://github.com/pop-os/cosmic-mime-apps", optional = true }
|
||||
notify = "6.1.1"
|
||||
once_cell = "1.20.3"
|
||||
once_cell = "1.21.1"
|
||||
regex = "1.11.1"
|
||||
ron = "0.8"
|
||||
rust-embed = "8.5.0"
|
||||
ron = "0.9.0"
|
||||
rust-embed = "8.6.0"
|
||||
sctk = { workspace = true, optional = true }
|
||||
secure-string = "0.3.0"
|
||||
serde = { version = "1.0.217", features = ["derive"] }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
slab = "0.4.9"
|
||||
slotmap = "1.0.7"
|
||||
static_init = "1.0.3"
|
||||
sunrise = "1.0.1"
|
||||
sunrise = "1.2.1"
|
||||
tachyonix = "0.3.1"
|
||||
timedate-zbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true }
|
||||
tokio = { workspace = true, features = ["fs", "io-util", "sync"] }
|
||||
|
|
@ -78,10 +78,10 @@ zbus = { version = "4.4.0", default-features = false, features = [
|
|||
"tokio",
|
||||
], optional = true }
|
||||
zbus_polkit = { version = "4.0.0", optional = true }
|
||||
fontdb = "0.16.2"
|
||||
fontdb = "=0.16.2"
|
||||
fixed_decimal = "0.5.6"
|
||||
mime = "0.3.17"
|
||||
rustix = "0.38.44"
|
||||
rustix = { version = "1.0.3", features = ["process"] }
|
||||
gettext-rs = { version = "0.7.2", features = [
|
||||
"gettext-system",
|
||||
], optional = true }
|
||||
|
|
|
|||
|
|
@ -754,10 +754,7 @@ impl cosmic::Application for SettingsApp {
|
|||
self.context_title = Some(title.to_string());
|
||||
}
|
||||
|
||||
Message::CloseContextDrawer => {
|
||||
self.core.window.show_context = false;
|
||||
self.active_context_page = None;
|
||||
}
|
||||
Message::CloseContextDrawer => return self.close_context_drawer(),
|
||||
|
||||
Message::Error(error) => {
|
||||
tracing::error!(error, "error occurred");
|
||||
|
|
@ -869,7 +866,9 @@ impl SettingsApp {
|
|||
|
||||
if current_page != page {
|
||||
self.loaded_pages.remove(¤t_page);
|
||||
close_context_drawer_task = cosmic::task::message(Message::CloseContextDrawer);
|
||||
|
||||
close_context_drawer_task = self.close_context_drawer();
|
||||
|
||||
leave_task = self
|
||||
.pages
|
||||
.on_leave(current_page)
|
||||
|
|
@ -920,6 +919,16 @@ impl SettingsApp {
|
|||
}
|
||||
}
|
||||
|
||||
fn close_context_drawer(&mut self) -> Task<Message> {
|
||||
self.core.window.show_context = false;
|
||||
self.active_context_page = None;
|
||||
self.pages
|
||||
.on_context_drawer_close(self.active_page)
|
||||
.unwrap_or(iced::Task::none())
|
||||
.map(Message::PageMessage)
|
||||
.map(Into::into)
|
||||
}
|
||||
|
||||
/// Adds a main page to the settings application.
|
||||
fn insert_page<P: page::AutoBind<crate::pages::Message>>(
|
||||
&mut self,
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ macro_rules! fl {
|
|||
|
||||
// Get the `Localizer` to be used for localizing this library.
|
||||
#[must_use]
|
||||
#[inline(always)]
|
||||
pub fn localizer() -> Box<dyn Localizer> {
|
||||
Box::from(DefaultLocalizer::new(&*LANGUAGE_LOADER, &Localizations))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use cosmic::iced::{Alignment, Length};
|
||||
use cosmic::widget::{self, button, icon, settings, text};
|
||||
use cosmic::{theme, Apply, Element, Task};
|
||||
use cosmic::{Apply, Element, Task, theme};
|
||||
use cosmic_config::{ConfigGet, ConfigSet};
|
||||
use cosmic_settings_config::shortcuts::{self, Action, Binding, Shortcuts};
|
||||
use cosmic_settings_page as page;
|
||||
|
|
@ -15,7 +15,7 @@ use std::str::FromStr;
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ShortcutMessage {
|
||||
AddKeybinding,
|
||||
AddAnotherKeybinding,
|
||||
ApplyReplace,
|
||||
CancelReplace,
|
||||
DeleteBinding(usize),
|
||||
|
|
@ -32,8 +32,18 @@ pub struct ShortcutBinding {
|
|||
pub id: widget::Id,
|
||||
pub binding: Binding,
|
||||
pub input: String,
|
||||
pub editing: bool,
|
||||
pub is_default: bool,
|
||||
pub is_saved: bool,
|
||||
}
|
||||
|
||||
impl ShortcutBinding {
|
||||
pub fn reset(&mut self) {
|
||||
self.input = if self.is_saved {
|
||||
self.binding.to_string()
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -57,8 +67,8 @@ impl ShortcutModel {
|
|||
id: widget::Id::unique(),
|
||||
binding: binding.clone(),
|
||||
input: String::new(),
|
||||
editing: false,
|
||||
is_default,
|
||||
is_saved: true,
|
||||
});
|
||||
|
||||
(slab, if is_default { modified } else { modified + 1 })
|
||||
|
|
@ -94,10 +104,13 @@ impl ShortcutModel {
|
|||
#[must_use]
|
||||
pub struct Model {
|
||||
pub entity: page::Entity,
|
||||
pub add_keybindings_button_id: cosmic::widget::Id,
|
||||
pub defaults: Shortcuts,
|
||||
pub editing: Option<usize>,
|
||||
pub replace_dialog: Option<(usize, Binding, Action, String)>,
|
||||
pub shortcut_models: Slab<ShortcutModel>,
|
||||
pub shortcut_context: Option<usize>,
|
||||
pub shortcut_title: String,
|
||||
pub config: cosmic_config::Config,
|
||||
pub custom: bool,
|
||||
pub actions: fn(&Shortcuts, &Shortcuts) -> Slab<ShortcutModel>,
|
||||
|
|
@ -107,10 +120,13 @@ impl Default for Model {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
entity: page::Entity::null(),
|
||||
add_keybindings_button_id: widget::Id::unique(),
|
||||
defaults: Shortcuts::default(),
|
||||
editing: None,
|
||||
replace_dialog: None,
|
||||
shortcut_models: Slab::new(),
|
||||
shortcut_context: None,
|
||||
shortcut_title: String::new(),
|
||||
config: shortcuts::context().unwrap(),
|
||||
custom: false,
|
||||
actions: |_, _| Slab::new(),
|
||||
|
|
@ -153,9 +169,16 @@ impl Model {
|
|||
}
|
||||
|
||||
pub(super) fn context_drawer(&self) -> Option<Element<'_, ShortcutMessage>> {
|
||||
self.shortcut_context
|
||||
.as_ref()
|
||||
.map(|id| context_drawer(&self.shortcut_models, *id, self.custom))
|
||||
self.shortcut_context.as_ref().map(|id| {
|
||||
context_drawer(
|
||||
&self.shortcut_title,
|
||||
&self.shortcut_models,
|
||||
self.editing,
|
||||
self.add_keybindings_button_id.clone(),
|
||||
*id,
|
||||
self.custom,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn dialog(&self) -> Option<Element<'_, ShortcutMessage>> {
|
||||
|
|
@ -207,6 +230,25 @@ impl Model {
|
|||
}
|
||||
|
||||
self.shortcut_models = (self.actions)(&self.defaults, &shortcuts);
|
||||
self.shortcut_context = None;
|
||||
self.editing = None;
|
||||
}
|
||||
|
||||
pub(super) fn on_context_drawer_close(&mut self) {
|
||||
if let Some(short_id) = self.shortcut_context.take() {
|
||||
if let Some(model) = self.shortcut_models.get_mut(short_id) {
|
||||
if let Some(remove_id) = model
|
||||
.bindings
|
||||
.iter()
|
||||
.find(|(_, binding)| !binding.is_saved)
|
||||
.map(|(id, _)| id)
|
||||
{
|
||||
model.bindings.remove(remove_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.editing = None;
|
||||
}
|
||||
|
||||
pub(super) fn on_clear(&mut self) {
|
||||
|
|
@ -249,17 +291,18 @@ impl Model {
|
|||
#[allow(clippy::too_many_lines)]
|
||||
pub(super) fn update(&mut self, message: ShortcutMessage) -> Task<crate::app::Message> {
|
||||
match message {
|
||||
ShortcutMessage::AddKeybinding => {
|
||||
ShortcutMessage::AddAnotherKeybinding => {
|
||||
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 {
|
||||
for (binding_id, shortcut) in &mut model.bindings {
|
||||
if shortcut.binding.is_set()
|
||||
|| Binding::from_str(&shortcut.input).is_ok()
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
self.editing = Some(binding_id);
|
||||
shortcut.input.clear();
|
||||
|
||||
return widget::text_input::focus(shortcut.id.clone());
|
||||
|
|
@ -267,13 +310,13 @@ impl Model {
|
|||
|
||||
// Create a new input and focus it.
|
||||
let id = widget::Id::unique();
|
||||
model.bindings.insert(ShortcutBinding {
|
||||
self.editing = Some(model.bindings.insert(ShortcutBinding {
|
||||
id: id.clone(),
|
||||
binding: Binding::default(),
|
||||
input: String::new(),
|
||||
editing: true,
|
||||
is_default: false,
|
||||
});
|
||||
is_saved: false,
|
||||
}));
|
||||
|
||||
return widget::text_input::focus(id);
|
||||
}
|
||||
|
|
@ -306,7 +349,10 @@ impl Model {
|
|||
|
||||
shortcut.binding = new_binding.clone();
|
||||
shortcut.input.clear();
|
||||
shortcut.editing = false;
|
||||
|
||||
if self.editing == Some(id) {
|
||||
self.editing = None;
|
||||
}
|
||||
|
||||
let action = model.action.clone();
|
||||
self.config_remove(&prev_binding);
|
||||
|
|
@ -319,7 +365,18 @@ impl Model {
|
|||
}
|
||||
}
|
||||
|
||||
ShortcutMessage::CancelReplace => self.replace_dialog = None,
|
||||
ShortcutMessage::CancelReplace => {
|
||||
if let Some(((id, _, _, _), short_id)) =
|
||||
self.replace_dialog.take().zip(self.shortcut_context)
|
||||
{
|
||||
if let Some(model) = self.shortcut_models.get_mut(short_id) {
|
||||
if let Some(binding) = model.bindings.get_mut(id) {
|
||||
binding.reset();
|
||||
return cosmic::widget::text_input::focus(binding.id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShortcutMessage::DeleteBinding(id) => {
|
||||
if let Some(short_id) = self.shortcut_context {
|
||||
|
|
@ -328,14 +385,8 @@ impl Model {
|
|||
if shortcut.is_default {
|
||||
self.config_add(Action::Disable, shortcut.binding.clone());
|
||||
} else {
|
||||
// if last keybind deleted, clear shortcut context
|
||||
if model.bindings.is_empty() {
|
||||
self.shortcut_context = None;
|
||||
}
|
||||
self.config_remove(&shortcut.binding);
|
||||
}
|
||||
|
||||
self.on_enter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -344,7 +395,6 @@ impl Model {
|
|||
let model = self.shortcut_models.remove(id);
|
||||
for (_, shortcut) in model.bindings {
|
||||
self.config_remove(&shortcut.binding);
|
||||
self.on_enter();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -352,10 +402,12 @@ impl Model {
|
|||
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 {
|
||||
self.editing = Some(id);
|
||||
shortcut.input = shortcut.binding.to_string();
|
||||
return widget::text_input::select_all(shortcut.id.clone());
|
||||
} else if self.editing == Some(id) {
|
||||
self.editing = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -395,14 +447,16 @@ impl Model {
|
|||
|
||||
ShortcutMessage::ShowShortcut(id, description) => {
|
||||
self.shortcut_context = Some(id);
|
||||
self.shortcut_title = description;
|
||||
self.replace_dialog = None;
|
||||
|
||||
let mut tasks = vec![cosmic::task::message(
|
||||
crate::app::Message::OpenContextDrawer(self.entity, description.into()),
|
||||
crate::app::Message::OpenContextDrawer(self.entity, "".into()),
|
||||
)];
|
||||
|
||||
if let Some(model) = self.shortcut_models.get(0) {
|
||||
if let Some(shortcut) = model.bindings.get(0) {
|
||||
self.editing = Some(0);
|
||||
tasks.push(widget::text_input::focus(shortcut.id.clone()));
|
||||
tasks.push(widget::text_input::select_all(shortcut.id.clone()));
|
||||
}
|
||||
|
|
@ -411,59 +465,7 @@ impl Model {
|
|||
return Task::batch(tasks);
|
||||
}
|
||||
|
||||
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 Task::none();
|
||||
}
|
||||
if let Some(action) = self.config_contains(&new_binding) {
|
||||
let action_str = if let Action::Spawn(_) = &action {
|
||||
super::localize_custom_action(&action, &new_binding)
|
||||
} else {
|
||||
super::localize_action(&action)
|
||||
};
|
||||
self.replace_dialog =
|
||||
Some((id, new_binding, action, action_str));
|
||||
return Task::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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ShortcutMessage::SubmitBinding(id) => return self.submit_binding(id),
|
||||
}
|
||||
|
||||
Task::none()
|
||||
|
|
@ -476,13 +478,87 @@ impl Model {
|
|||
.fold(widget::list_column(), widget::ListColumn::add)
|
||||
.into()
|
||||
}
|
||||
|
||||
fn submit_binding(&mut self, id: usize) -> Task<crate::app::Message> {
|
||||
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) {
|
||||
if shortcut.input.is_empty() {
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
match Binding::from_str(&shortcut.input) {
|
||||
Ok(new_binding) => {
|
||||
if shortcut.binding == new_binding {
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
if !new_binding.is_set() {
|
||||
shortcut.input.clear();
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
if let Some(action) = self.config_contains(&new_binding) {
|
||||
let action_str = if let Action::Spawn(_) = &action {
|
||||
super::localize_custom_action(&action, &new_binding)
|
||||
} else {
|
||||
super::localize_action(&action)
|
||||
};
|
||||
self.replace_dialog = Some((id, new_binding, action, action_str));
|
||||
return Task::none();
|
||||
}
|
||||
|
||||
apply_binding = Some(new_binding);
|
||||
}
|
||||
|
||||
Err(why) => {
|
||||
tracing::error!(why, "keybinding input invalid");
|
||||
shortcut.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.is_saved = true;
|
||||
shortcut.input.clear();
|
||||
|
||||
if self.editing == Some(id) {
|
||||
self.editing = None;
|
||||
}
|
||||
|
||||
let action = model.action.clone();
|
||||
self.config_remove(&prev_binding);
|
||||
self.config_add(action, new_binding);
|
||||
return cosmic::widget::text_input::focus(
|
||||
self.add_keybindings_button_id.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Task::none()
|
||||
}
|
||||
}
|
||||
|
||||
fn context_drawer(
|
||||
shortcuts: &Slab<ShortcutModel>,
|
||||
fn context_drawer<'a>(
|
||||
title: &'a str,
|
||||
shortcuts: &'a Slab<ShortcutModel>,
|
||||
editing: Option<usize>,
|
||||
add_keybindings_id: widget::Id,
|
||||
id: usize,
|
||||
show_action: bool,
|
||||
) -> Element<ShortcutMessage> {
|
||||
) -> Element<'a, ShortcutMessage> {
|
||||
let cosmic::cosmic_theme::Spacing {
|
||||
space_xxs,
|
||||
space_xs,
|
||||
|
|
@ -505,35 +581,39 @@ fn context_drawer(
|
|||
let bindings = model.bindings.iter().enumerate().fold(
|
||||
widget::list_column().spacing(space_xxs),
|
||||
|section, (_, (bind_id, shortcut))| {
|
||||
let text: Cow<'_, str> = if !shortcut.editing && shortcut.binding.is_set() {
|
||||
let editing = editing == Some(bind_id);
|
||||
let text: Cow<'_, str> = if !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| {
|
||||
let input = widget::editable_input("", text, editing, move |enable| {
|
||||
ShortcutMessage::EditBinding(bind_id, enable)
|
||||
})
|
||||
.select_on_focus(true)
|
||||
.on_input(move |text| ShortcutMessage::InputBinding(bind_id, text))
|
||||
.on_unfocus(ShortcutMessage::SubmitBinding(bind_id))
|
||||
.on_submit(move |_| ShortcutMessage::SubmitBinding(bind_id))
|
||||
.padding([0, space_xs])
|
||||
.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 mut children = Vec::with_capacity(2);
|
||||
children.push(input);
|
||||
|
||||
let flex_control =
|
||||
settings::item_row(vec![input, delete_button]).align_y(Alignment::Center);
|
||||
if shortcut.is_saved {
|
||||
let delete_button = widget::button::icon(icon::from_name("edit-delete-symbolic"))
|
||||
.on_press(ShortcutMessage::DeleteBinding(bind_id))
|
||||
.into();
|
||||
children.push(delete_button);
|
||||
}
|
||||
|
||||
section.add(flex_control)
|
||||
section.add(settings::item_row(children).align_y(Alignment::Center))
|
||||
},
|
||||
);
|
||||
|
||||
// TODO: Detect when it is necessary
|
||||
let reset_keybinding_button = if show_action {
|
||||
let reset_keybinding_button = if model.modified == 0 || show_action {
|
||||
None
|
||||
} else {
|
||||
let button = widget::button::standard(fl!("reset-to-default"))
|
||||
|
|
@ -541,8 +621,13 @@ fn context_drawer(
|
|||
Some(button)
|
||||
};
|
||||
|
||||
let add_keybinding_button =
|
||||
widget::button::standard(fl!("add-keybinding")).on_press(ShortcutMessage::AddKeybinding);
|
||||
let add_keybinding_button = widget::button::standard(fl!("add-another-keybinding"))
|
||||
.id(add_keybindings_id)
|
||||
.on_press_maybe(if model.bindings.iter().any(|(_, b)| !b.is_saved) {
|
||||
None
|
||||
} else {
|
||||
Some(ShortcutMessage::AddAnotherKeybinding)
|
||||
});
|
||||
|
||||
let button_container = widget::row::with_capacity(2)
|
||||
.push_maybe(reset_keybinding_button)
|
||||
|
|
@ -552,7 +637,8 @@ fn context_drawer(
|
|||
.width(Length::Fill)
|
||||
.align_x(Alignment::End);
|
||||
|
||||
widget::column::with_capacity(if show_action { 3 } else { 2 })
|
||||
widget::column::with_capacity(if show_action { 4 } else { 3 })
|
||||
.push(widget::text::heading(title))
|
||||
.spacing(space_l)
|
||||
.push_maybe(action)
|
||||
.push(bindings)
|
||||
|
|
|
|||
|
|
@ -67,9 +67,10 @@ pub enum Message {
|
|||
#[derive(Default)]
|
||||
struct AddShortcut {
|
||||
pub active: bool,
|
||||
pub editing: Option<usize>,
|
||||
pub name: String,
|
||||
pub task: String,
|
||||
pub keys: Slab<(String, widget::Id, bool)>,
|
||||
pub keys: Slab<(String, widget::Id)>,
|
||||
}
|
||||
|
||||
impl AddShortcut {
|
||||
|
|
@ -79,8 +80,7 @@ impl AddShortcut {
|
|||
self.task.clear();
|
||||
|
||||
if self.keys.is_empty() {
|
||||
self.keys
|
||||
.insert((String::new(), widget::Id::unique(), false));
|
||||
self.keys.insert((String::new(), widget::Id::unique()));
|
||||
} else {
|
||||
while self.keys.len() > 1 {
|
||||
self.keys.remove(self.keys.len() - 1);
|
||||
|
|
@ -103,34 +103,20 @@ impl Page {
|
|||
}
|
||||
|
||||
Message::KeyEditing(id, enable) => {
|
||||
self.add_shortcut.keys[id].2 = enable;
|
||||
if enable {
|
||||
self.add_shortcut.editing = Some(id)
|
||||
} else if self.add_shortcut.editing == Some(id) {
|
||||
let task = self.add_keybinding();
|
||||
self.add_shortcut.editing = None;
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
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 Task::batch(vec![
|
||||
widget::text_input::focus(new_id.clone()),
|
||||
widget::text_input::select_all(new_id),
|
||||
]);
|
||||
}
|
||||
Message::AddKeybinding => return self.add_keybinding(),
|
||||
|
||||
Message::AddShortcut => {
|
||||
let name = self.add_shortcut.name.trim();
|
||||
|
|
@ -172,12 +158,13 @@ impl Page {
|
|||
}
|
||||
|
||||
Message::EditCombination => {
|
||||
let (_, id, editing) = &mut self.add_shortcut.keys[0];
|
||||
*editing = true;
|
||||
return Task::batch(vec![
|
||||
widget::text_input::focus(id.clone()),
|
||||
widget::text_input::select_all(id.clone()),
|
||||
]);
|
||||
if let Some((slab_index, (_, id))) = self.add_shortcut.keys.iter().next() {
|
||||
self.add_shortcut.editing = Some(slab_index);
|
||||
return Task::batch(vec![
|
||||
widget::text_input::focus(id.clone()),
|
||||
widget::text_input::select_all(id.clone()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Message::NameSubmit => {
|
||||
|
|
@ -227,6 +214,31 @@ impl Page {
|
|||
Task::none()
|
||||
}
|
||||
|
||||
fn add_keybinding(&mut self) -> Task<crate::app::Message> {
|
||||
// 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.editing = Some(
|
||||
self.add_shortcut
|
||||
.keys
|
||||
.insert((String::new(), new_id.clone())),
|
||||
);
|
||||
|
||||
Task::batch(vec![
|
||||
widget::text_input::focus(new_id.clone()),
|
||||
widget::text_input::select_all(new_id),
|
||||
])
|
||||
}
|
||||
|
||||
fn add_keybinding_context(&self) -> Element<'_, Message> {
|
||||
let name_input = widget::text_input("", &self.add_shortcut.name)
|
||||
.padding([6, 12])
|
||||
|
|
@ -258,15 +270,17 @@ impl Page {
|
|||
|
||||
let keys = self.add_shortcut.keys.iter().fold(
|
||||
widget::list_column().spacing(0),
|
||||
|column, (id, (text, widget_id, editing))| {
|
||||
|column, (id, (text, widget_id))| {
|
||||
let key_combination = widget::editable_input(
|
||||
fl!("type-key-combination"),
|
||||
text,
|
||||
*editing,
|
||||
self.add_shortcut.editing == Some(id),
|
||||
move |enable| Message::KeyEditing(id, enable),
|
||||
)
|
||||
.select_on_focus(true)
|
||||
.padding([0, 12])
|
||||
.on_input(move |input| Message::KeyInput(id, input))
|
||||
.on_unfocus(Message::AddKeybinding)
|
||||
.on_submit(|_| Message::AddKeybinding)
|
||||
.id(widget_id.clone())
|
||||
.apply(widget::container)
|
||||
|
|
@ -278,7 +292,7 @@ impl Page {
|
|||
|
||||
let controls = widget::list_column().add(input_fields).add(keys).spacing(0);
|
||||
|
||||
let add_keybinding_button = widget::button::standard(fl!("add-keybinding"))
|
||||
let add_keybinding_button = widget::button::standard(fl!("add-another-keybinding"))
|
||||
.on_press(Message::AddShortcut)
|
||||
.apply(widget::container)
|
||||
.width(Length::Fill)
|
||||
|
|
@ -358,6 +372,11 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
.map(|el| el.map(crate::pages::Message::CustomShortcuts))
|
||||
}
|
||||
|
||||
fn on_context_drawer_close(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_context_drawer_close();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
Task::none()
|
||||
|
|
@ -385,8 +404,8 @@ fn bindings(_defaults: &Shortcuts, keybindings: &Shortcuts) -> Slab<ShortcutMode
|
|||
id: widget::Id::unique(),
|
||||
binding: binding.clone(),
|
||||
input: String::new(),
|
||||
editing: false,
|
||||
is_default: false,
|
||||
is_saved: true,
|
||||
};
|
||||
|
||||
if let Some((_, existing_model)) =
|
||||
|
|
|
|||
|
|
@ -59,9 +59,13 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
.map(|el| el.map(crate::pages::Message::ManageWindowShortcuts))
|
||||
}
|
||||
|
||||
fn on_context_drawer_close(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_context_drawer_close();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,13 +14,13 @@ pub mod tiling;
|
|||
|
||||
use cosmic::iced::Length;
|
||||
use cosmic::widget::{self, icon, settings, text};
|
||||
use cosmic::{theme, Apply, Element, Task};
|
||||
use cosmic::{Apply, Element, Task, theme};
|
||||
use cosmic_config::ConfigGet;
|
||||
use cosmic_settings_config::Binding;
|
||||
use cosmic_settings_config::shortcuts::action::{
|
||||
Direction, FocusDirection, Orientation, ResizeDirection,
|
||||
};
|
||||
use cosmic_settings_config::shortcuts::{self, Action, Shortcuts};
|
||||
use cosmic_settings_config::Binding;
|
||||
use cosmic_settings_page::Section;
|
||||
use cosmic_settings_page::{self as page, section};
|
||||
use itertools::Itertools;
|
||||
|
|
@ -476,25 +476,21 @@ fn all_system_actions() -> &'static [Action] {
|
|||
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),
|
||||
|
|
@ -505,11 +501,9 @@ fn all_system_actions() -> &'static [Action] {
|
|||
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),
|
||||
|
|
|
|||
|
|
@ -59,9 +59,13 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
.map(|el| el.map(crate::pages::Message::MoveWindowShortcuts))
|
||||
}
|
||||
|
||||
fn on_context_drawer_close(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_context_drawer_close();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,9 +59,13 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
.map(|el| el.map(crate::pages::Message::NavShortcuts))
|
||||
}
|
||||
|
||||
fn on_context_drawer_close(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_context_drawer_close();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,9 +59,13 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
.map(|el| el.map(crate::pages::Message::SystemShortcuts))
|
||||
}
|
||||
|
||||
fn on_context_drawer_close(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_context_drawer_close();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,9 +59,13 @@ impl page::Page<crate::pages::Message> for Page {
|
|||
.map(|el| el.map(crate::pages::Message::TilingShortcuts))
|
||||
}
|
||||
|
||||
fn on_context_drawer_close(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_context_drawer_close();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> Task<crate::pages::Message> {
|
||||
self.model.on_enter();
|
||||
|
||||
Task::none()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,10 +88,6 @@ impl Page {
|
|||
match message {
|
||||
Message::HostnameEdit(editing) => {
|
||||
self.editing_device_name = editing;
|
||||
|
||||
if !editing {
|
||||
return self.hostname_submit();
|
||||
}
|
||||
}
|
||||
|
||||
Message::HostnameInput(hostname) => {
|
||||
|
|
@ -119,6 +115,7 @@ impl Page {
|
|||
}
|
||||
|
||||
fn hostname_submit(&mut self) -> cosmic::app::Task<crate::app::Message> {
|
||||
eprintln!("hostname submit");
|
||||
if self.hostname_input == self.info.device_name {
|
||||
return Task::none();
|
||||
}
|
||||
|
|
@ -182,6 +179,7 @@ fn device() -> Section<crate::pages::Message> {
|
|||
)
|
||||
.width(250)
|
||||
.on_input(Message::HostnameInput)
|
||||
.on_unfocus(Message::HostnameSubmit)
|
||||
.on_submit(|_| Message::HostnameSubmit);
|
||||
|
||||
let device_name = settings::item::builder(&*desc[device])
|
||||
|
|
|
|||
|
|
@ -577,7 +577,7 @@ show-extended-input-sources = Show extended input sources
|
|||
keyboard-shortcuts = Keyboard Shortcuts
|
||||
.desc = View and customize shortcuts
|
||||
|
||||
add-keybinding = Add keybinding
|
||||
add-another-keybinding = Add another keybinding
|
||||
cancel = Cancel
|
||||
command = Command
|
||||
custom = Custom
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ regex = "1.11.1"
|
|||
slotmap = "1.0.7"
|
||||
libcosmic = { workspace = true }
|
||||
downcast-rs = "1.2.1"
|
||||
once_cell = "1.20.3"
|
||||
tokio.workspace = true
|
||||
url = "2.5.4"
|
||||
slab = "0.4.9"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ pub struct Binder<Message> {
|
|||
}
|
||||
|
||||
impl<Message> Default for Binder<Message> {
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
content: SparseSecondaryMap::new(),
|
||||
|
|
@ -42,12 +43,14 @@ impl<Message> Default for Binder<Message> {
|
|||
impl<Message: 'static> Binder<Message> {
|
||||
/// Check if a page exists in the model.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn contains_item(&self, id: crate::Entity) -> bool {
|
||||
self.info.contains_key(id)
|
||||
}
|
||||
|
||||
/// Returns the content of a page, if it has any.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn content(&self, page: crate::Entity) -> Option<&[section::Entity]> {
|
||||
self.content.get(page).map(Vec::as_slice)
|
||||
}
|
||||
|
|
@ -87,6 +90,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn find_page_by_id(&self, id: &str) -> Option<(crate::Entity, &Info)> {
|
||||
self.info.iter().find(|(_id, info)| info.id == id)
|
||||
}
|
||||
|
|
@ -117,22 +121,26 @@ impl<Message: 'static> Binder<Message> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn model(&self, id: crate::Entity) -> Option<&dyn Page<Message>> {
|
||||
self.page.get(id).map(AsRef::as_ref)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn model_mut(&mut self, id: crate::Entity) -> Option<&mut dyn Page<Message>> {
|
||||
self.page.get_mut(id).map(AsMut::as_mut)
|
||||
}
|
||||
|
||||
/// Get entity ID of page by its type ID.
|
||||
#[inline]
|
||||
pub fn page_id<P: Page<Message>>(&self) -> Option<crate::Entity> {
|
||||
self.typed_page_ids.get(&TypeId::of::<P>()).copied()
|
||||
}
|
||||
|
||||
/// Obtain a reference to a page by its type ID.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn page<P: Page<Message>>(&self) -> Option<&P> {
|
||||
let page = self.page.get(self.page_id::<P>()?)?;
|
||||
page.downcast_ref::<P>()
|
||||
|
|
@ -140,6 +148,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
|
||||
/// Create a context drawer for the given page.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn context_drawer(&self, id: crate::Entity) -> Option<Element<'_, Message>> {
|
||||
let page = self.page.get(id)?;
|
||||
page.context_drawer()
|
||||
|
|
@ -147,6 +156,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
|
||||
/// Create a dialog for the given page.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn dialog(&self, id: crate::Entity) -> Option<Element<'_, Message>> {
|
||||
let page = self.page.get(id)?;
|
||||
page.dialog()
|
||||
|
|
@ -154,12 +164,23 @@ impl<Message: 'static> Binder<Message> {
|
|||
|
||||
/// Obtain a reference to a page by its type ID.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn page_mut<P: Page<Message>>(&mut self) -> Option<&mut P> {
|
||||
let page = self.page.get_mut(self.page_id::<P>()?)?;
|
||||
page.downcast_mut::<P>()
|
||||
}
|
||||
|
||||
/// Returns a Task when a context drawer is closed.
|
||||
#[inline]
|
||||
pub fn on_context_drawer_close(&mut self, id: crate::Entity) -> Option<Task<Message>> {
|
||||
if let Some(page) = self.page.get_mut(id) {
|
||||
return Some(page.on_context_drawer_close());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns a Task when a page is left
|
||||
#[inline]
|
||||
pub fn on_leave(&mut self, id: crate::Entity) -> Option<Task<Message>> {
|
||||
if let Some(page) = self.page.get_mut(id) {
|
||||
return Some(page.on_leave());
|
||||
|
|
@ -168,6 +189,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
}
|
||||
|
||||
/// Calls a page's load function to refresh its data.
|
||||
#[inline]
|
||||
pub fn on_enter(&mut self, id: crate::Entity) -> Task<Message> {
|
||||
if let Some(page) = self.page.get_mut(id) {
|
||||
return page.on_enter();
|
||||
|
|
@ -204,13 +226,14 @@ impl<Message: 'static> Binder<Message> {
|
|||
) -> impl Iterator<Item = (crate::Entity, section::Entity)> + 'a {
|
||||
self.content.iter().flat_map(move |(page, sections)| {
|
||||
sections
|
||||
.into_iter()
|
||||
.iter()
|
||||
.filter(|&id| self.sections[*id].search_matches(rule))
|
||||
.map(move |&id| (page, id))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the sub-pages of a page, if it has any.
|
||||
#[inline]
|
||||
pub fn sub_pages(&self, page: crate::Entity) -> Option<&[crate::Entity]> {
|
||||
self.sub_pages.get(page).map(AsRef::as_ref)
|
||||
}
|
||||
|
|
@ -219,6 +242,7 @@ impl<Message: 'static> Binder<Message> {
|
|||
pub trait AutoBind<Message: 'static>: Page<Message> + Default + 'static {
|
||||
/// Attaches sub-pages to the page.
|
||||
#[allow(clippy::must_use_candidate)]
|
||||
#[inline]
|
||||
fn sub_pages(page: crate::Insert<Message>) -> crate::Insert<Message> {
|
||||
page
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,13 +9,15 @@ pub struct Insert<'a, Message> {
|
|||
pub id: Entity,
|
||||
}
|
||||
|
||||
impl<'a, Message: 'static> Insert<'a, Message> {
|
||||
impl<Message: 'static> Insert<'_, Message> {
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn id(self) -> Entity {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn content(self, content: Content) -> Self {
|
||||
self.model.content.insert(self.id, content);
|
||||
self
|
||||
|
|
@ -26,7 +28,11 @@ impl<'a, Message: 'static> Insert<'a, Message> {
|
|||
#[allow(clippy::must_use_candidate)]
|
||||
pub fn sub_page<P: AutoBind<Message>>(self) -> Self {
|
||||
let sub_page = self.model.register::<P>().id();
|
||||
self.sub_page_inner(sub_page)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn sub_page_inner(self, sub_page: Entity) -> Self {
|
||||
self.model.info[sub_page].parent = Some(self.id);
|
||||
|
||||
self.model
|
||||
|
|
@ -43,7 +49,11 @@ impl<'a, Message: 'static> Insert<'a, Message> {
|
|||
#[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.sub_page_with_id_inner(sub_page)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn sub_page_with_id_inner(&mut self, sub_page: Entity) -> Entity {
|
||||
self.model.info[sub_page].parent = Some(self.id);
|
||||
|
||||
self.model
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ pub use binder::{AutoBind, Binder};
|
|||
|
||||
mod insert;
|
||||
use cosmic::{Element, Task};
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use downcast_rs::{Downcast, impl_downcast};
|
||||
pub use insert::Insert;
|
||||
|
||||
pub mod section;
|
||||
|
|
@ -30,6 +30,7 @@ pub trait Page<Message: 'static>: Downcast {
|
|||
|
||||
/// Initialize the sections used by this page.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
fn content(
|
||||
&self,
|
||||
_sections: &mut SlotMap<section::Entity, Section<Message>>,
|
||||
|
|
@ -39,46 +40,62 @@ pub trait Page<Message: 'static>: Downcast {
|
|||
|
||||
/// Display a context drawer for the page.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
fn context_drawer(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Set a custom page header
|
||||
#[inline]
|
||||
fn header(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Display an inner app dialog for the page.
|
||||
#[inline]
|
||||
fn dialog(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Response from a file chooser dialog request.
|
||||
#[inline]
|
||||
fn file_chooser(&mut self, _selected: Vec<url::Url>) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Alter the contents of the page's header view.
|
||||
#[inline]
|
||||
fn header_view(&self) -> Option<Element<'_, Message>> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Emit on the context drawer being closed
|
||||
#[allow(unused)]
|
||||
#[inline]
|
||||
fn on_context_drawer_close(&mut self) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Reload page metadata via a Task.
|
||||
#[allow(unused)]
|
||||
#[inline]
|
||||
fn on_enter(&mut self) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Emit a command when the page is left
|
||||
#[inline]
|
||||
fn on_leave(&mut self) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Assigns the entity ID of the page to the page.
|
||||
#[allow(unused)]
|
||||
#[inline]
|
||||
fn set_id(&mut self, entity: Entity) {}
|
||||
|
||||
/// The title to display in the page header.
|
||||
#[inline]
|
||||
fn title(&self) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
|
|
@ -112,6 +129,7 @@ pub struct Info {
|
|||
}
|
||||
|
||||
impl Info {
|
||||
#[inline]
|
||||
pub fn new(id: impl Into<Cow<'static, str>>, icon_name: impl Into<Cow<'static, str>>) -> Self {
|
||||
Self {
|
||||
title: String::new(),
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ impl<Message: 'static> Default for Section<Message> {
|
|||
|
||||
impl<Message: 'static> Section<Message> {
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn search_matches(&self, rule: &Regex) -> bool {
|
||||
if self.search_ignore {
|
||||
return false;
|
||||
|
|
@ -72,6 +73,7 @@ impl<Message: 'static> Section<Message> {
|
|||
false
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn show_while<Model: Page<Message>>(
|
||||
mut self,
|
||||
func: impl for<'a> Fn(&'a Model) -> bool + 'static,
|
||||
|
|
@ -92,6 +94,7 @@ impl<Message: 'static> Section<Message> {
|
|||
/// # Panics
|
||||
///
|
||||
/// Will panic if the `Model` type does not match the page type.
|
||||
#[inline]
|
||||
pub fn view<Model: Page<Message>>(
|
||||
mut self,
|
||||
func: impl for<'a> Fn(
|
||||
|
|
@ -116,6 +119,7 @@ impl<Message: 'static> Section<Message> {
|
|||
}
|
||||
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub fn unimplemented<'a, Message: 'static>(
|
||||
_binder: &'a Binder<Message>,
|
||||
_page: &'a dyn Page<Message>,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@ futures-lite = "2.6.0"
|
|||
futures-util = "0.3.31"
|
||||
image = "0.25.5"
|
||||
infer = "0.16.0"
|
||||
jxl-oxide = "0.11.1"
|
||||
tokio = { version = "1.43.0", features = ["sync"] }
|
||||
jxl-oxide = "0.11.3"
|
||||
tokio = { version = "1.44.1", features = ["sync"] }
|
||||
tracing = "0.1.41"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue