From e47684ffdb98482f8147f9c01a09f4917b177bd3 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Mon, 11 Mar 2024 15:21:48 -0400 Subject: [PATCH] refactor: icon styling and headerbar icon styling Headerbar icons are transparent when their window is not focused, but otherwise share the same style as icons with selection. This updates the icon styles to match figma when selected. --- examples/cosmic/src/window.rs | 1 - examples/multi-window/src/window.rs | 8 ++- src/app/core.rs | 11 ++++ src/app/cosmic.rs | 24 ++++++++ src/app/mod.rs | 6 +- src/theme/style/button.rs | 66 ++++++++++++++++------ src/widget/button/icon.rs | 8 +++ src/widget/button/style.rs | 8 +-- src/widget/button/widget.rs | 10 ++-- src/widget/color_picker/mod.rs | 6 +- src/widget/header_bar.rs | 88 ++--------------------------- 11 files changed, 121 insertions(+), 115 deletions(-) diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index e0e98ece..d09375e9 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -469,7 +469,6 @@ impl Application for Window { }; let mut header = header_bar() - .window_id(window::Id::MAIN) .title("COSMIC Design System - Iced") .on_close(Message::Close) .on_drag(Message::Drag) diff --git a/examples/multi-window/src/window.rs b/examples/multi-window/src/window.rs index dd47212b..3fb843b6 100644 --- a/examples/multi-window/src/window.rs +++ b/examples/multi-window/src/window.rs @@ -131,7 +131,11 @@ impl cosmic::Application for MultiWindow { let input = text_input("something", &w.input_value) .on_input(move |msg| Message::Input(input_id.clone(), msg)) .id(w.input_id.clone()); - + let focused = self + .core() + .focused_window() + .map(|i| i == id) + .unwrap_or_default(); let new_window_button = button(text("New Window")).on_press(Message::NewWindow); let content = scrollable( @@ -151,7 +155,7 @@ impl cosmic::Application for MultiWindow { if id == window::Id::MAIN { window_content.into() } else { - column![header_bar().window_id(id), window_content].into() + column![header_bar().focused(focused), window_content].into() } } diff --git a/src/app/core.rs b/src/app/core.rs index f40ae384..f0d33385 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use crate::config::CosmicTk; use cosmic_config::CosmicConfigEntry; use cosmic_theme::ThemeMode; +use iced::window; use iced_core::window::Id; use palette::Srgba; @@ -57,6 +58,9 @@ pub struct Core { /// Scaling factor used by the application scale_factor: f32, + /// Window focus state + pub(super) focused_window: Option, + pub(super) theme_sub_counter: u64, /// Last known system theme pub(super) system_theme: Theme, @@ -136,6 +140,7 @@ impl Default for Core { height: 0, width: 0, }, + focused_window: None, #[cfg(feature = "applet")] applet: crate::applet::Context::default(), #[cfg(feature = "single-instance")] @@ -285,6 +290,12 @@ impl Core { ) } + /// Get the current focused window if it exists + #[must_use] + pub fn focused_window(&self) -> Option { + self.focused_window.clone() + } + /// Whether the application should use a dark theme, according to the system #[must_use] pub fn system_is_dark(&self) -> bool { diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 45833bab..09c2d8c5 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -78,6 +78,10 @@ pub enum Message { ShowWindowMenu, #[cfg(feature = "xdg-portal")] DesktopSettings(crate::theme::portal::Desktop), + /// Window focus changed + Focus(window::Id), + /// Window focus lost + Unfocus(window::Id), } #[derive(Default)] @@ -159,6 +163,10 @@ where iced::Event::Window(id, window::Event::Closed) => { return Some(Message::SurfaceClosed(id)) } + iced::Event::Window(id, window::Event::Focused) => return Some(Message::Focus(id)), + iced::Event::Window(id, window::Event::Unfocused) => { + return Some(Message::Unfocus(id)) + } #[cfg(feature = "wayland")] iced::Event::PlatformSpecific(PlatformSpecific::Wayland(event)) => match event { wayland::Event::Window(WindowEvent::State(state), _surface, id) => { @@ -557,6 +565,22 @@ impl Cosmic { Message::ToolkitConfig(config) => { self.app.core_mut().toolkit_config = config; } + + Message::Focus(f) => { + self.app.core_mut().focused_window = Some(f); + } + + Message::Unfocus(id) => { + let core = self.app.core_mut(); + if core + .focused_window + .as_ref() + .map(|cur| *cur == id) + .unwrap_or_default() + { + core.focused_window = None; + } + } } iced::Command::none() diff --git a/src/app/mod.rs b/src/app/mod.rs index d7fd3221..3e015395 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -642,6 +642,10 @@ impl ApplicationExt for App { fn view_main(&self) -> Element> { let core = self.core(); let is_condensed = core.is_condensed(); + let focused = core + .focused_window() + .map(|i| i == self.main_window_id()) + .unwrap_or_default(); let content_row = crate::widget::row::with_children({ let mut widgets = Vec::with_capacity(2); @@ -686,7 +690,7 @@ impl ApplicationExt for App { .push_maybe(if core.window.show_headerbar { Some({ let mut header = crate::widget::header_bar() - .window_id(self.main_window_id()) + .focused(focused) .title(&core.window.header_title) .on_drag(Message::Cosmic(cosmic::Message::Drag)) .on_close(Message::Cosmic(cosmic::Message::Close)) diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 4b3549c3..afd9dffb 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -22,6 +22,7 @@ pub enum Button { Destructive, Link, Icon, + HeaderBar, IconVertical, Image, #[default] @@ -66,7 +67,7 @@ pub fn appearance( appearance.icon_color = icon; } - Button::Icon | Button::IconVertical => { + Button::Icon | Button::IconVertical | Button::HeaderBar => { if matches!(style, Button::IconVertical) { corner_radii = &cosmic.corner_radii.radius_m; } @@ -146,14 +147,23 @@ pub fn appearance( impl StyleSheet for crate::Theme { type Style = Button; - fn active(&self, focused: bool, style: &Self::Style) -> Appearance { + fn active(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance { if let Button::Custom { active, .. } = style { return active(focused, self); } + let accent = self.cosmic().accent_color(); appearance(self, focused, style, |component| { - let text_color = if let Button::Icon | Button::IconVertical = style { - None + let text_color = if matches!( + style, + Button::Icon | Button::IconVertical | Button::HeaderBar + ) && selected + { + Some(accent.into()) + } else if matches!(style, Button::HeaderBar) && !selected { + let mut c = Color::from(component.on); + c.a = 0.8; + Some(c) } else { Some(component.on.into()) }; @@ -179,39 +189,61 @@ impl StyleSheet for crate::Theme { } fn drop_target(&self, style: &Self::Style) -> Appearance { - self.active(false, style) + self.active(false, false, style) } - fn hovered(&self, focused: bool, style: &Self::Style) -> Appearance { + fn hovered(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance { if let Button::Custom { hovered, .. } = style { return hovered(focused, self); } + let accent = self.cosmic().accent_button.hover; appearance( self, focused || matches!(style, Button::Image), style, |component| { - ( - component.hover.into(), - Some(component.on.into()), - Some(component.on.into()), - ) + let text_color = if matches!( + style, + Button::Icon | Button::IconVertical | Button::HeaderBar + ) && selected + { + Some(accent.into()) + } else if matches!(style, Button::HeaderBar) && !selected { + let mut c = Color::from(component.on); + c.a = 0.8; + Some(c) + } else { + Some(component.on.into()) + }; + + (component.hover.into(), text_color, text_color) }, ) } - fn pressed(&self, focused: bool, style: &Self::Style) -> Appearance { + fn pressed(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance { if let Button::Custom { pressed, .. } = style { return pressed(focused, self); } + let accent = self.cosmic().accent_button.pressed; appearance(self, focused, style, |component| { - ( - component.pressed.into(), - Some(component.on.into()), - Some(component.on.into()), - ) + let text_color = if matches!( + style, + Button::Icon | Button::IconVertical | Button::HeaderBar + ) && selected + { + Some(accent.into()) + } else if matches!(style, Button::HeaderBar) && !selected { + let mut c = Color::from(component.on); + c.a = 0.8; + Some(c) + } else { + Some(component.on.into()) + }; + + (component.pressed.into(), text_color, text_color) }) } diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index 9a70a510..6ac98389 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -16,12 +16,14 @@ pub type Button<'a, Message> = Builder<'a, Message, Icon>; pub struct Icon { handle: Handle, vertical: bool, + selected: bool, } pub fn icon<'a, Message>(handle: impl Into) -> Button<'a, Message> { Button::new(Icon { handle: handle.into(), vertical: false, + selected: false, }) } @@ -124,6 +126,11 @@ impl<'a, Message> Button<'a, Message> { self } + pub fn selected(mut self, selected: bool) -> Self { + self.variant.selected = selected; + self + } + pub fn vertical(mut self, vertical: bool) -> Self { self.variant.vertical = vertical; self.style = Style::IconVertical; @@ -179,6 +186,7 @@ impl<'a, Message: Clone + 'static> From> for Element<'a, Mes .padding(0) .id(builder.id) .on_press_maybe(builder.on_press) + .selected(builder.variant.selected) .style(builder.style); if builder.tooltip.is_empty() { diff --git a/src/widget/button/style.rs b/src/widget/button/style.rs index 9cda41e9..a097c3af 100644 --- a/src/widget/button/style.rs +++ b/src/widget/button/style.rs @@ -68,21 +68,21 @@ pub trait StyleSheet { type Style: Default; /// Produces the active [`Appearance`] of a button. - fn active(&self, focused: bool, style: &Self::Style) -> Appearance; + fn active(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance; /// Produces the disabled [`Appearance`] of a button. fn disabled(&self, style: &Self::Style) -> Appearance; /// [`Appearance`] when the button is the target of a DND operation. fn drop_target(&self, style: &Self::Style) -> Appearance { - self.hovered(false, style) + self.hovered(false, false, style) } /// Produces the hovered [`Appearance`] of a button. - fn hovered(&self, focused: bool, style: &Self::Style) -> Appearance; + fn hovered(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance; /// Produces the pressed [`Appearance`] of a button. - fn pressed(&self, focused: bool, style: &Self::Style) -> Appearance; + fn pressed(&self, focused: bool, selected: bool, style: &Self::Style) -> Appearance; /// Background color of the selection indicator fn selection_background(&self) -> Background; diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 033fe24d..d17dd623 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -528,10 +528,10 @@ where } if self.on_press.is_none() { - node.set_disabled() + node.set_disabled(); } if is_hovered { - node.set_hovered() + node.set_hovered(); } node.set_default_action_verb(DefaultActionVerb::Click); @@ -696,12 +696,12 @@ where style_sheet.disabled(style) } else if is_mouse_over { if state.is_pressed { - style_sheet.pressed(state.is_focused || is_selected, style) + style_sheet.pressed(state.is_focused, is_selected, style) } else { - style_sheet.hovered(state.is_focused || is_selected, style) + style_sheet.hovered(state.is_focused, is_selected, style) } } else { - style_sheet.active(state.is_focused || is_selected, style) + style_sheet.active(state.is_focused, is_selected, style) }; let doubled_border_width = styling.border_width * 2.0; diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 0c4dc0b0..7ff646b8 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -799,7 +799,7 @@ pub fn color_button<'a, Message: 'static>( } else { (0.0, Color::TRANSPARENT) }; - let standard = theme.active(focused, &Button::Standard); + let standard = theme.active(focused, false, &Button::Standard); button::Appearance { shadow_offset: Vector::default(), background: color.map(Background::from).or(standard.background), @@ -837,7 +837,7 @@ pub fn color_button<'a, Message: 'static>( (0.0, Color::TRANSPARENT) }; - let standard = theme.hovered(focused, &Button::Standard); + let standard = theme.hovered(focused, false, &Button::Standard); button::Appearance { shadow_offset: Vector::default(), background: color.map(Background::from).or(standard.background), @@ -859,7 +859,7 @@ pub fn color_button<'a, Message: 'static>( (0.0, Color::TRANSPARENT) }; - let standard = theme.pressed(focused, &Button::Standard); + let standard = theme.pressed(focused, false, &Button::Standard); button::Appearance { shadow_offset: Vector::default(), background: color.map(Background::from).or(standard.background), diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 6bed9828..60fcbc85 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -5,7 +5,7 @@ use crate::{ext::CollectionWidget, widget, Element}; use apply::Apply; use derive_setters::Setters; use iced::{window, Length}; -use iced_core::{renderer::Quad, widget::tree, Background, Renderer, Widget}; +use iced_core::{widget::tree, Widget}; use std::borrow::Cow; #[must_use] @@ -20,7 +20,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { start: Vec::new(), center: Vec::new(), end: Vec::new(), - window_id: None, + focused: false, } } @@ -50,9 +50,8 @@ pub struct HeaderBar<'a, Message> { #[setters(strip_option)] on_right_click: Option, - /// The window id for the headerbar. - #[setters(strip_option)] - window_id: Option, + /// Focused state of the window + focused: bool, /// Elements packed at the start of the headerbar. #[setters(skip)] @@ -100,7 +99,6 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { #[must_use] pub fn build(self) -> HeaderBarWidget<'a, Message> { HeaderBarWidget { - window_id: self.window_id, header_bar_inner: self.into_element(), } } @@ -108,7 +106,6 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { pub struct HeaderBarWidget<'a, Message> { header_bar_inner: Element<'a, Message>, - window_id: Option, } impl<'a, Message: Clone + 'static> Widget @@ -122,23 +119,6 @@ impl<'a, Message: Clone + 'static> Widget tree::Tag { - tree::Tag::of::() - } - - fn diff(&mut self, tree: &mut tree::Tree) { - tree.diff_children(&mut [&mut self.header_bar_inner]); - let prev = tree.state.downcast_mut::(); - if prev.window_id != self.window_id { - *prev = MyState::new(self.window_id); - } - } - - fn state(&self) -> tree::State { - let state = MyState::new(self.window_id); - tree::State::new(state) - } - fn layout( &self, tree: &mut tree::Tree, @@ -174,28 +154,6 @@ impl<'a, Message: Clone + 'static> Widget(); - if !state.window_has_focus { - let header_bar_appearance = - ::appearance( - theme, - &crate::theme::Container::HeaderBar, - ); - let cosmic = theme.cosmic(); - let mut neutral_0 = cosmic.palette.neutral_0; - neutral_0.alpha = 0.3; - - // draw overlay rectangle - renderer.fill_quad( - Quad { - bounds: layout.bounds(), - border: header_bar_appearance.border, - shadow: header_bar_appearance.shadow, - }, - Background::Color(neutral_0.into()), - ); - } } fn on_event( @@ -209,14 +167,6 @@ impl<'a, Message: Clone + 'static> Widget, viewport: &iced_core::Rectangle, ) -> iced_core::event::Status { - if let iced_core::Event::Window(id, iced_core::window::Event::Focused) = event { - let state = state.state.downcast_mut::(); - state.focus_window(id); - } else if let iced_core::Event::Window(id, iced_core::window::Event::Unfocused) = event { - let state = state.state.downcast_mut::(); - state.unfocus_window(id); - } - let child_state = &mut state.children[0]; let child_layout = layout.children().next().unwrap(); self.header_bar_inner.as_widget_mut().on_event( @@ -370,6 +320,8 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { widget::icon::from_svg_bytes(icon_bytes) .symbolic(true) .apply(widget::button::icon) + .style(crate::theme::Button::HeaderBar) + .selected(self.focused) .icon_size(size) .on_press(on_press) }; @@ -415,31 +367,3 @@ impl<'a, Message: Clone + 'static> From> for Elemen Element::new(headerbar) } } - -pub struct MyState { - pub window_id: Option, - pub window_has_focus: bool, -} - -impl MyState { - pub fn new(id: Option) -> Self { - Self { - window_id: id, - window_has_focus: id.is_none(), - } - } - - pub fn focus_window(&mut self, id: window::Id) { - if self.window_id != Some(id) { - return; - } - self.window_has_focus = true; - } - - pub fn unfocus_window(&mut self, id: window::Id) { - if self.window_id != Some(id) { - return; - } - self.window_has_focus = false; - } -}