make button focus ring conditional on tab press
This commit is contained in:
1 parent
64a80dc069
commit
1d7a472847
5 files changed
+121
-14
No files matched your search
@@ -9,11 +9,24 @@ pub fn surface(background: Color, radius: f32) -> theme::Container<'static> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn button_style(p: Palette, selected: bool) -> theme::Button {
|
pub fn button_style(p: Palette, selected: bool) -> theme::Button {
|
||||||
styled_button(p, selected, false, 1.0)
|
button_style_with_focus(p, selected, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn button_style_with_focus(p: Palette, selected: bool, focus_visible: bool) -> theme::Button {
|
||||||
|
styled_button(p, selected, false, 1.0, focus_visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selection cards share native button states while revealing the modal material.
|
/// Selection cards share native button states while revealing the modal material.
|
||||||
pub fn material_button_style(p: Palette, selected: bool, material_opacity: f32) -> theme::Button {
|
pub fn material_button_style(p: Palette, selected: bool, material_opacity: f32) -> theme::Button {
|
||||||
|
material_button_style_with_focus(p, selected, material_opacity, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn material_button_style_with_focus(
|
||||||
|
p: Palette,
|
||||||
|
selected: bool,
|
||||||
|
material_opacity: f32,
|
||||||
|
focus_visible: bool,
|
||||||
|
) -> theme::Button {
|
||||||
let opacity = if material_opacity.is_finite() {
|
let opacity = if material_opacity.is_finite() {
|
||||||
material_opacity.clamp(0.0, 1.0)
|
material_opacity.clamp(0.0, 1.0)
|
||||||
} else {
|
} else {
|
||||||
@@ -26,7 +39,7 @@ pub fn material_button_style(p: Palette, selected: bool, material_opacity: f32)
|
|||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
|
|
||||||
styled_button(p, selected, false, fill)
|
styled_button(p, selected, false, fill, focus_visible)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_button<'a, M: Clone + 'a>(
|
pub fn text_button<'a, M: Clone + 'a>(
|
||||||
@@ -82,7 +95,7 @@ pub(crate) fn switch_colors(palette: Palette, enabled: bool, hovered: bool) -> (
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn navigation_style(p: Palette, selected: bool) -> theme::Button {
|
pub fn navigation_style(p: Palette, selected: bool) -> theme::Button {
|
||||||
styled_button(p, selected, true, 1.0)
|
styled_button(p, selected, true, 1.0, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn settings_input(p: Palette) -> theme::TextInput {
|
pub fn settings_input(p: Palette) -> theme::TextInput {
|
||||||
@@ -128,7 +141,7 @@ pub fn select<'a, M: 'a>(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn styled_button(p: Palette, selected: bool, navigation: bool, opacity: f32) -> theme::Button {
|
fn styled_button(p: Palette, selected: bool, navigation: bool, opacity: f32, focus_visible: bool) -> theme::Button {
|
||||||
let style = move |hover: bool, focused: bool| {
|
let style = move |hover: bool, focused: bool| {
|
||||||
let background = if selected {
|
let background = if selected {
|
||||||
mix(p.sidebar, p.accent, if hover { 0.24 } else { 0.17 })
|
mix(p.sidebar, p.accent, if hover { 0.24 } else { 0.17 })
|
||||||
@@ -169,7 +182,7 @@ fn styled_button(p: Palette, selected: bool, navigation: bool, opacity: f32) ->
|
|||||||
} else {
|
} else {
|
||||||
Color::TRANSPARENT
|
Color::TRANSPARENT
|
||||||
},
|
},
|
||||||
outline_width: if focused { 2. } else { 0. },
|
outline_width: if focused && focus_visible { 2. } else { 0. },
|
||||||
outline_color: p.accent,
|
outline_color: p.accent,
|
||||||
overlay: None,
|
overlay: None,
|
||||||
shadow_offset: Vector::ZERO,
|
shadow_offset: Vector::ZERO,
|
||||||
@@ -350,6 +363,55 @@ pub fn filled_button(fill: Color, foreground: Color, radius: f32, opacity: f32)
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hiding_modal_focus_preserves_button_borders_and_materials() {
|
||||||
|
use cosmic::widget::button::Catalog;
|
||||||
|
|
||||||
|
let palette = Palette::from_resolved(&ferese_config::theme::default_theme());
|
||||||
|
let theme = palette.native_theme();
|
||||||
|
|
||||||
|
for selected in [false, true] {
|
||||||
|
for opacity in [0.65, 1.0] {
|
||||||
|
let hidden = material_button_style_with_focus(palette, selected, opacity, false);
|
||||||
|
let visible = material_button_style_with_focus(palette, selected, opacity, true);
|
||||||
|
|
||||||
|
for (hidden, visible, resting) in [
|
||||||
|
(
|
||||||
|
theme.active(true, selected, &hidden),
|
||||||
|
theme.active(true, selected, &visible),
|
||||||
|
theme.active(false, selected, &visible),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
theme.hovered(true, selected, &hidden),
|
||||||
|
theme.hovered(true, selected, &visible),
|
||||||
|
theme.hovered(false, selected, &visible),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
theme.pressed(true, selected, &hidden),
|
||||||
|
theme.pressed(true, selected, &visible),
|
||||||
|
theme.pressed(false, selected, &visible),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
assert_eq!(hidden.outline_width, 0.0);
|
||||||
|
assert_eq!(visible.outline_width, 2.0);
|
||||||
|
assert_eq!(resting.outline_width, 0.0);
|
||||||
|
assert_eq!(hidden.border_width, 1.0);
|
||||||
|
assert_eq!(hidden.border_width, visible.border_width);
|
||||||
|
assert_eq!(hidden.border_color, visible.border_color);
|
||||||
|
assert_eq!(hidden.border_radius, visible.border_radius);
|
||||||
|
assert_eq!(hidden.background, visible.background);
|
||||||
|
assert_eq!(hidden.text_color, visible.text_color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let hidden = button_style_with_focus(palette, selected, false);
|
||||||
|
let visible = button_style_with_focus(palette, selected, true);
|
||||||
|
assert_eq!(theme.active(true, selected, &hidden).outline_width, 0.0);
|
||||||
|
assert_eq!(theme.active(true, selected, &visible).outline_width, 2.0);
|
||||||
|
assert_eq!(theme.active(true, selected, &hidden).border_width, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn authentication_button_does_not_reverse_enabled_and_disabled_text() {
|
fn authentication_button_does_not_reverse_enabled_and_disabled_text() {
|
||||||
use cosmic::widget::button::Catalog;
|
use cosmic::widget::button::Catalog;
|
||||||
|
|||||||
@@ -391,6 +391,7 @@ impl FereseShell {
|
|||||||
f32::from(self.config.theme.surface_popover[3]) / 255.0
|
f32::from(self.config.theme.surface_popover[3]) / 255.0
|
||||||
};
|
};
|
||||||
let model = &self.display_mode;
|
let model = &self.display_mode;
|
||||||
|
let focus_visible = self.system_modal.as_ref().is_some_and(|modal| modal.focus_visible);
|
||||||
let pending = model.pending();
|
let pending = model.pending();
|
||||||
let mut header = row![
|
let mut header = row![
|
||||||
text("Display mode").size(17),
|
text("Display mode").size(17),
|
||||||
@@ -435,10 +436,11 @@ impl FereseShell {
|
|||||||
.id(format!("ferese-display-mode-{}", mode.name()).into())
|
.id(format!("ferese-display-mode-{}", mode.name()).into())
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.padding([14, 6])
|
.padding([14, 6])
|
||||||
.class(ferese_theme::controls::material_button_style(
|
.class(ferese_theme::controls::material_button_style_with_focus(
|
||||||
palette,
|
palette,
|
||||||
model.selected == mode,
|
model.selected == mode,
|
||||||
material_opacity,
|
material_opacity,
|
||||||
|
focus_visible,
|
||||||
))
|
))
|
||||||
.on_press_maybe(available.then_some(cosmic::Action::App(Message::SelectDisplayMode(mode)))),
|
.on_press_maybe(available.then_some(cosmic::Action::App(Message::SelectDisplayMode(mode)))),
|
||||||
);
|
);
|
||||||
@@ -504,6 +506,11 @@ impl FereseShell {
|
|||||||
palette,
|
palette,
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
.class(ferese_theme::controls::button_style_with_focus(
|
||||||
|
palette,
|
||||||
|
false,
|
||||||
|
focus_visible
|
||||||
|
))
|
||||||
.id("ferese-modal-cancel".into())
|
.id("ferese-modal-cancel".into())
|
||||||
.on_press_maybe((!model.busy).then_some(cosmic::Action::App(cancel))),
|
.on_press_maybe((!model.busy).then_some(cosmic::Action::App(cancel))),
|
||||||
ferese_theme::controls::text_button(
|
ferese_theme::controls::text_button(
|
||||||
@@ -512,6 +519,11 @@ impl FereseShell {
|
|||||||
palette,
|
palette,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
.class(ferese_theme::controls::button_style_with_focus(
|
||||||
|
palette,
|
||||||
|
true,
|
||||||
|
focus_visible
|
||||||
|
))
|
||||||
.id("ferese-display-apply".into())
|
.id("ferese-display-apply".into())
|
||||||
.on_press_maybe(can_apply.then_some(cosmic::Action::App(apply))),
|
.on_press_maybe(can_apply.then_some(cosmic::Action::App(apply))),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -368,6 +368,7 @@ impl cosmic::Application for FereseShell {
|
|||||||
.load(Ordering::Relaxed)
|
.load(Ordering::Relaxed)
|
||||||
.then_some(Message::Event(event, id)),
|
.then_some(Message::Event(event, id)),
|
||||||
Event::Keyboard(_)
|
Event::Keyboard(_)
|
||||||
|
| Event::Mouse(cosmic::iced::mouse::Event::ButtonPressed(_))
|
||||||
| Event::Window(window::Event::Opened { .. } | window::Event::Closed)
|
| Event::Window(window::Event::Opened { .. } | window::Event::Closed)
|
||||||
| Event::PlatformSpecific(PlatformSpecific::Wayland(
|
| Event::PlatformSpecific(PlatformSpecific::Wayland(
|
||||||
wayland::Event::Popup(..) | wayland::Event::Layer(..) | wayland::Event::Output(..),
|
wayland::Event::Popup(..) | wayland::Event::Layer(..) | wayland::Event::Output(..),
|
||||||
|
|||||||
@@ -164,6 +164,13 @@ impl FereseShell {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn handle_event(&mut self, event: Event, id: window::Id) -> Task<Message> {
|
pub(super) fn handle_event(&mut self, event: Event, id: window::Id) -> Task<Message> {
|
||||||
|
if let Event::Mouse(cosmic::iced::mouse::Event::ButtonPressed(_)) = &event
|
||||||
|
&& let Some(modal) = &mut self.system_modal
|
||||||
|
&& modal.contains(id)
|
||||||
|
{
|
||||||
|
modal.focus_visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
if let Event::Mouse(mouse) = &event {
|
if let Event::Mouse(mouse) = &event {
|
||||||
if !self
|
if !self
|
||||||
.outputs
|
.outputs
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ pub(super) struct SystemModal {
|
|||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
inhibitors: Option<compositor_ipc::Approval>,
|
inhibitors: Option<compositor_ipc::Approval>,
|
||||||
keyboard_nav: bool,
|
keyboard_nav: bool,
|
||||||
|
pub(super) focus_visible: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SystemModal {
|
impl SystemModal {
|
||||||
@@ -236,6 +237,7 @@ impl FereseShell {
|
|||||||
error: None,
|
error: None,
|
||||||
inhibitors: mode.is_none().then(compositor_ipc::Approval::default),
|
inhibitors: mode.is_none().then(compositor_ipc::Approval::default),
|
||||||
keyboard_nav,
|
keyboard_nav,
|
||||||
|
focus_visible: false,
|
||||||
});
|
});
|
||||||
Task::batch(tasks)
|
Task::batch(tasks)
|
||||||
}
|
}
|
||||||
@@ -387,10 +389,17 @@ impl FereseShell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn navigate_system_modal(&self, backwards: bool) -> Task<Message> {
|
pub(super) fn navigate_system_modal(&mut self, backwards: bool) -> Task<Message> {
|
||||||
if self.system_modal.as_ref().is_none_or(|modal| modal.motion.closing()) {
|
let Some(modal) = &mut self.system_modal else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
|
||||||
|
if modal.motion.closing() {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modal.focus_visible = true;
|
||||||
|
|
||||||
cosmic::iced::advanced::widget::operate(modal_focus_operation(backwards))
|
cosmic::iced::advanced::widget::operate(modal_focus_operation(backwards))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,14 +545,25 @@ impl FereseShell {
|
|||||||
row![
|
row![
|
||||||
Space::new().width(Length::Fill),
|
Space::new().width(Length::Fill),
|
||||||
ferese_theme::controls::text_button("Cancel", shell_font(), palette, false)
|
ferese_theme::controls::text_button("Cancel", shell_font(), palette, false)
|
||||||
|
.class(ferese_theme::controls::button_style_with_focus(
|
||||||
|
palette,
|
||||||
|
false,
|
||||||
|
modal.focus_visible,
|
||||||
|
))
|
||||||
.id("ferese-modal-cancel".into())
|
.id("ferese-modal-cancel".into())
|
||||||
.on_press(cosmic::Action::App(Message::CancelPower)),
|
.on_press(cosmic::Action::App(Message::CancelPower)),
|
||||||
ferese_theme::controls::text_button(label, shell_font(), palette, true).on_press_maybe(
|
ferese_theme::controls::text_button(label, shell_font(), palette, true)
|
||||||
modal
|
.class(ferese_theme::controls::button_style_with_focus(
|
||||||
.inhibitors
|
palette,
|
||||||
.is_some()
|
true,
|
||||||
.then_some(cosmic::Action::App(Message::ExecutePower))
|
modal.focus_visible,
|
||||||
),
|
))
|
||||||
|
.on_press_maybe(
|
||||||
|
modal
|
||||||
|
.inhibitors
|
||||||
|
.is_some()
|
||||||
|
.then_some(cosmic::Action::App(Message::ExecutePower))
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.spacing(10),
|
.spacing(10),
|
||||||
)
|
)
|
||||||
@@ -582,6 +602,11 @@ impl FereseShell {
|
|||||||
container(row![
|
container(row![
|
||||||
Space::new().width(Length::Fill),
|
Space::new().width(Length::Fill),
|
||||||
ferese_theme::controls::text_button("Got it", shell_font(), palette, true,)
|
ferese_theme::controls::text_button("Got it", shell_font(), palette, true,)
|
||||||
|
.class(ferese_theme::controls::button_style_with_focus(
|
||||||
|
palette,
|
||||||
|
true,
|
||||||
|
modal.focus_visible,
|
||||||
|
))
|
||||||
.id("ferese-modal-cancel".into())
|
.id("ferese-modal-cancel".into())
|
||||||
.on_press(cosmic::Action::App(Message::CancelPower)),
|
.on_press(cosmic::Action::App(Message::CancelPower)),
|
||||||
])
|
])
|
||||||
|
|||||||
Reference in new issue
Block a user