Add support for accelerator keys in dialog, fixes #390

This commit is contained in:
Jeremy Soller 2025-03-19 14:44:13 -06:00
parent 637d610342
commit 28f324fc11
No known key found for this signature in database
GPG key ID: 670FDFB5428E05CA
2 changed files with 130 additions and 27 deletions

34
Cargo.lock generated
View file

@ -1210,7 +1210,7 @@ dependencies = [
[[package]]
name = "cosmic-config"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"atomicwrites",
"cosmic-config-derive",
@ -1232,7 +1232,7 @@ dependencies = [
[[package]]
name = "cosmic-config-derive"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"quote",
"syn 1.0.109",
@ -1373,7 +1373,7 @@ dependencies = [
[[package]]
name = "cosmic-theme"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"almost",
"cosmic-config",
@ -2164,9 +2164,9 @@ dependencies = [
[[package]]
name = "freedesktop-desktop-entry"
version = "0.7.8"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6a4cdc5571033a6a329b9e8a8430594d1e3b60f42bb79e79686cadef34740ea"
checksum = "7e5e2bd2e383df08a8439c2e096be16d5355aa00f976b295cf8e077ea5953d5d"
dependencies = [
"cached",
"gettext-rs",
@ -2786,7 +2786,7 @@ dependencies = [
[[package]]
name = "iced"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"dnd",
"iced_accessibility",
@ -2804,7 +2804,7 @@ dependencies = [
[[package]]
name = "iced_accessibility"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"accesskit",
"accesskit_winit",
@ -2813,7 +2813,7 @@ dependencies = [
[[package]]
name = "iced_core"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"bitflags 2.9.0",
"bytes",
@ -2837,7 +2837,7 @@ dependencies = [
[[package]]
name = "iced_futures"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"futures",
"iced_core",
@ -2863,7 +2863,7 @@ dependencies = [
[[package]]
name = "iced_graphics"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"bitflags 2.9.0",
"bytemuck",
@ -2885,7 +2885,7 @@ dependencies = [
[[package]]
name = "iced_renderer"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"iced_graphics",
"iced_tiny_skia",
@ -2897,7 +2897,7 @@ dependencies = [
[[package]]
name = "iced_runtime"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"bytes",
"cosmic-client-toolkit",
@ -2912,7 +2912,7 @@ dependencies = [
[[package]]
name = "iced_tiny_skia"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"bytemuck",
"cosmic-text",
@ -2928,7 +2928,7 @@ dependencies = [
[[package]]
name = "iced_wgpu"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"as-raw-xcb-connection",
"bitflags 2.9.0",
@ -2959,7 +2959,7 @@ dependencies = [
[[package]]
name = "iced_widget"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"cosmic-client-toolkit",
"dnd",
@ -2977,7 +2977,7 @@ dependencies = [
[[package]]
name = "iced_winit"
version = "0.14.0-dev"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"cosmic-client-toolkit",
"dnd",
@ -3557,7 +3557,7 @@ checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libcosmic"
version = "0.1.0"
source = "git+https://github.com/pop-os/libcosmic.git#e29ce0d4c1a3ff09913e4f0d3bfd5553fe5a770b"
source = "git+https://github.com/pop-os/libcosmic.git#91eae67dd59c70283590253d5a98688093017ebe"
dependencies = [
"apply",
"ashpd 0.9.2",

View file

@ -13,7 +13,7 @@ use cosmic::{
theme,
widget::{
self,
menu::{Action as MenuAction, KeyBind},
menu::{key_bind::Modifier, Action as MenuAction, KeyBind},
segmented_button,
},
Application, ApplicationExt, Element,
@ -139,6 +139,71 @@ impl AsRef<str> for DialogFilter {
}
}
#[derive(Clone, Debug)]
pub struct DialogLabelSpan {
pub text: String,
pub underline: bool,
}
#[derive(Clone, Debug)]
pub struct DialogLabel {
pub spans: Vec<DialogLabelSpan>,
pub key_bind_opt: Option<KeyBind>,
}
impl<T: AsRef<str>> From<T> for DialogLabel {
fn from(text: T) -> Self {
let mut spans = Vec::<DialogLabelSpan>::new();
let mut key_bind_opt = None;
let mut next_underline = false;
for c in text.as_ref().chars() {
let underline = next_underline;
next_underline = false;
if c == '_' {
if !underline {
next_underline = true;
continue;
}
}
if underline && key_bind_opt.is_none() {
key_bind_opt = Some(KeyBind {
modifiers: vec![Modifier::Alt],
key: Key::Character(c.to_lowercase().to_string().into()),
});
}
if let Some(span) = spans.last_mut() {
if underline == span.underline {
span.text.push(c);
continue;
}
}
spans.push(DialogLabelSpan {
text: String::from(c),
underline,
});
}
dbg!(Self {
spans,
key_bind_opt
})
}
}
impl<'a, M: Clone + 'static> From<&'a DialogLabel> for Element<'a, M> {
fn from(label: &'a DialogLabel) -> Self {
let mut iced_spans = Vec::with_capacity(label.spans.len());
for span in label.spans.iter() {
iced_spans.push(cosmic::iced::widget::span(&span.text).underline(span.underline));
}
cosmic::iced::widget::rich_text(iced_spans).into()
}
}
pub struct Dialog<M> {
cosmic: Cosmic<App>,
mapper: fn(DialogMessage) -> M,
@ -218,8 +283,8 @@ impl<M: Send + 'static> Dialog<M> {
.map(move |message| cosmic::action::app(mapper(message)))
}
pub fn set_accept_label(&mut self, accept_label: impl Into<String>) {
self.cosmic.app.accept_label = accept_label.into();
pub fn set_accept_label(&mut self, accept_label: impl AsRef<str>) {
self.cosmic.app.accept_label = DialogLabel::from(accept_label);
}
pub fn choices(&self) -> &[DialogChoice] {
@ -388,7 +453,7 @@ struct App {
core: Core,
flags: Flags,
title: String,
accept_label: String,
accept_label: DialogLabel,
choices: Vec<DialogChoice>,
context_page: ContextPage,
dialog_pages: VecDeque<DialogPage>,
@ -409,8 +474,11 @@ struct App {
impl App {
fn button_view(&self) -> Element<Message> {
let cosmic_theme::Spacing {
space_xxxs,
space_xxs,
space_xs,
space_s,
space_l,
..
} = theme::active().cosmic().spacing;
@ -458,11 +526,37 @@ impl App {
}
row = row.push(widget::horizontal_space());
row = row.push(widget::button::standard(fl!("cancel")).on_press(Message::Cancel));
row = row.push(if self.flags.kind.save() {
widget::button::suggested(&self.accept_label).on_press(Message::Save(false))
} else {
widget::button::suggested(&self.accept_label).on_press(Message::Open)
});
let mut has_selected = false;
if let Some(items) = self.tab.items_opt() {
for item in items.iter() {
if item.selected {
has_selected = true;
break;
}
}
}
row = row.push(
//TODO: easier way to create buttons with rich text
widget::button::custom(
widget::row::with_children(vec![Element::from(&self.accept_label)])
.padding([0, space_s])
.width(Length::Shrink)
.height(space_l)
.spacing(space_xxxs)
.align_y(Alignment::Center)
)
.padding(0)
.on_press_maybe(if self.flags.kind.save() {
Some(Message::Save(false))
} else if has_selected {
Some(Message::Open)
} else {
None
})
.class(widget::button::ButtonClass::Suggested)
/*TODO: a11y feature: .label(&self.accept_label.text)*/
);
col = col.push(row);
@ -786,7 +880,7 @@ impl Application for App {
core,
flags,
title,
accept_label,
accept_label: DialogLabel::from(accept_label),
choices: Vec::new(),
context_page: ContextPage::Preview(None, PreviewKind::Selected),
dialog_pages: VecDeque::new(),
@ -1174,6 +1268,15 @@ impl Application for App {
return self.update(Message::from(action.message()));
}
}
if let Some(key_bind) = &self.accept_label.key_bind_opt {
if key_bind.matches(modifiers, &key) {
return self.update(if self.flags.kind.save() {
Message::Save(false)
} else {
Message::Open
});
}
}
}
Message::Modifiers(modifiers) => {
self.modifiers = modifiers;