From 709ab268a0b0b949da1e0f999426f6b30f467416 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 20 May 2024 00:23:31 +0200 Subject: [PATCH 01/46] refactor(app): iced::Command -> Command --- src/app/mod.rs | 71 +++++++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 41 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index e9bc84ed..3fbc7cc8 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -429,7 +429,7 @@ where fn core_mut(&mut self) -> &mut Core; /// Creates the application, and optionally emits command on initialize. - fn init(core: Core, flags: Self::Flags) -> (Self, iced::Command>); + fn init(core: Core, flags: Self::Flags) -> (Self, Command); /// Displays a context drawer on the side of the application window when `Some`. fn context_drawer(&self) -> Option> { @@ -506,28 +506,28 @@ where } // Called when context drawer is toggled - fn on_context_drawer(&mut self) -> iced::Command> { - iced::Command::none() + fn on_context_drawer(&mut self) -> Command { + Command::none() } /// Called when the escape key is pressed. - fn on_escape(&mut self) -> iced::Command> { - iced::Command::none() + fn on_escape(&mut self) -> Command { + Command::none() } /// Called when a navigation item is selected. - fn on_nav_select(&mut self, id: nav_bar::Id) -> iced::Command> { - iced::Command::none() + fn on_nav_select(&mut self, id: nav_bar::Id) -> Command { + Command::none() } /// Called when a context menu is requested for a navigation item. - fn on_nav_context(&mut self, id: nav_bar::Id) -> iced::Command> { - iced::Command::none() + fn on_nav_context(&mut self, id: nav_bar::Id) -> Command { + Command::none() } /// Called when the search function is requested. - fn on_search(&mut self) -> iced::Command> { - iced::Command::none() + fn on_search(&mut self) -> Command { + Command::none() } /// Called when a window is resized. @@ -539,8 +539,8 @@ where } /// Respond to an application-specific message. - fn update(&mut self, message: Self::Message) -> iced::Command> { - iced::Command::none() + fn update(&mut self, message: Self::Message) -> Command { + Command::none() } /// Respond to a system theme change @@ -548,8 +548,8 @@ where &mut self, keys: &[&'static str], new_theme: &cosmic_theme::Theme, - ) -> iced::Command> { - iced::Command::none() + ) -> Command { + Command::none() } /// Respond to a system theme mode change @@ -557,8 +557,8 @@ where &mut self, keys: &[&'static str], new_theme: &cosmic_theme::ThemeMode, - ) -> iced::Command> { - iced::Command::none() + ) -> Command { + Command::none() } /// Constructs the view for the main window. @@ -576,24 +576,21 @@ where /// Handles dbus activation messages #[cfg(feature = "single-instance")] - fn dbus_activation( - &mut self, - msg: DbusActivationMessage, - ) -> iced::Command> { - iced::Command::none() + fn dbus_activation(&mut self, msg: DbusActivationMessage) -> Command { + Command::none() } } /// Methods automatically derived for all types implementing [`Application`]. pub trait ApplicationExt: Application { /// Initiates a window drag. - fn drag(&mut self) -> iced::Command>; + fn drag(&mut self) -> Command; /// Maximizes the window. - fn maximize(&mut self) -> iced::Command>; + fn maximize(&mut self) -> Command; /// Minimizes the window. - fn minimize(&mut self) -> iced::Command>; + fn minimize(&mut self) -> Command; /// Get the title of the main window. #[cfg(not(any(feature = "multi-window", feature = "wayland")))] @@ -615,30 +612,26 @@ pub trait ApplicationExt: Application { #[cfg(not(any(feature = "multi-window", feature = "wayland")))] /// Set the title of the main window. - fn set_window_title(&mut self, title: String) -> iced::Command>; + fn set_window_title(&mut self, title: String) -> Command; #[cfg(any(feature = "multi-window", feature = "wayland"))] /// Set the title of a window. - fn set_window_title( - &mut self, - title: String, - id: window::Id, - ) -> iced::Command>; + fn set_window_title(&mut self, title: String, id: window::Id) -> Command; /// View template for the main window. fn view_main(&self) -> Element>; } impl ApplicationExt for App { - fn drag(&mut self) -> iced::Command> { + fn drag(&mut self) -> Command { command::drag(Some(self.main_window_id())) } - fn maximize(&mut self) -> iced::Command> { + fn maximize(&mut self) -> Command { command::maximize(Some(self.main_window_id()), true) } - fn minimize(&mut self) -> iced::Command> { + fn minimize(&mut self) -> Command { command::minimize(Some(self.main_window_id())) } @@ -656,21 +649,17 @@ impl ApplicationExt for App { } #[cfg(any(feature = "multi-window", feature = "wayland"))] - fn set_window_title( - &mut self, - title: String, - id: window::Id, - ) -> iced::Command> { + fn set_window_title(&mut self, title: String, id: window::Id) -> Command { self.core_mut().title.insert(id, title.clone()); command::set_title(Some(id), title) } #[cfg(not(any(feature = "multi-window", feature = "wayland")))] - fn set_window_title(&mut self, title: String) -> iced::Command> { + fn set_window_title(&mut self, title: String) -> Command { let id = self.main_window_id(); self.core_mut().title.insert(id, title.clone()); - iced::Command::none() + Command::none() } /// Creates the view for the main window. From b3bce0740ec0aa1f83f2b932d5a625462be08522 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 20 May 2024 00:28:02 +0200 Subject: [PATCH 02/46] refactor(menu): add `menu::bar` as shorthand for `MenuBar::new` --- src/widget/menu.rs | 3 ++- src/widget/menu/menu_bar.rs | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/widget/menu.rs b/src/widget/menu.rs index cd30a17c..2348b9af 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -59,10 +59,11 @@ pub use action::MenuAction as Action; mod flex; pub mod key_bind; +pub use key_bind::KeyBind; mod menu_bar; -pub use menu_bar::MenuBar; pub(crate) use menu_bar::MenuBarState; +pub use menu_bar::{menu_bar as bar, MenuBar}; mod menu_inner; mod menu_tree; diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index 14192b40..c885913e 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -19,6 +19,13 @@ use iced_widget::core::{ Alignment, Clipboard, Element, Layout, Length, Padding, Rectangle, Shell, Widget, }; +/// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. +pub fn menu_bar( + menu_roots: Vec>, +) -> MenuBar { + MenuBar::new(menu_roots) +} + pub(crate) struct MenuBarState { pub(crate) pressed: bool, pub(crate) view_cursor: Cursor, @@ -56,8 +63,7 @@ impl Default for MenuBarState { } } -/// A `MenuBar` collects `MenuTree`s and handles -/// all the layout, event processing and drawing +/// A `MenuBar` collects `MenuTree`s and handles all the layout, event processing, and drawing. #[allow(missing_debug_implementations)] pub struct MenuBar<'a, Message, Renderer = crate::Renderer> where From 31ea71deef7d79b277f0d7f028097d018ddc52d1 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 20 May 2024 00:50:17 +0200 Subject: [PATCH 03/46] refactor(menu): remove redundant entity argument on MenuActions --- src/widget/menu/action.rs | 8 ++------ src/widget/menu/menu_tree.rs | 8 ++++---- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/widget/menu/action.rs b/src/widget/menu/action.rs index 94da8639..25ac538f 100644 --- a/src/widget/menu/action.rs +++ b/src/widget/menu/action.rs @@ -31,7 +31,7 @@ use crate::widget::segmented_button::Entity; /// impl MenuAction for MyAction { /// type Message = MyMessage; /// -/// fn message(&self, entity: Option) -> Self::Message { +/// fn message(&self) -> Self::Message { /// match self { /// MyAction::Open => MyMessage::Open, /// MyAction::Save => MyMessage::Save, @@ -46,12 +46,8 @@ pub trait MenuAction: Clone + Copy + Eq + PartialEq { /// Returns a message of type `Self::Message` when the action is triggered. /// - /// # Arguments - /// - /// * `entity` - An optional `Entity` that may be associated with the action. - /// /// # Returns /// /// * `Self::Message` - The message that is produced when the action is triggered. - fn message(&self, entity: Option) -> Self::Message; + fn message(&self) -> Self::Message; } diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 51870c92..56715f7e 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -225,7 +225,7 @@ where Element<'a, Message, crate::Theme, Renderer>: From>, { fn find_key(action: &A, key_binds: &HashMap) -> String { - for (key_bind, key_action) in key_binds.iter() { + for (key_bind, key_action) in key_binds { if action == key_action { return key_bind.to_string(); } @@ -248,12 +248,12 @@ where widget::horizontal_space(Length::Fill), widget::text(key), ) - .on_press(action.message(None)); + .on_press(action.message()); trees.push(MenuTree::::new(menu_button)); } MenuItem::CheckBox(label, value, action) => { - let key = find_key(&action, &key_binds); + let key = find_key(&action, key_binds); trees.push(MenuTree::new( menu_button!( if value { @@ -267,7 +267,7 @@ where widget::horizontal_space(Length::Fill), widget::text(key) ) - .on_press(action.message(None)), + .on_press(action.message()), )); } MenuItem::Folder(label, children) => { From 16c1265535a0a4d9e1e5ded2f39096dc8f144e9c Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 20 May 2024 00:59:40 +0200 Subject: [PATCH 04/46] improv(segmented-button): set icons with `impl Into` --- src/widget/segmented_button/model/builder.rs | 4 ++-- src/widget/segmented_button/model/entity.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/widget/segmented_button/model/builder.rs b/src/widget/segmented_button/model/builder.rs index 87d25adb..df59bd4d 100644 --- a/src/widget/segmented_button/model/builder.rs +++ b/src/widget/segmented_button/model/builder.rs @@ -103,8 +103,8 @@ where /// .build() /// ``` #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] - pub fn icon(mut self, icon: Icon) -> Self { - self.model.0.icon_set(self.id, icon); + pub fn icon(mut self, icon: impl Into) -> Self { + self.model.0.icon_set(self.id, icon.into()); self } diff --git a/src/widget/segmented_button/model/entity.rs b/src/widget/segmented_button/model/entity.rs index c9f0997d..f23f7130 100644 --- a/src/widget/segmented_button/model/entity.rs +++ b/src/widget/segmented_button/model/entity.rs @@ -89,8 +89,8 @@ where /// model.insert().text("Item A").icon(IconSource::from("icon-a")); /// ``` #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] - pub fn icon(self, icon: Icon) -> Self { - self.model.icon_set(self.id, icon); + pub fn icon(self, icon: impl Into) -> Self { + self.model.icon_set(self.id, icon.into()); self } From 0d4c3db16297f669a31f27299308729ffcd819d6 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 20 May 2024 17:19:42 +0200 Subject: [PATCH 05/46] chore(doc): inline re-exported API docs --- cosmic-config/src/lib.rs | 2 ++ src/config/mod.rs | 1 + src/lib.rs | 21 ++++++++++++++ src/theme/style/mod.rs | 9 ++++++ src/widget/mod.rs | 62 ++++++++++++++++++++++++++++++++++++++-- 5 files changed, 93 insertions(+), 2 deletions(-) diff --git a/cosmic-config/src/lib.rs b/cosmic-config/src/lib.rs index b0db5828..48a18a52 100644 --- a/cosmic-config/src/lib.rs +++ b/cosmic-config/src/lib.rs @@ -1,3 +1,5 @@ +//! Integrations for cosmic-config — the cosmic configuration system. + use notify::{ event::{EventKind, ModifyKind}, Watcher, diff --git a/src/config/mod.rs b/src/config/mod.rs index 78bf068a..0dca84db 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,4 +4,5 @@ //! Configurations available to libcosmic applications. pub mod toolkit; +#[doc(inline)] pub use toolkit::CosmicTk; diff --git a/src/lib.rs b/src/lib.rs index fe23fb5a..ee29b3e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,10 @@ pub mod command; pub mod config; +#[doc(inline)] pub use cosmic_config; + +#[doc(inline)] pub use cosmic_theme; #[cfg(any(feature = "xdg-portal", feature = "rfd"))] @@ -43,15 +46,31 @@ mod ext; pub mod font; +#[doc(inline)] pub use iced; + +#[doc(inline)] pub use iced_core; + +#[doc(inline)] pub use iced_futures; + +#[doc(inline)] pub use iced_renderer; + +#[doc(inline)] pub use iced_runtime; + #[cfg(feature = "wayland")] pub use iced_sctk; + +#[doc(inline)] pub use iced_style; + +#[doc(inline)] pub use iced_widget; + +#[doc(inline)] #[cfg(feature = "winit")] pub use iced_winit; @@ -67,6 +86,8 @@ pub mod process; pub use cctk; pub mod theme; + +#[doc(inline)] pub use theme::{style, Theme}; pub mod widget; diff --git a/src/theme/style/mod.rs b/src/theme/style/mod.rs index a0108cbe..0469673a 100644 --- a/src/theme/style/mod.rs +++ b/src/theme/style/mod.rs @@ -9,18 +9,27 @@ pub use self::button::Button; mod dropdown; pub mod iced; +#[doc(inline)] pub use self::iced::Application; +#[doc(inline)] pub use self::iced::Checkbox; +#[doc(inline)] pub use self::iced::Container; +#[doc(inline)] pub use self::iced::ProgressBar; +#[doc(inline)] pub use self::iced::Rule; +#[doc(inline)] pub use self::iced::Svg; +#[doc(inline)] pub use self::iced::Text; pub mod menu_bar; mod segmented_button; +#[doc(inline)] pub use self::segmented_button::SegmentedButton; mod text_input; +#[doc(inline)] pub use self::text_input::TextInput; diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 501eb351..94e07af0 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -4,40 +4,73 @@ //! Cosmic-themed widget implementations. // Re-exports from Iced +#[doc(inline)] pub use iced::widget::{checkbox, Checkbox}; + +#[doc(inline)] pub use iced::widget::{combo_box, ComboBox}; + +#[doc(inline)] pub use iced::widget::{container, Container}; + +#[doc(inline)] pub use iced::widget::{horizontal_space, space, vertical_space, Space}; + +#[doc(inline)] pub use iced::widget::{image, Image}; + +#[doc(inline)] pub use iced::widget::{lazy, Lazy}; + +#[doc(inline)] pub use iced::widget::{mouse_area, MouseArea}; + +#[doc(inline)] pub use iced::widget::{pane_grid, PaneGrid}; + +#[doc(inline)] pub use iced::widget::{progress_bar, ProgressBar}; + +#[doc(inline)] pub use iced::widget::{radio, Radio}; + +#[doc(inline)] pub use iced::widget::{responsive, Responsive}; + +#[doc(inline)] pub use iced::widget::{slider, vertical_slider, Slider, VerticalSlider}; + +#[doc(inline)] pub use iced::widget::{svg, Svg}; + +#[doc(inline)] pub use iced_core::widget::{Id, Operation, Widget}; pub mod aspect_ratio; pub mod button; +#[doc(inline)] pub use button::{button, Button, IconButton, LinkButton, TextButton}; pub(crate) mod common; pub mod calendar; +#[doc(inline)] pub use calendar::{calendar, Calendar}; pub mod card; +#[doc(inline)] pub use card::*; pub mod color_picker; +#[doc(inline)] pub use color_picker::{ColorPicker, ColorPickerModel}; pub mod context_drawer; +#[doc(inline)] pub use context_drawer::{context_drawer, ContextDrawer}; +#[doc(inline)] pub use column::{column, Column}; pub mod column { pub type Column<'a, Message> = iced::widget::Column<'a, Message, crate::Theme, crate::Renderer>; @@ -59,12 +92,15 @@ pub mod column { } pub mod layer_container; +#[doc(inline)] pub use layer_container::{layer_container, LayerContainer}; pub mod context_menu; +#[doc(inline)] pub use context_menu::{context_menu, ContextMenu}; pub mod dialog; +#[doc(inline)] pub use dialog::{dialog, Dialog}; /// An element to distinguish a boundary between two elements. @@ -117,46 +153,59 @@ pub mod divider { } pub mod dnd_destination; +#[doc(inline)] pub use dnd_destination::{dnd_destination, DndDestination}; pub mod dnd_source; +#[doc(inline)] pub use dnd_source::{dnd_source, DndSource}; pub mod dropdown; +#[doc(inline)] pub use dropdown::{dropdown, Dropdown}; pub mod flex_row; +#[doc(inline)] pub use flex_row::{flex_row, FlexRow}; pub mod grid; +#[doc(inline)] pub use grid::{grid, Grid}; mod header_bar; +#[doc(inline)] pub use header_bar::{header_bar, HeaderBar}; pub mod icon; +#[doc(inline)] pub use icon::{icon, Icon}; #[cfg(feature = "animated-image")] pub mod frames; pub mod list; -pub use list::*; +#[doc(inline)] +pub use list::{list_column, ListColumn}; pub mod menu; pub mod nav_bar; +#[doc(inline)] pub use nav_bar::{nav_bar, nav_bar_dnd}; pub mod nav_bar_toggle; +#[doc(inline)] pub use nav_bar_toggle::{nav_bar_toggle, NavBarToggle}; pub mod popover; +#[doc(inline)] pub use popover::{popover, Popover}; pub mod rectangle_tracker; +#[doc(inline)] pub use rectangle_tracker::{rectangle_tracker, RectangleTracker}; +#[doc(inline)] pub use row::{row, Row}; pub mod row { pub type Row<'a, Message> = iced::widget::Row<'a, Message, crate::Theme, crate::Renderer>; @@ -178,6 +227,7 @@ pub mod row { } mod scrollable; +#[doc(inline)] pub use scrollable::*; pub mod segmented_button; @@ -186,19 +236,26 @@ pub mod segmented_control; pub mod settings; pub mod spin_button; +#[doc(inline)] pub use spin_button::{spin_button, SpinButton}; pub mod tab_bar; pub mod text; +#[doc(inline)] pub use text::{text, Text}; pub mod text_input; -pub use text_input::*; +#[doc(inline)] +pub use text_input::{ + editable_input, inline_input, search_input, secure_input, text_input, TextInput, +}; mod toggler; +#[doc(inline)] pub use toggler::toggler; +#[doc(inline)] pub use tooltip::{tooltip, Tooltip}; pub mod tooltip { use crate::Element; @@ -224,4 +281,5 @@ pub mod tooltip { } pub mod warning; +#[doc(inline)] pub use warning::*; From f4936344f01f592ca3b0616eff8f32e75c1e2b35 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 20 May 2024 20:01:47 +0200 Subject: [PATCH 06/46] chore(doc): add documentation for a handful of widgets --- src/widget/aspect_ratio.rs | 6 ++-- src/widget/button/icon.rs | 2 ++ src/widget/button/image.rs | 2 ++ src/widget/button/link.rs | 3 ++ src/widget/button/mod.rs | 15 +++++++++ src/widget/button/text.rs | 5 +++ src/widget/button/widget.rs | 2 +- src/widget/calendar.rs | 3 ++ src/widget/color_picker/mod.rs | 12 +++++-- src/widget/context_drawer/mod.rs | 3 ++ src/widget/dropdown/mod.rs | 3 ++ src/widget/flex_row/mod.rs | 4 ++- src/widget/grid/mod.rs | 4 ++- src/widget/mod.rs | 56 +++++++++++++++++++++++++++++++- src/widget/spin_button/mod.rs | 3 ++ 15 files changed, 113 insertions(+), 10 deletions(-) diff --git a/src/widget/aspect_ratio.rs b/src/widget/aspect_ratio.rs index 32ef16e5..c918f8c7 100644 --- a/src/widget/aspect_ratio.rs +++ b/src/widget/aspect_ratio.rs @@ -1,3 +1,5 @@ +//! A container which constraints itself to a specific aspect ratio. + use iced::widget::Container; use iced::Size; use iced_core::alignment; @@ -21,9 +23,7 @@ where AspectRatio::new(content, ratio) } -/// An element decorating some content. -/// -/// It is normally used for alignment purposes. +/// A container which constraints itself to a specific aspect ratio. #[allow(missing_debug_implementations)] pub struct AspectRatio<'a, Message, Renderer> where diff --git a/src/widget/button/icon.rs b/src/widget/button/icon.rs index d1e10d02..c22c8cb6 100644 --- a/src/widget/button/icon.rs +++ b/src/widget/button/icon.rs @@ -13,12 +13,14 @@ use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Icon>; +/// The icon variant of a button. pub struct Icon { handle: Handle, vertical: bool, selected: bool, } +/// A button constructed from an icon handle, using icon button styling. pub fn icon<'a, Message>(handle: impl Into) -> Button<'a, Message> { Button::new(Icon { handle: handle.into(), diff --git a/src/widget/button/image.rs b/src/widget/button/image.rs index 9bfa2880..d93844cd 100644 --- a/src/widget/button/image.rs +++ b/src/widget/button/image.rs @@ -11,6 +11,7 @@ use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Image<'a, Handle, Message>>; +/// A button constructed from an image handle, using image button styling. pub fn image<'a, Message>(handle: impl Into + 'a) -> Button<'a, Message> { Button::new(Image { image: widget::image(handle).border_radius([9.0; 4]), @@ -19,6 +20,7 @@ pub fn image<'a, Message>(handle: impl Into + 'a) -> Button<'a, Message> }) } +/// The image variant of a button. pub struct Image<'a, Handle, Message> { image: widget::Image<'a, Handle>, selected: bool, diff --git a/src/widget/button/link.rs b/src/widget/button/link.rs index d1d43e90..52cc44b1 100644 --- a/src/widget/button/link.rs +++ b/src/widget/button/link.rs @@ -1,6 +1,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +//! Hyperlink button widget + use super::Builder; use super::Style; use crate::prelude::*; @@ -17,6 +19,7 @@ pub struct Hyperlink { trailing_icon: bool, } +/// A hyperlink button. pub fn link<'a, Message>(label: impl Into> + 'static) -> Button<'a, Message> { Button::new( label, diff --git a/src/widget/button/mod.rs b/src/widget/button/mod.rs index 9ba5dc2f..c9cf555a 100644 --- a/src/widget/button/mod.rs +++ b/src/widget/button/mod.rs @@ -1,29 +1,41 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +//! Button widgets for COSMIC applications. + pub use crate::theme::Button as Style; pub mod link; use derive_setters::Setters; +#[doc(inline)] pub use link::link; +#[doc(inline)] pub use link::Button as LinkButton; mod icon; +#[doc(inline)] pub use icon::icon; +#[doc(inline)] pub use icon::Button as IconButton; mod image; +#[doc(inline)] pub use image::image; +#[doc(inline)] pub use image::Button as ImageButton; mod style; +#[doc(inline)] pub use style::{Appearance, StyleSheet}; mod text; +#[doc(inline)] pub use text::Button as TextButton; +#[doc(inline)] pub use text::{destructive, standard, suggested, text}; mod widget; +#[doc(inline)] pub use widget::{draw, focus, layout, mouse_interaction, Button}; use iced_core::font::Weight; @@ -31,10 +43,12 @@ use iced_core::widget::Id; use iced_core::{Length, Padding}; use std::borrow::Cow; +/// A button with the default style, which may contain any widget as its content. pub fn button<'a, Message>(content: impl Into>) -> Button<'a, Message> { Button::new(content) } +/// An image button which may contain any widget as its content. pub fn custom_image_button<'a, Message>( content: impl Into>, on_remove: Option, @@ -42,6 +56,7 @@ pub fn custom_image_button<'a, Message>( Button::new_image(content, on_remove) } +/// A builder for constructing a custom [`Button`]. #[must_use] #[derive(Setters)] pub struct Builder<'a, Message, Variant> { diff --git a/src/widget/button/text.rs b/src/widget/button/text.rs index 134e0623..f58b4374 100644 --- a/src/widget/button/text.rs +++ b/src/widget/button/text.rs @@ -10,26 +10,31 @@ use std::borrow::Cow; pub type Button<'a, Message> = Builder<'a, Message, Text>; +/// A text button with the destructive style pub fn destructive<'a, Message>(label: impl Into>) -> Button<'a, Message> { Button::new(Text::new()) .label(label) .style(Style::Destructive) } +/// A text button with the suggested style pub fn suggested<'a, Message>(label: impl Into>) -> Button<'a, Message> { Button::new(Text::new()) .label(label) .style(Style::Suggested) } +/// A text button with the standard style pub fn standard<'a, Message>(label: impl Into>) -> Button<'a, Message> { Button::new(Text::new()).label(label) } +/// A text button with the text style pub fn text<'a, Message>(label: impl Into>) -> Button<'a, Message> { Button::new(Text::new()).label(label).style(Style::Text) } +/// The text variant of a button. pub struct Text { pub(super) leading_icon: Option, pub(super) trailing_icon: Option, diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 498a94d9..7444b212 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -59,7 +59,7 @@ pub struct Button<'a, Message> { impl<'a, Message> Button<'a, Message> { /// Creates a new [`Button`] with the given content. - pub fn new(content: impl Into>) -> Self { + pub(super) fn new(content: impl Into>) -> Self { Self { id: Id::unique(), #[cfg(feature = "a11y")] diff --git a/src/widget/calendar.rs b/src/widget/calendar.rs index fc47f5f3..61825ff9 100644 --- a/src/widget/calendar.rs +++ b/src/widget/calendar.rs @@ -1,6 +1,8 @@ // Copyright 2024 System76 // SPDX-License-Identifier: MPL-2.0 +//! A widget that displays an interactive calendar. + use std::cmp; use crate::iced_core::{Length, Padding}; @@ -8,6 +10,7 @@ use crate::widget::{button, column, grid, icon, row, text, Grid}; use chrono::{Datelike, Days, Months, NaiveDate, Weekday}; use iced::alignment::{Horizontal, Vertical}; +/// A widget that displays an interactive calendar. pub fn calendar( selected: &NaiveDate, on_select: impl Fn(NaiveDate) -> M + 'static, diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 07e255cd..ff2894b1 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -1,6 +1,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +//! Widgets for selecting colors with a color picker. + use std::borrow::Cow; use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; @@ -29,9 +31,12 @@ use palette::{FromColor, RgbHue}; use super::button::StyleSheet; use super::divider::horizontal; use super::icon::{self, from_name}; -use super::segmented_button::{self, Model, SingleSelect}; +use super::segmented_button::{self, SingleSelect}; use super::{button, segmented_control, text, text_input, tooltip, Icon}; +#[doc(inline)] +pub use ColorPickerModel as Model; + // TODO is this going to look correct enough? lazy_static! { pub static ref HSV_RAINBOW: Vec = (0u16..8) @@ -64,7 +69,7 @@ pub enum ColorPickerUpdate { #[derive(Setters)] pub struct ColorPickerModel { #[setters(skip)] - segmented_model: Model, + segmented_model: segmented_button::Model, #[setters(skip)] active_color: palette::Hsv, #[setters(skip)] @@ -246,7 +251,7 @@ impl ColorPickerModel { #[derive(Setters, Clone)] pub struct ColorPickerBuilder<'a, Message> { #[setters(skip)] - model: &'a Model, + model: &'a segmented_button::Model, #[setters(skip)] active_color: palette::Hsv, #[setters(skip)] @@ -757,6 +762,7 @@ fn color_to_string(c: palette::Hsv, is_hex: bool) -> String { } } +/// A button for selecting a color from a color picker. pub fn color_button<'a, Message: 'static>( on_press: Option, color: Option, diff --git a/src/widget/context_drawer/mod.rs b/src/widget/context_drawer/mod.rs index 3a3982fd..258b4f88 100644 --- a/src/widget/context_drawer/mod.rs +++ b/src/widget/context_drawer/mod.rs @@ -1,6 +1,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +//! An overlayed widget that attaches a toggleable context drawer to the view. + mod overlay; mod widget; @@ -8,6 +10,7 @@ pub use widget::ContextDrawer; use crate::Element; +/// An overlayed widget that attaches a toggleable context drawer to the view. pub fn context_drawer<'a, Message: Clone + 'static, Content, Drawer>( header: &'a str, on_close: Message, diff --git a/src/widget/dropdown/mod.rs b/src/widget/dropdown/mod.rs index f91acb6f..6e3db648 100644 --- a/src/widget/dropdown/mod.rs +++ b/src/widget/dropdown/mod.rs @@ -2,6 +2,8 @@ // Copyright 2019 Héctor Ramón, Iced contributors // SPDX-License-Identifier: MPL-2.0 AND MIT +//! Displays a list of options in a popover menu on select. + pub mod menu; pub use menu::Menu; @@ -10,6 +12,7 @@ pub mod multi; mod widget; pub use widget::*; +/// Displays a list of options in a popover menu on select. pub fn dropdown<'a, S: AsRef, Message: 'a>( selections: &'a [S], selected: Option, diff --git a/src/widget/flex_row/mod.rs b/src/widget/flex_row/mod.rs index 95161090..4f546527 100644 --- a/src/widget/flex_row/mod.rs +++ b/src/widget/flex_row/mod.rs @@ -1,6 +1,8 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +//! Responsively generates rows of widgets based on the dimensions of its children. + pub mod layout; pub mod widget; @@ -8,7 +10,7 @@ pub use widget::FlexRow; use crate::Element; -/// Responsively generates rows and columns of widgets based on its dimmensions. +/// Responsively generates rows of widgets based on the dimensions of its children. pub const fn flex_row(children: Vec>) -> FlexRow { FlexRow::new(children) } diff --git a/src/widget/grid/mod.rs b/src/widget/grid/mod.rs index e561a22a..f4c8c652 100644 --- a/src/widget/grid/mod.rs +++ b/src/widget/grid/mod.rs @@ -1,12 +1,14 @@ // Copyright 2023 System76 // SPDX-License-Identifier: MPL-2.0 +//! Arrange widgets with a grid layout. + pub mod layout; pub mod widget; pub use widget::Grid; -/// Responsively generates rows and columns of widgets based on its dimmensions. +/// Arrange widgets with a grid layout. pub const fn grid<'a, Message>() -> Grid<'a, Message> { Grid::new() } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 94e07af0..e0745dce 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -1,7 +1,50 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 -//! Cosmic-themed widget implementations. +//! The COSMIC widget library +//! +//! This module contains a wide variety of widgets used throughout the COSMIC app ecosystem. +//! +//! # Overview +//! +//! Add widgets to your application view by calling the modules and functions below. +//! Widgets are constructed by chaining their property methods using a functional paradigm. +//! Modules may contain additional functions for constructing different variations of a widget. +//! Each module will typically have one widget with the same name as the module, which will be re-exported here. +//! +//! ```no_run +//! use cosmic::prelude::*; +//! use cosmic::{cosmic_theme, theme, widget}; +//! +//! const REPOSITORY: &str = "https://github.com/pop-os/libcosmic"; +//! +//! let cosmic_theme::Spacing { space_xxs, .. } = theme::active().cosmic().spacing; +//! +//! let link = widget::button::link(REPOSITORY) +//! .on_press(Message::LaunchUrl(REPOSITORY)) +//! .padding(0); +//! +//! let content = widget::column() +//! .push(widget::icon::from_name("my-app-icon")) +//! .push(widget::text::title3("My App Name")) +//! .push(link) +//! .align_items(Alignment::Center) +//! .spacing(space_xxs); +//! ``` +//! +//! Widgets may borrow data from your application struct, and should do so to avoid allocating. +//! +//! ```no_run +//! let text = widget::text::body(&self.cached_text); +//! ``` +//! +//! Use the [`cosmic::Apply`](crate::Apply) trait to embed widgets into other widgets which accept them. +//! +//! ```no_run +//! let button = widget::icon::from_name("printer-symbolic") +//! .apply(widget::button::icon) +//! .on_press(Message::Print); +//! ``` // Re-exports from Iced #[doc(inline)] @@ -73,19 +116,24 @@ pub use context_drawer::{context_drawer, ContextDrawer}; #[doc(inline)] pub use column::{column, Column}; pub mod column { + //! A container which aligns its children in a column. + pub type Column<'a, Message> = iced::widget::Column<'a, Message, crate::Theme, crate::Renderer>; #[must_use] + /// A container which aligns its children in a column. pub fn column<'a, Message>() -> Column<'a, Message> { Column::new() } #[must_use] + /// A pre-allocated [`column`]. pub fn with_capacity<'a, Message>(capacity: usize) -> Column<'a, Message> { Column::with_children(Vec::with_capacity(capacity)) } #[must_use] + /// A [`column`] that will be assigned a [`Vec`] of children. pub fn with_children(children: Vec>) -> Column { Column::with_children(children) } @@ -207,20 +255,26 @@ pub use rectangle_tracker::{rectangle_tracker, RectangleTracker}; #[doc(inline)] pub use row::{row, Row}; + pub mod row { + //! A container which aligns its children in a row. + pub type Row<'a, Message> = iced::widget::Row<'a, Message, crate::Theme, crate::Renderer>; #[must_use] + /// A container which aligns its children in a row. pub fn row<'a, Message>() -> Row<'a, Message> { Row::new() } #[must_use] + /// A pre-allocated [`row`]. pub fn with_capacity<'a, Message>(capacity: usize) -> Row<'a, Message> { Row::with_children(Vec::with_capacity(capacity)) } #[must_use] + /// A [`row`] that will be assigned a [`Vec`] of children. pub fn with_children(children: Vec>) -> Row { Row::with_children(children) } diff --git a/src/widget/spin_button/mod.rs b/src/widget/spin_button/mod.rs index 62c1e12d..b8806d6e 100644 --- a/src/widget/spin_button/mod.rs +++ b/src/widget/spin_button/mod.rs @@ -1,6 +1,8 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 +//! A control for incremental adjustments of a value. + mod model; use std::borrow::Cow; @@ -20,6 +22,7 @@ pub struct SpinButton<'a, Message> { on_change: Box Message + 'static>, } +/// A control for incremental adjustments of a value. pub fn spin_button<'a, Message: 'static>( label: impl Into>, on_change: impl Fn(model::Message) -> Message + 'static, From aa3f5d3401dd63df3f5615746a23d978242885f9 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 21 May 2024 01:29:50 +0200 Subject: [PATCH 07/46] improv: `impl From for Message` --- examples/open-dialog/src/main.rs | 2 +- src/app/mod.rs | 6 ++++++ src/applet/mod.rs | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index f8b95360..63bb3a10 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -140,7 +140,7 @@ impl cosmic::Application for App { // Send this back to the application. Message::FileRead(url, contents) }) - .map(cosmic::app::message::app); + .map(Into::into); } // Creates a new open dialog. diff --git a/src/app/mod.rs b/src/app/mod.rs index 3fbc7cc8..408a712b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -37,6 +37,12 @@ pub mod message { pub const fn none() -> Message { Message::None } + + impl From for Message { + fn from(value: M) -> Self { + Self::App(value) + } + } } pub use self::command::Command; diff --git a/src/applet/mod.rs b/src/applet/mod.rs index 07a9c272..2c60cc62 100644 --- a/src/applet/mod.rs +++ b/src/applet/mod.rs @@ -372,7 +372,7 @@ pub fn style() -> ::Style { pub fn menu_button<'a, Message>( content: impl Into>, ) -> crate::widget::Button<'a, Message> { - crate::widget::Button::new(content) + crate::widget::button(content) .style(Button::AppletMenu) .padding(menu_control_padding()) .width(Length::Fill) From f9f8eb51cb593c910051dce7b97abdb1ae5f31e2 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 21 May 2024 04:34:35 +0200 Subject: [PATCH 08/46] fix(menu_bar): root text will inherit icon color from header --- examples/menu/src/main.rs | 24 +++++++++++------------- examples/open-dialog/src/main.rs | 2 +- src/theme/style/button.rs | 4 ++-- src/theme/style/menu_bar.rs | 2 +- src/widget/button/widget.rs | 8 +++++++- src/widget/menu/action.rs | 2 -- src/widget/menu/menu_bar.rs | 4 ++-- src/widget/menu/menu_inner.rs | 4 ++-- 8 files changed, 26 insertions(+), 24 deletions(-) diff --git a/examples/menu/src/main.rs b/examples/menu/src/main.rs index dbd8de07..ab668cb6 100644 --- a/examples/menu/src/main.rs +++ b/examples/menu/src/main.rs @@ -14,9 +14,7 @@ use cosmic::iced_core::{Length, Size}; use cosmic::widget::menu::action::MenuAction; use cosmic::widget::menu::key_bind::KeyBind; use cosmic::widget::menu::key_bind::Modifier; -use cosmic::widget::menu::menu_tree::{menu_items, menu_root, MenuItem}; -use cosmic::widget::menu::{ItemHeight, ItemWidth, MenuBar, MenuTree}; -use cosmic::widget::segmented_button::Entity; +use cosmic::widget::menu::{self, ItemHeight, ItemWidth}; use cosmic::{executor, Element}; /// Runs application with these settings @@ -67,7 +65,7 @@ pub enum Action { impl MenuAction for Action { type Message = Message; - fn message(&self, _entity_opt: Option) -> Self::Message { + fn message(&self) -> Self::Message { match self { Action::WindowClose => Message::WindowClose, Action::ToggleHideContent => Message::ToggleHideContent, @@ -156,23 +154,23 @@ impl cosmic::Application for App { } pub fn menu_bar<'a>(config: &Config, key_binds: &HashMap) -> Element<'a, Message> { - MenuBar::new(vec![MenuTree::with_children( - menu_root("File"), - menu_items( + menu::bar(vec![menu::Tree::with_children( + menu::root("File"), + menu::items( key_binds, vec![ - MenuItem::Button("New window", Action::WindowNew), - MenuItem::Divider, - MenuItem::Folder( + menu::Item::Button("New window", Action::WindowNew), + menu::Item::Divider, + menu::Item::Folder( "View", - vec![MenuItem::CheckBox( + vec![menu::Item::CheckBox( "Hide content", config.hide_content, Action::ToggleHideContent, )], ), - MenuItem::Divider, - MenuItem::Button("Quit", Action::WindowClose), + menu::Item::Divider, + menu::Item::Button("Quit", Action::WindowClose), ], ), )]) diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index 63bb3a10..8cc01b85 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -168,7 +168,7 @@ impl cosmic::Application for App { Err(why) => Message::OpenError(Arc::new(why)), } }) - .map(cosmic::app::Message::App); + .map(Into::into); } // Displays an error in the application's warning bar. diff --git a/src/theme/style/button.rs b/src/theme/style/button.rs index 0509d62f..e79496cd 100644 --- a/src/theme/style/button.rs +++ b/src/theme/style/button.rs @@ -120,8 +120,8 @@ pub fn appearance( } Button::MenuRoot => { appearance.background = None; - appearance.icon_color = Some(cosmic.accent.base.into()); - appearance.text_color = Some(cosmic.accent.base.into()); + appearance.icon_color = None; + appearance.text_color = None; } Button::MenuItem => { let (background, _, _) = color(&cosmic.background.component); diff --git a/src/theme/style/menu_bar.rs b/src/theme/style/menu_bar.rs index 1b17c527..18b983fd 100644 --- a/src/theme/style/menu_bar.rs +++ b/src/theme/style/menu_bar.rs @@ -19,7 +19,7 @@ pub struct Appearance { pub border_color: Color, /// The expand value of the menus' background pub background_expand: [u16; 4], - /// The highlighted path [`Color`] of the the menu bar and its menus. + // /// The highlighted path [`Color`] of the the menu bar and its menus. pub path: Color, } diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 7444b212..9c079008 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -375,7 +375,13 @@ impl<'a, Message: 'a + Clone> Widget }; let mut icon_color = styling.icon_color.unwrap_or(renderer_style.icon_color); - let mut text_color = styling.text_color.unwrap_or(renderer_style.text_color); + + // Menu roots should share the accent color that icons get in the header. + let mut text_color = if matches!(self.style, crate::theme::Button::MenuRoot) { + icon_color + } else { + styling.text_color.unwrap_or(renderer_style.text_color) + }; if let Some(alpha) = headerbar_alpha { icon_color.a = alpha; diff --git a/src/widget/menu/action.rs b/src/widget/menu/action.rs index 25ac538f..1b70209a 100644 --- a/src/widget/menu/action.rs +++ b/src/widget/menu/action.rs @@ -1,5 +1,3 @@ -use crate::widget::segmented_button::Entity; - /// `MenuAction` is a trait that represents an action in a menu. /// /// It is used to define the behavior of menu items when they are activated. diff --git a/src/widget/menu/menu_bar.rs b/src/widget/menu/menu_bar.rs index c885913e..c191ac0d 100644 --- a/src/widget/menu/menu_bar.rs +++ b/src/widget/menu/menu_bar.rs @@ -390,8 +390,8 @@ where }, shadow: Default::default(), }; - let path_color = styling.path; - renderer.fill_quad(path_quad, path_color); + + renderer.fill_quad(path_quad, styling.path); } } diff --git a/src/widget/menu/menu_inner.rs b/src/widget/menu/menu_inner.rs index 74c44606..e0d22757 100644 --- a/src/widget/menu/menu_inner.rs +++ b/src/widget/menu/menu_inner.rs @@ -709,8 +709,8 @@ where }, shadow: Shadow::default(), }; - let path_color = styling.path; - r.fill_quad(path_quad, path_color); + + r.fill_quad(path_quad, styling.path); } // draw item From 752662eedf584fc7f12da95ceabeaf7b36b57c75 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 21 May 2024 04:36:32 +0200 Subject: [PATCH 09/46] chore: ignore doc tests for widget module --- src/widget/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/mod.rs b/src/widget/mod.rs index e0745dce..280ef26b 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -12,7 +12,7 @@ //! Modules may contain additional functions for constructing different variations of a widget. //! Each module will typically have one widget with the same name as the module, which will be re-exported here. //! -//! ```no_run +//! ```no_run,ignore //! use cosmic::prelude::*; //! use cosmic::{cosmic_theme, theme, widget}; //! @@ -34,13 +34,13 @@ //! //! Widgets may borrow data from your application struct, and should do so to avoid allocating. //! -//! ```no_run +//! ```no_run,ignore //! let text = widget::text::body(&self.cached_text); //! ``` //! //! Use the [`cosmic::Apply`](crate::Apply) trait to embed widgets into other widgets which accept them. //! -//! ```no_run +//! ```no_run,ignore //! let button = widget::icon::from_name("printer-symbolic") //! .apply(widget::button::icon) //! .on_press(Message::Print); From 0607161276805c66b8c2b382bbc2f83618d18e28 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 21 May 2024 05:12:41 +0200 Subject: [PATCH 10/46] improv: messages now automatically convert into `cosmic::app::Message` --- examples/open-dialog/src/main.rs | 6 ++---- src/command/mod.rs | 14 ++++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/open-dialog/src/main.rs b/examples/open-dialog/src/main.rs index 8cc01b85..c51d2f6c 100644 --- a/examples/open-dialog/src/main.rs +++ b/examples/open-dialog/src/main.rs @@ -139,8 +139,7 @@ impl cosmic::Application for App { // Send this back to the application. Message::FileRead(url, contents) - }) - .map(Into::into); + }); } // Creates a new open dialog. @@ -167,8 +166,7 @@ impl cosmic::Application for App { Err(why) => Message::OpenError(Arc::new(why)), } - }) - .map(Into::into); + }); } // Displays an error in the application's warning bar. diff --git a/src/command/mod.rs b/src/command/mod.rs index 24776e43..10e32a28 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -16,18 +16,20 @@ use iced_runtime::command::Action; use std::future::Future; /// Yields a command which contains a batch of commands. -pub fn batch(commands: impl IntoIterator>) -> Command { - Command::batch(commands) +pub fn batch, Y: 'static>( + commands: impl IntoIterator>, +) -> Command { + Command::batch(commands).map(Into::into) } /// Yields a command which will run the future on the runtime executor. -pub fn future(future: impl Future + Send + 'static) -> Command { - Command::single(Action::Future(Box::pin(future))) +pub fn future, Y>(future: impl Future + Send + 'static) -> Command { + Command::single(Action::Future(Box::pin(async move { future.await.into() }))) } /// Yields a command which will return a message. -pub fn message(message: M) -> Command { - future(async move { message }) +pub fn message, Y>(message: X) -> Command { + future(async move { message.into() }) } /// Initiates a window drag. From 9ce3a517280691d356b454071cf4705fa7ac6b2c Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Mon, 20 May 2024 22:04:13 -0700 Subject: [PATCH 11/46] fix: text input attempting to subtract with overflow --- src/widget/text_input/input.rs | 42 ++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/widget/text_input/input.rs b/src/widget/text_input/input.rs index b8c0c839..5bf4f27a 100644 --- a/src/widget/text_input/input.rs +++ b/src/widget/text_input/input.rs @@ -729,28 +729,30 @@ where } } - let index = tree.children.len() - 1; - if let (Some(trailing_icon), Some(tree)) = - (self.trailing_icon.as_mut(), tree.children.get_mut(index)) - { - let children = text_layout.children(); - trailing_icon_layout = Some(children.last().unwrap()); + if tree.children.len() > 0 { + let index = tree.children.len() - 1; + if let (Some(trailing_icon), Some(tree)) = + (self.trailing_icon.as_mut(), tree.children.get_mut(index)) + { + let children = text_layout.children(); + trailing_icon_layout = Some(children.last().unwrap()); - if let Some(trailing_layout) = trailing_icon_layout { - if cursor_position.is_over(trailing_layout.bounds()) { - let res = trailing_icon.as_widget_mut().on_event( - tree, - event.clone(), - trailing_layout, - cursor_position, - renderer, - clipboard, - shell, - viewport, - ); + if let Some(trailing_layout) = trailing_icon_layout { + if cursor_position.is_over(trailing_layout.bounds()) { + let res = trailing_icon.as_widget_mut().on_event( + tree, + event.clone(), + trailing_layout, + cursor_position, + renderer, + clipboard, + shell, + viewport, + ); - if res == event::Status::Captured { - return res; + if res == event::Status::Captured { + return res; + } } } } From b8266b6100b3231f2492796c4e890e0169b227c0 Mon Sep 17 00:00:00 2001 From: Victoria Brekenfeld <4404502+Drakulix@users.noreply.github.com> Date: Tue, 21 May 2024 16:19:57 +0200 Subject: [PATCH 12/46] improv(app): drop setting WGPU_POWER_PREF --- src/app/mod.rs | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 408a712b..03ba86a6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -155,40 +155,11 @@ pub(crate) fn iced_settings( /// /// Returns error on application failure. pub fn run(settings: Settings, flags: App::Flags) -> iced::Result { - #[cfg(feature = "wgpu")] - wgpu_power_pref(); - let settings = iced_settings::(settings, flags); cosmic::Cosmic::::run(settings) } -/// Default to rendering the application with the low power GPU preference. -#[cfg(feature = "wgpu")] -fn wgpu_power_pref() { - fn is_desktop() -> bool { - let chassis = std::fs::read_to_string("/sys/class/dmi/id/chassis_type").unwrap_or_default(); - - chassis.trim() == "3" - } - - // Ignore if the system is a desktop. - if is_desktop() { - return; - } - - // Ignore if requested to run on NVIDIA GPU - if std::env::var("__NV_PRIME_RENDER_OFFLOAD").ok().as_deref() == Some("1") { - return; - } - - #[allow(clippy::items_after_statements)] - const VAR: &str = "WGPU_POWER_PREF"; - if std::env::var(VAR).is_err() { - std::env::set_var(VAR, "low"); - } -} - #[cfg(feature = "single-instance")] #[derive(Debug, Clone)] pub struct DbusActivationMessage> { From e302e9505c65e8e4cd9a765d8f507602f441692e Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 21 May 2024 09:55:12 -0400 Subject: [PATCH 13/46] fix(theme): include alpha value in to_hex --- cosmic-theme/src/output/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index 31b0f773..c265c88a 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -42,5 +42,8 @@ impl Theme { pub fn to_hex(c: Srgba) -> String { let c_u8: Rgba = c.into_format(); - format!("{:02x}{:02x}{:02x}", c_u8.red, c_u8.green, c_u8.blue) + format!( + "{:02x}{:02x}{:02x}{:02x}", + c_u8.red, c_u8.green, c_u8.blue, c_u8.alpha + ) } From 2fc4184636e1f5c6a8d26c541a85a367a3878854 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 21 May 2024 13:58:11 -0400 Subject: [PATCH 14/46] fix: window & headerbar radius --- src/theme/style/iced.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/theme/style/iced.rs b/src/theme/style/iced.rs index 94dee4c5..6f3388c4 100644 --- a/src/theme/style/iced.rs +++ b/src/theme/style/iced.rs @@ -452,8 +452,8 @@ impl container::StyleSheet for Theme { radius: [ cosmic.corner_radii.radius_0[0], cosmic.corner_radii.radius_0[1], - cosmic.corner_radii.radius_xs[2], - cosmic.corner_radii.radius_xs[3], + cosmic.corner_radii.radius_s[2], + cosmic.corner_radii.radius_s[3], ] .into(), ..Default::default() @@ -494,8 +494,8 @@ impl container::StyleSheet for Theme { background: Some(iced::Background::Color(cosmic.background.base.into())), border: Border { radius: [ - cosmic.corner_radii.radius_xs[0], - cosmic.corner_radii.radius_xs[1], + cosmic.corner_radii.radius_s[0], + cosmic.corner_radii.radius_s[1], cosmic.corner_radii.radius_0[2], cosmic.corner_radii.radius_0[3], ] From 428dafe37cced00cab0afa00ddc5f6c8ebccd428 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Wed, 22 May 2024 13:21:01 -0400 Subject: [PATCH 15/46] fix: retry portal when it fails, and only apply theme changes when there is a relevant change --- src/app/cosmic.rs | 9 ++++ src/theme/portal.rs | 120 +++++++++++++++++++++++++------------------- 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 1560c56f..bdec9233 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -156,6 +156,7 @@ where } } + #[allow(clippy::too_many_lines)] fn subscription(&self) -> Subscription { let window_events = listen_with(|event, _| { match event { @@ -429,6 +430,11 @@ impl Cosmic { } Message::SystemThemeChange(keys, theme) => { + let cur_is_dark = THEME.with(|t| t.borrow().theme_type.is_dark()); + // Ignore updates if the current theme mode does not match. + if cur_is_dark != theme.cosmic().is_dark { + return iced::Command::none(); + } let cmd = self.app.system_theme_update(&keys, theme.cosmic()); // Record the last-known system theme in event that the current theme is custom. self.app.core_mut().system_theme = theme.clone(); @@ -472,6 +478,9 @@ impl Cosmic { }; } Message::SystemThemeModeChange(keys, mode) => { + if !keys.contains(&"is_dark") { + return iced::Command::none(); + } if THEME.with(|t| match t.borrow().theme_type { ThemeType::System { theme: _, diff --git a/src/theme/portal.rs b/src/theme/portal.rs index 277abd7b..b0fc5f84 100644 --- a/src/theme/portal.rs +++ b/src/theme/portal.rs @@ -1,7 +1,6 @@ use ashpd::desktop::settings::{ColorScheme, Contrast}; use ashpd::desktop::Color; use iced::futures::{self, select, FutureExt, SinkExt, StreamExt}; -use iced::Subscription; use iced_futures::subscription; use tracing::error; @@ -15,68 +14,83 @@ pub enum Desktop { pub fn desktop_settings() -> iced_futures::Subscription { subscription::channel(std::any::TypeId::of::(), 10, |mut tx| { async move { - let Ok(settings) = ashpd::desktop::settings::Settings::new().await else { - // wait forever - error!("Failed to create the settings proxy"); - futures::future::pending::<()>().await; - unreachable!() - }; - - match settings.color_scheme().await { - Ok(color_scheme) => { - let _ = tx.send(Desktop::ColorScheme(color_scheme)).await; - } - Err(err) => error!("Failed to get the color scheme {err:?}"), - }; - match settings.contrast().await { - Ok(contrast) => { - let _ = tx.send(Desktop::Contrast(contrast)).await; - } - Err(err) => error!("Failed to get the contrast {err:?}"), - }; - - let mut color_scheme_stream = settings.receive_color_scheme_changed().await.ok(); - if color_scheme_stream.is_none() { - error!("Failed to receive color scheme changes"); - } - - let mut contrast_stream = settings.receive_contrast_changed().await.ok(); - if contrast_stream.is_none() { - error!("Failed to receive contrast changes"); - } - + let mut attempts = 0; loop { - let next_color_scheme = async { - if let Some(s) = color_scheme_stream.as_mut() { - return s.next().await; + let Ok(settings) = ashpd::desktop::settings::Settings::new().await else { + error!("Failed to create the settings proxy"); + #[cfg(feature = "tokio")] + ::tokio::time::sleep(::tokio::time::Duration::from_secs(2_u64.pow(attempts))) + .await; + #[cfg(not(feature = "tokio"))] + { + pending::<()>().await; + unreachable!(); } - futures::future::pending().await + attempts += 1; + continue; }; - let next_contrast = async { - if let Some(s) = contrast_stream.as_mut() { - return s.next().await; + match settings.color_scheme().await { + Ok(color_scheme) => { + let _ = tx.send(Desktop::ColorScheme(color_scheme)).await; } - futures::future::pending().await + Err(err) => error!("Failed to get the color scheme {err:?}"), + }; + match settings.contrast().await { + Ok(contrast) => { + let _ = tx.send(Desktop::Contrast(contrast)).await; + } + Err(err) => error!("Failed to get the contrast {err:?}"), }; - select! { - s = next_color_scheme.fuse() => { - if let Some(s) = s { - _ = tx.send(Desktop::ColorScheme(s)).await; - } else { - color_scheme_stream = None; + let mut color_scheme_stream = settings.receive_color_scheme_changed().await.ok(); + if color_scheme_stream.is_none() { + error!("Failed to receive color scheme changes"); + } + + let mut contrast_stream = settings.receive_contrast_changed().await.ok(); + if contrast_stream.is_none() { + error!("Failed to receive contrast changes"); + } + + loop { + if color_scheme_stream.is_none() && contrast_stream.is_none() { + break; + } + let next_color_scheme = async { + if let Some(s) = color_scheme_stream.as_mut() { + return s.next().await; } - }, + futures::future::pending().await + }; - c = next_contrast.fuse() => { - if let Some(c) = c { - _ = tx.send(Desktop::Contrast(c)).await; - } else { - contrast_stream = None; + let next_contrast = async { + if let Some(s) = contrast_stream.as_mut() { + return s.next().await; } - } - }; + futures::future::pending().await + }; + + select! { + s = next_color_scheme.fuse() => { + if let Some(s) = s { + _ = tx.send(Desktop::ColorScheme(s)).await; + } else { + color_scheme_stream = None; + } + }, + + c = next_contrast.fuse() => { + if let Some(c) = c { + _ = tx.send(Desktop::Contrast(c)).await; + } else { + contrast_stream = None; + } + } + }; + // Reset the attempts counter if we successfully received a change + attempts = 0; + } } } }) From 8b0bb6a677ef7dfc02f97e1f579308cf3c18eefd Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Fri, 24 May 2024 20:40:18 -0400 Subject: [PATCH 16/46] fix(theme): gtk3 css doesn't allow hex colors with alpha --- cosmic-theme/src/output/gtk4_output.rs | 142 ++++++++++++------------- cosmic-theme/src/output/mod.rs | 8 ++ 2 files changed, 79 insertions(+), 71 deletions(-) diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index db21b8d1..21aab991 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -6,7 +6,7 @@ use std::{ num::NonZeroUsize, }; -use super::{to_hex, OutputError}; +use super::{to_rgba, OutputError}; impl Theme { #[must_use] @@ -24,50 +24,50 @@ impl Theme { .. } = self; - let window_bg = to_hex(background.base); - let window_fg = to_hex(background.on); + let window_bg = to_rgba(background.base); + let window_fg = to_rgba(background.on); - let view_bg = to_hex(primary.base); - let view_fg = to_hex(primary.on); + let view_bg = to_rgba(primary.base); + let view_fg = to_rgba(primary.on); - let headerbar_bg = to_hex(background.base); - let headerbar_fg = to_hex(background.on); - let headerbar_border_color = to_hex(background.divider); + let headerbar_bg = to_rgba(background.base); + let headerbar_fg = to_rgba(background.on); + let headerbar_border_color = to_rgba(background.divider); - let sidebar_bg = to_hex(primary.base); - let sidebar_fg = to_hex(primary.on); - let sidebar_shade = to_hex(if self.is_dark { + let sidebar_bg = to_rgba(primary.base); + let sidebar_fg = to_rgba(primary.on); + let sidebar_shade = to_rgba(if self.is_dark { Rgba::new(0.0, 0.0, 0.0, 0.08) } else { Rgba::new(0.0, 0.0, 0.0, 0.32) }); let backdrop_overlay = Srgba::new(1.0, 1.0, 1.0, if self.is_dark { 0.08 } else { 0.32 }); - let sidebar_backdrop = to_hex(over(backdrop_overlay, primary.base)); + let sidebar_backdrop = to_rgba(over(backdrop_overlay, primary.base)); - let secondary_sidebar_bg = to_hex(secondary.base); - let secondary_sidebar_fg = to_hex(secondary.on); - let secondary_sidebar_shade = to_hex(if self.is_dark { + let secondary_sidebar_bg = to_rgba(secondary.base); + let secondary_sidebar_fg = to_rgba(secondary.on); + let secondary_sidebar_shade = to_rgba(if self.is_dark { Rgba::new(0.0, 0.0, 0.0, 0.08) } else { Rgba::new(0.0, 0.0, 0.0, 0.32) }); - let secondary_sidebar_backdrop = to_hex(over(backdrop_overlay, secondary.base)); + let secondary_sidebar_backdrop = to_rgba(over(backdrop_overlay, secondary.base)); - let headerbar_backdrop = to_hex(background.base); + let headerbar_backdrop = to_rgba(background.base); - let card_bg = to_hex(background.component.base); - let card_fg = to_hex(background.component.on); + let card_bg = to_rgba(background.component.base); + let card_fg = to_rgba(background.component.on); - let thumbnail_bg = to_hex(background.component.base); - let thumbnail_fg = to_hex(background.component.on); + let thumbnail_bg = to_rgba(background.component.base); + let thumbnail_fg = to_rgba(background.component.on); - let dialog_bg = to_hex(primary.base); - let dialog_fg = to_hex(primary.on); + let dialog_bg = to_rgba(primary.base); + let dialog_fg = to_rgba(primary.on); - let popover_bg = to_hex(background.component.base); - let popover_fg = to_hex(background.component.on); + let popover_bg = to_rgba(background.component.base); + let popover_fg = to_rgba(background.component.on); - let shade = to_hex(if self.is_dark { + let shade = to_rgba(if self.is_dark { Rgba::new(0.0, 0.0, 0.0, 0.32) } else { Rgba::new(0.0, 0.0, 0.0, 0.08) @@ -75,44 +75,44 @@ impl Theme { let mut inverted_bg_divider = background.base; inverted_bg_divider.alpha = 0.5; - let scrollbar_outline = to_hex(inverted_bg_divider); + let scrollbar_outline = to_rgba(inverted_bg_divider); let mut css = format! {r#" -@define-color window_bg_color #{window_bg}; -@define-color window_fg_color #{window_fg}; +@define-color window_bg_color {window_bg}; +@define-color window_fg_color {window_fg}; -@define-color view_bg_color #{view_bg}; -@define-color view_fg_color #{view_fg}; +@define-color view_bg_color {view_bg}; +@define-color view_fg_color {view_fg}; -@define-color headerbar_bg_color #{headerbar_bg}; -@define-color headerbar_fg_color #{headerbar_fg}; -@define-color headerbar_border_color_color #{headerbar_border_color}; -@define-color headerbar_backdrop_color #{headerbar_backdrop}; +@define-color headerbar_bg_color {headerbar_bg}; +@define-color headerbar_fg_color {headerbar_fg}; +@define-color headerbar_border_color_color {headerbar_border_color}; +@define-color headerbar_backdrop_color {headerbar_backdrop}; -@define-color sidebar_bg_color #{sidebar_bg}; -@define-color sidebar_fg_color #{sidebar_fg}; -@define-color sidebar_shade_color #{sidebar_shade}; -@define-color sidebar_backdrop_color #{sidebar_backdrop}; +@define-color sidebar_bg_color {sidebar_bg}; +@define-color sidebar_fg_color {sidebar_fg}; +@define-color sidebar_shade_color {sidebar_shade}; +@define-color sidebar_backdrop_color {sidebar_backdrop}; -@define-color secondary_sidebar_bg_color #{secondary_sidebar_bg}; -@define-color secondary_sidebar_fg_color #{secondary_sidebar_fg}; -@define-color secondary_sidebar_shade_color #{secondary_sidebar_shade}; -@define-color secondary_sidebar_backdrop_color #{secondary_sidebar_backdrop}; +@define-color secondary_sidebar_bg_color {secondary_sidebar_bg}; +@define-color secondary_sidebar_fg_color {secondary_sidebar_fg}; +@define-color secondary_sidebar_shade_color {secondary_sidebar_shade}; +@define-color secondary_sidebar_backdrop_color {secondary_sidebar_backdrop}; -@define-color card_bg_color #{card_bg}; -@define-color card_fg_color #{card_fg}; +@define-color card_bg_color {card_bg}; +@define-color card_fg_color {card_fg}; -@define-color thumbnail_bg_color #{thumbnail_bg}; -@define-color thumbnail_fg_color #{thumbnail_fg}; +@define-color thumbnail_bg_color {thumbnail_bg}; +@define-color thumbnail_fg_color {thumbnail_fg}; -@define-color dialog_bg_color #{dialog_bg}; -@define-color dialog_fg_color #{dialog_fg}; +@define-color dialog_bg_color {dialog_bg}; +@define-color dialog_fg_color {dialog_fg}; -@define-color popover_bg_color #{popover_bg}; -@define-color popover_fg_color #{popover_fg}; +@define-color popover_bg_color {popover_bg}; +@define-color popover_fg_color {popover_fg}; -@define-color shade_color #{shade}; -@define-color scrollbar_outline_color #{scrollbar_outline}; +@define-color shade_color {shade}; +@define-color scrollbar_outline_color {scrollbar_outline}; "#}; css.push_str(&component_gtk4_css("accent", accent)); @@ -130,10 +130,10 @@ impl Theme { css.push_str(&color_css("purple", palette.ext_purple)); let neutral_steps = steps(palette.neutral_5, NonZeroUsize::new(10).unwrap()); for (i, c) in neutral_steps[..5].iter().enumerate() { - css.push_str(&format!("@define-color light_{i} #{};\n", to_hex(*c))); + css.push_str(&format!("@define-color light_{i} {};\n", to_rgba(*c))); } for (i, c) in neutral_steps[5..].iter().enumerate() { - css.push_str(&format!("@define-color dark_{i} #{};\n", to_hex(*c))); + css.push_str(&format!("@define-color dark_{i} {};\n", to_rgba(*c))); } css } @@ -232,13 +232,13 @@ impl Theme { fn component_gtk4_css(prefix: &str, c: &Component) -> String { format!( r#" -@define-color {prefix}_color #{}; -@define-color {prefix}_bg_color #{}; -@define-color {prefix}_fg_color #{}; +@define-color {prefix}_color {}; +@define-color {prefix}_bg_color {}; +@define-color {prefix}_fg_color {}; "#, - to_hex(c.base), - to_hex(c.base), - to_hex(c.on), + to_rgba(c.base), + to_rgba(c.base), + to_rgba(c.on), ) } @@ -248,17 +248,17 @@ fn color_css(prefix: &str, c_3: Srgba) -> String { let c_1: Srgba = oklch.lighten(0.2).into_color(); let c_4: Srgba = oklch.darken(0.1).into_color(); let c_5: Srgba = oklch.darken(0.2).into_color(); - let c_1 = to_hex(c_1); - let c_2 = to_hex(c_2); - let c_3 = to_hex(c_3); - let c_4 = to_hex(c_4); - let c_5 = to_hex(c_5); + let c_1 = to_rgba(c_1); + let c_2 = to_rgba(c_2); + let c_3 = to_rgba(c_3); + let c_4 = to_rgba(c_4); + let c_5 = to_rgba(c_5); format! {r#" -@define-color {prefix}_1 #{c_1}; -@define-color {prefix}_2 #{c_2}; -@define-color {prefix}_3 #{c_3}; -@define-color {prefix}_4 #{c_4}; -@define-color {prefix}_5 #{c_5}; +@define-color {prefix}_1 {c_1}; +@define-color {prefix}_2 {c_2}; +@define-color {prefix}_3 {c_3}; +@define-color {prefix}_4 {c_4}; +@define-color {prefix}_5 {c_5}; "#} } diff --git a/cosmic-theme/src/output/mod.rs b/cosmic-theme/src/output/mod.rs index c265c88a..7344fabe 100644 --- a/cosmic-theme/src/output/mod.rs +++ b/cosmic-theme/src/output/mod.rs @@ -47,3 +47,11 @@ pub fn to_hex(c: Srgba) -> String { c_u8.red, c_u8.green, c_u8.blue, c_u8.alpha ) } + +pub fn to_rgba(c: Srgba) -> String { + let c_u8: Rgba = c.into_format(); + format!( + "rgba({}, {}, {}, {:1.2})", + c_u8.red, c_u8.green, c_u8.blue, c.alpha + ) +} From d559403f7fb01b8d7da1803a27fa18b43df37a92 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 27 May 2024 22:37:48 +0200 Subject: [PATCH 17/46] fix(image-button): fix rendering on wgpu --- src/widget/button/widget.rs | 196 ++++++++++++++++++++++-------------- 1 file changed, 122 insertions(+), 74 deletions(-) diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 9c079008..8687b033 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -21,6 +21,7 @@ use iced_core::{ Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; use iced_renderer::core::widget::{operation, OperationOutputWrapper}; +use iced_wgpu::graphics::Mesh; use crate::theme::THEME; @@ -409,77 +410,105 @@ impl<'a, Message: 'a + Clone> Widget on_remove, } = &self.variant { - let selection_background = theme.selection_background(); + let mut parent_bounds = bounds; + parent_bounds.y -= 8.0; + parent_bounds.width += 16.0; + parent_bounds.height += 16.0; - let c_rad = THEME.with(|t| t.borrow().cosmic().corner_radii); + renderer.with_layer(parent_bounds, |renderer| { + let selection_background = theme.selection_background(); - if self.selected { - renderer.fill_quad( - Quad { - bounds: Rectangle { - width: 24.0, - height: 20.0, - x: bounds.x + styling.border_width, - y: bounds.y + (bounds.height - 20.0 - styling.border_width), + let c_rad = THEME.with(|t| t.borrow().cosmic().corner_radii); + + // NOTE: Workaround to round the border of the unselected, unhovered image. + if !self.selected && !is_mouse_over { + let mut bounds = bounds; + bounds.x -= 2.0; + bounds.y -= 2.0; + bounds.width += 4.0; + bounds.height += 4.0; + renderer.fill_quad( + renderer::Quad { + bounds, + border: Border { + width: 2.0, + color: crate::theme::active().current_container().base.into(), + radius: 9.0.into(), + }, + shadow: Shadow::default(), }, - border: Border { - radius: [ - c_rad.radius_0[0], - c_rad.radius_s[1], - c_rad.radius_0[2], - c_rad.radius_s[3], - ] - .into(), - ..Default::default() + Color::TRANSPARENT, + ); + } + + if self.selected { + renderer.fill_quad( + Quad { + bounds: Rectangle { + width: 24.0, + height: 20.0, + x: bounds.x + styling.border_width, + y: bounds.y + (bounds.height - 20.0 - styling.border_width), + }, + border: Border { + radius: [ + c_rad.radius_0[0], + c_rad.radius_s[1], + c_rad.radius_0[2], + c_rad.radius_s[3], + ] + .into(), + ..Default::default() + }, + shadow: Shadow::default(), }, - shadow: Shadow::default(), - }, - selection_background, - ); + selection_background, + ); - iced_core::svg::Renderer::draw( - renderer, - crate::widget::common::object_select().clone(), - Some(icon_color), - Rectangle { - width: 16.0, - height: 16.0, - x: bounds.x + 5.0 + styling.border_width, - y: bounds.y + (bounds.height - 18.0 - styling.border_width), - }, - ); - } + iced_core::svg::Renderer::draw( + renderer, + crate::widget::common::object_select().clone(), + Some(icon_color), + Rectangle { + width: 16.0, + height: 16.0, + x: bounds.x + 5.0 + styling.border_width, + y: bounds.y + (bounds.height - 18.0 - styling.border_width), + }, + ); + } - if on_remove.is_some() { - if let Some(position) = cursor.position() { - if bounds.contains(position) { - let bounds = removal_bounds(layout.bounds(), 4.0); - renderer.fill_quad( - renderer::Quad { - bounds, - shadow: Shadow::default(), - border: Border { - radius: c_rad.radius_m.into(), - ..Default::default() + if on_remove.is_some() { + if let Some(position) = cursor.position() { + if bounds.contains(position) { + let bounds = removal_bounds(layout.bounds(), 4.0); + renderer.fill_quad( + renderer::Quad { + bounds, + shadow: Shadow::default(), + border: Border { + radius: c_rad.radius_m.into(), + ..Default::default() + }, }, - }, - selection_background, - ); + selection_background, + ); - iced_core::svg::Renderer::draw( - renderer, - close_icon.clone(), - Some(icon_color), - Rectangle { - width: 16.0, - height: 16.0, - x: bounds.x + 4.0, - y: bounds.y + 4.0, - }, - ); + iced_core::svg::Renderer::draw( + renderer, + close_icon.clone(), + Some(icon_color), + Rectangle { + width: 16.0, + height: 16.0, + x: bounds.x + 4.0, + y: bounds.y + 4.0, + }, + ); + } } } - } + }); } } @@ -525,7 +554,7 @@ impl<'a, Message: 'a + Clone> Widget let child_tree = self .content .as_widget() - .a11y_nodes(child_layout, &child_tree, p); + .a11y_nodes(child_layout, child_tree, p); let Rectangle { x, @@ -781,19 +810,38 @@ pub fn draw( // Then draw the button contents onto the background. draw_contents(renderer, styling); - // Finish by drawing the border above the contents. - renderer.fill_quad( - renderer::Quad { - bounds, - border: Border { - width: styling.border_width, - color: styling.border_color, - radius: styling.border_radius, + let mut clipped_bounds = bounds; + clipped_bounds.height += styling.border_width; + + renderer.with_layer(clipped_bounds, |renderer| { + // NOTE: Workaround to round the border of the hovered/selected image. + renderer.fill_quad( + renderer::Quad { + bounds, + border: Border { + width: styling.border_width, + color: crate::theme::active().current_container().base.into(), + radius: 0.0.into(), + }, + shadow: Shadow::default(), }, - shadow: Shadow::default(), - }, - Color::TRANSPARENT, - ); + Color::TRANSPARENT, + ); + + // Finish by drawing the border above the contents. + renderer.fill_quad( + renderer::Quad { + bounds, + border: Border { + width: styling.border_width, + color: styling.border_color, + radius: styling.border_radius, + }, + shadow: Shadow::default(), + }, + Color::TRANSPARENT, + ); + }); } else { draw_contents(renderer, styling); } From 375a2d127b94f5f6656b4d5e9e44704c48d36e22 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 28 May 2024 15:31:05 +0200 Subject: [PATCH 18/46] chore(button): remove unused import --- src/widget/button/widget.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widget/button/widget.rs b/src/widget/button/widget.rs index 8687b033..427a1917 100644 --- a/src/widget/button/widget.rs +++ b/src/widget/button/widget.rs @@ -21,7 +21,6 @@ use iced_core::{ Background, Clipboard, Color, Layout, Length, Padding, Point, Rectangle, Shell, Vector, Widget, }; use iced_renderer::core::widget::{operation, OperationOutputWrapper}; -use iced_wgpu::graphics::Mesh; use crate::theme::THEME; From 9f017de1fab4bb16606c6ae428bf6df2395e4c8b Mon Sep 17 00:00:00 2001 From: nabrious0 <162843960+nabrious0@users.noreply.github.com> Date: Tue, 28 May 2024 14:49:10 +0100 Subject: [PATCH 19/46] Theme GTK3/4 window controls to match COSMIC theme (#464) * Update gtk4_output.rs * get headerbar border-radius from theme --------- Co-authored-by: Ashley Wulber <48420062+wash2@users.noreply.github.com> --- cosmic-theme/src/output/gtk4_output.rs | 74 ++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/cosmic-theme/src/output/gtk4_output.rs b/cosmic-theme/src/output/gtk4_output.rs index 21aab991..e9cbb508 100644 --- a/cosmic-theme/src/output/gtk4_output.rs +++ b/cosmic-theme/src/output/gtk4_output.rs @@ -73,6 +73,12 @@ impl Theme { Rgba::new(0.0, 0.0, 0.0, 0.08) }); + let window_control_hover_bg = to_rgba(if self.is_dark { + Rgba::new(255.0, 255.0, 255.0, 0.12) + } else { + Rgba::new(0.0, 0.0, 0.0, 0.12) + }); + let corner_radius = self.corner_radii.radius_s[0]; let mut inverted_bg_divider = background.base; inverted_bg_divider.alpha = 0.5; let scrollbar_outline = to_rgba(inverted_bg_divider); @@ -113,6 +119,74 @@ impl Theme { @define-color shade_color {shade}; @define-color scrollbar_outline_color {scrollbar_outline}; + +.close, .maximize, .minimize {{ + background: transparent; +}} +.close:not(:hover) > image, +.maximize:not(:hover) > image, +.minimize:not(:hover) > image {{ + background: transparent; +}} +.close > image, +.maximize > image, +.minimize > image {{ + transition: 0; + color: @accent_bg_color; + border-radius: 100%; +}} +.close:backdrop > image, +.maximize:backdrop > image, +.minimize:backdrop > image {{ + filter: grayscale(1) brightness(1.7); +}} +.close:hover > image, +.maximize:hover > image, +.minimize:hover > image {{ + background: {window_control_hover_bg}; +}} +.image-button {{ + transition: 0; +}} +.image-button > image, +.image-button > box, +.popup > image, +.toggle > image, +.toggle > arrow, +.toggle > box > contents > image, +button > image, +button > widget > box > image, +stack > box > image +{{ + color: @accent_bg_color; +}} +.image-button:insensitive > image, +.image-button:insensitive > box, +.popup:insensitive > image, +.toggle:insensitive > image, +.toggle:insensitive > box > contents > image, +button:insensitive > image, +button:insensitive > widget > box > image, +stack:insensitive > box > image {{ + opacity: 0.5; +}} +.image-button:backdrop > image, +.image-button:backdrop > box, +.popup:backdrop > image, +.toggle:backdrop > image, +.toggle:backdrop > arrow, +button:backdrop > image, +button:backdrop > widget > box > image, +stack:backdrop > box > image, +.toggle:backdrop > box > contents > image {{ + filter: grayscale(1) brightness(1.7); +}} +window {{ background: @window_bg_color }} +window:not(.maximized) {{ + border-radius: {corner_radius:.0}px; +}} + +window, statuspage.view {{ background: @window_bg_color }} "#}; css.push_str(&component_gtk4_css("accent", accent)); From 8ecf1a8eddebaff0d562819d6f227e39d0e7b5a7 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 28 May 2024 13:58:29 -0600 Subject: [PATCH 20/46] fix(app) set iced.window.exit_on_close_request --- src/app/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 03ba86a6..794cb424 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -98,7 +98,7 @@ pub(crate) fn iced_settings( iced.default_font = settings.default_font; iced.default_text_size = iced::Pixels(settings.default_text_size); iced.exit_on_close_request = settings.exit_on_close; - // iced.window.exit_on_close_request = settings.exit_on_close; + iced.window.exit_on_close_request = settings.exit_on_close; iced.id = Some(App::APP_ID.to_owned()); #[cfg(all(not(feature = "wayland"), target_os = "linux"))] { From 56ff501d361bd6066dbfe36476de0580387af6f7 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Tue, 28 May 2024 19:07:58 -0400 Subject: [PATCH 21/46] fix: wayland exit_on_close --- src/app/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 794cb424..49d631ee 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -98,7 +98,11 @@ pub(crate) fn iced_settings( iced.default_font = settings.default_font; iced.default_text_size = iced::Pixels(settings.default_text_size); iced.exit_on_close_request = settings.exit_on_close; - iced.window.exit_on_close_request = settings.exit_on_close; + #[cfg(not(feature = "wayland"))] + { + let exit_on_close = settings.exit_on_close; + iced.window.exit_on_close_request = exit_on_close; + } iced.id = Some(App::APP_ID.to_owned()); #[cfg(all(not(feature = "wayland"), target_os = "linux"))] { From f0bfa87a368364fec32be33623f394bddb0530c1 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 27 May 2024 20:47:12 +0200 Subject: [PATCH 22/46] chore: remove design demo example --- examples/design-demo | 1 - justfile | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 160000 examples/design-demo diff --git a/examples/design-demo b/examples/design-demo deleted file mode 160000 index 493e17a0..00000000 --- a/examples/design-demo +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 493e17a0105c7523fb7ff5fd7221ec586ac9010f diff --git a/justfile b/justfile index dcddf7e7..0280da7c 100644 --- a/justfile +++ b/justfile @@ -1,4 +1,4 @@ -examples := 'applet application config cosmic cosmic-design-demo multi-window open-dialog' +examples := 'applet application calendar config context-menu cosmic image-button menu multi-window nav-context open-dialog' clippy_args := '-W clippy::all -W clippy::pedantic' # Check for errors and linter warnings From 6720b8277c91d05f3bd6a83d92191cba96f51873 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 27 May 2024 21:49:49 +0200 Subject: [PATCH 23/46] feat: CosmicTk::header_size density config --- Cargo.toml | 4 +- src/app/core.rs | 13 ----- src/app/cosmic.rs | 4 +- src/app/mod.rs | 6 +-- src/config/mod.rs | 102 +++++++++++++++++++++++++++++++++-- src/config/toolkit.rs | 43 --------------- src/widget/header_bar.rs | 21 ++++---- src/widget/nav_bar_toggle.rs | 4 -- 8 files changed, 118 insertions(+), 79 deletions(-) delete mode 100644 src/config/toolkit.rs diff --git a/Cargo.toml b/Cargo.toml index 8b7a2b3c..ad7570de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ desktop = ["process", "dep:freedesktop-desktop-entry", "dep:mime", "dep:shlex"] # Enables keycode serialization serde-keycode = ["iced_core/serde"] # Prevents multiple separate process instances. -single-instance = ["dep:zbus", "serde", "ron"] +single-instance = ["dep:zbus", "ron"] # smol async runtime smol = ["iced/smol", "zbus?/async-io"] tokio = [ @@ -80,7 +80,7 @@ mime = { version = "0.3.17", optional = true } nix = { version = "0.27", features = ["process"], optional = true } palette = "0.7.3" rfd = { version = "0.14.0", optional = true } -serde = { version = "1.0.180", optional = true } +serde = { version = "1.0.180", features = ["derive"]} slotmap = "1.0.6" thiserror = "1.0.44" tokio = { version = "1.24.2", optional = true } diff --git a/src/app/core.rs b/src/app/core.rs index 6594f4a7..eeb0ae22 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -73,9 +73,6 @@ pub struct Core { /// Configured theme mode pub(super) system_theme_mode: ThemeMode, - /// Libcosmic toolkit configuration. - pub(super) toolkit_config: CosmicTk, - pub(super) portal_is_dark: Option, pub(super) portal_accent: Option, @@ -123,16 +120,6 @@ impl Default for Core { }) }) .unwrap_or_default(), - toolkit_config: CosmicTk::config() - .map(|c| { - CosmicTk::get_entry(&c).unwrap_or_else(|(errors, mode)| { - for why in errors { - tracing::error!(?why, "CosmicTk config entry error"); - } - mode - }) - }) - .unwrap_or_default(), window: Window { context_title: String::new(), header_title: String::new(), diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index bdec9233..21ddf636 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -198,7 +198,7 @@ where self.app.subscription().map(super::Message::App), self.app .core() - .watch_config::(crate::config::toolkit::ID) + .watch_config::(crate::config::ID) .map(|update| { for why in update.errors { tracing::error!(?why, "cosmic toolkit config update error"); @@ -628,7 +628,7 @@ impl Cosmic { crate::icon_theme::set_default(config.icon_theme.clone()); } - self.app.core_mut().toolkit_config = config; + crate::config::COSMIC_TK.with(|tk| *tk.borrow_mut() = config); } Message::Focus(f) => { diff --git a/src/app/mod.rs b/src/app/mod.rs index 49d631ee..aa5b5183 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -84,7 +84,7 @@ pub(crate) fn iced_settings( if let Some(icon_theme) = settings.default_icon_theme { crate::icon_theme::set_default(icon_theme); } else { - crate::icon_theme::set_default(core.toolkit_config.icon_theme.clone()); + crate::icon_theme::set_default(crate::config::icon_theme()); } THEME.with(move |t| { @@ -714,11 +714,11 @@ impl ApplicationExt for App { header = header.start(toggle); } - if core.window.show_maximize && core.toolkit_config.show_maximize { + if core.window.show_maximize && crate::config::show_maximize() { header = header.on_maximize(Message::Cosmic(cosmic::Message::Maximize)); } - if core.window.show_minimize && core.toolkit_config.show_minimize { + if core.window.show_minimize && crate::config::show_minimize() { header = header.on_minimize(Message::Cosmic(cosmic::Message::Minimize)); } diff --git a/src/config/mod.rs b/src/config/mod.rs index 0dca84db..15eb0bf5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,6 +3,102 @@ //! Configurations available to libcosmic applications. -pub mod toolkit; -#[doc(inline)] -pub use toolkit::CosmicTk; +use cosmic_config::cosmic_config_derive::CosmicConfigEntry; +use cosmic_config::{Config, CosmicConfigEntry}; +use serde::{Deserialize, Serialize}; +use std::cell::RefCell; +use std::rc::Rc; + +/// ID for the `CosmicTk` config. +pub const ID: &str = "com.system76.CosmicTk"; + +thread_local! { + pub static COSMIC_TK: RefCell = RefCell::new(CosmicTk::config() + .map(|c| { + CosmicTk::get_entry(&c).unwrap_or_else(|(errors, mode)| { + for why in errors { + tracing::error!(?why, "CosmicTk config entry error"); + } + mode + }) + }) + .unwrap_or_default()) +} + +/// Apply the theme to other toolkits. +pub fn apply_theme_global() -> bool { + COSMIC_TK.with(|tk| tk.borrow().apply_theme_global) +} + +/// Show minimize button in window header. +pub fn show_minimize() -> bool { + COSMIC_TK.with(|tk| tk.borrow().show_minimize) +} + +/// Show maximize button in window header. +pub fn show_maximize() -> bool { + COSMIC_TK.with(|tk| tk.borrow().show_maximize) +} + +/// Preferred icon theme. +pub fn icon_theme() -> String { + COSMIC_TK.with(|tk| tk.borrow().icon_theme.clone()) +} + +/// Density of CSD/SSD header bars. +pub fn header_size() -> Density { + COSMIC_TK.with(|tk| tk.borrow().header_size) +} + +/// Interface density. +pub fn interface_density() -> Density { + COSMIC_TK.with(|tk| tk.borrow().interface_density) +} + +#[derive(Clone, CosmicConfigEntry, Debug, Eq, PartialEq)] +#[version = 1] +pub struct CosmicTk { + /// Apply the theme to other toolkits. + pub apply_theme_global: bool, + + /// Show minimize button in window header. + pub show_minimize: bool, + + /// Show maximize button in window header. + pub show_maximize: bool, + + /// Preferred icon theme. + pub icon_theme: String, + + /// Density of CSD/SSD header bars. + pub header_size: Density, + + /// Interface density. + pub interface_density: Density, +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)] +pub enum Density { + Compact, + #[default] + Standard, +} + +impl Default for CosmicTk { + fn default() -> Self { + Self { + apply_theme_global: false, + show_minimize: true, + show_maximize: true, + icon_theme: String::from("Cosmic"), + header_size: Density::Standard, + interface_density: Density::Standard, + } + } +} + +impl CosmicTk { + pub fn config() -> Result { + Config::new(ID, Self::VERSION) + } +} diff --git a/src/config/toolkit.rs b/src/config/toolkit.rs deleted file mode 100644 index 2f738cb9..00000000 --- a/src/config/toolkit.rs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2024 System76 -// SPDX-License-Identifier: MPL-2.0 - -//! Configurations for the libcosmic toolkit. - -use cosmic_config::cosmic_config_derive::CosmicConfigEntry; -use cosmic_config::{Config, CosmicConfigEntry}; - -/// ID for the `CosmicTk` config. -pub const ID: &str = "com.system76.CosmicTk"; - -#[derive(Clone, CosmicConfigEntry, Debug, Eq, PartialEq)] -#[version = 1] -pub struct CosmicTk { - /// Show minimize button in window header. - pub show_minimize: bool, - - /// Show maximize button in window header. - pub show_maximize: bool, - - /// Preferred icon theme. - pub icon_theme: String, - - /// Apply the theme to other toolkits. - pub apply_theme_global: bool, -} - -impl Default for CosmicTk { - fn default() -> Self { - Self { - show_minimize: true, - show_maximize: true, - icon_theme: String::from("Cosmic"), - apply_theme_global: false, - } - } -} - -impl CosmicTk { - pub fn config() -> Result { - Config::new(ID, Self::VERSION) - } -} diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 6d47d2d5..af45fb09 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -4,7 +4,7 @@ use crate::{ext::CollectionWidget, widget, Element}; use apply::Apply; use derive_setters::Setters; -use iced::Length; +use iced::{Length, Padding}; use iced_core::{widget::tree, Widget}; use std::borrow::Cow; @@ -99,7 +99,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { #[must_use] pub fn build(self) -> HeaderBarWidget<'a, Message> { HeaderBarWidget { - header_bar_inner: self.into_element(), + header_bar_inner: self.view(), } } } @@ -253,7 +253,7 @@ impl<'a, Message: Clone + 'static> Widget HeaderBar<'a, Message> { /// Converts the headerbar builder into an Iced element. - pub fn into_element(mut self) -> Element<'a, Message> { + pub fn view(mut self) -> Element<'a, Message> { // Take ownership of the regions to be packed. let start = std::mem::take(&mut self.start); let center = std::mem::take(&mut self.center); @@ -263,6 +263,11 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { end.push(widget::horizontal_space(Length::Fixed(12.0)).into()); end.push(self.window_controls()); + let (height, padding) = match crate::config::header_size() { + crate::config::Density::Compact => (36.0, 2.0), + crate::config::Density::Standard => (48.0, 8.0), + }; + // Creates the headerbar widget. let mut widget = widget::row::with_capacity(4) // If elements exist in the start region, append them here. @@ -295,9 +300,9 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .width(Length::Shrink), ) .align_items(iced::Alignment::Center) - .height(Length::Fixed(50.0)) - .padding(8) - .spacing(8) + .height(Length::Fixed(height)) + .padding(padding) + .spacing(padding) .apply(widget::container) .style(crate::theme::Container::HeaderBar { focused: self.focused, @@ -326,9 +331,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { let mut title = Cow::default(); std::mem::swap(&mut title, &mut self.title); - widget::text(title) - .size(16) - .font(crate::font::FONT_SEMIBOLD) + widget::text::heading(title) .apply(widget::container) .center_x() .center_y() diff --git a/src/widget/nav_bar_toggle.rs b/src/widget/nav_bar_toggle.rs index cd1ec67f..e39144ca 100644 --- a/src/widget/nav_bar_toggle.rs +++ b/src/widget/nav_bar_toggle.rs @@ -42,13 +42,9 @@ impl<'a, Message: 'static + Clone> From> for Element<'a, M }; widget::button::icon(icon) - .padding([8, 16, 8, 16]) .on_press_maybe(nav_bar_toggle.on_toggle) .selected(nav_bar_toggle.selected) .style(nav_bar_toggle.style) - .apply(widget::container) - .center_y() - .height(Length::Fill) .into() } } From 057ee5f0866d3b9bd281074a4e85d9381898e304 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 28 May 2024 07:46:28 +0200 Subject: [PATCH 24/46] feat(headerbar): add field to configure density --- src/widget/header_bar.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index af45fb09..21701d40 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -1,6 +1,7 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 +use crate::config::Density; use crate::{ext::CollectionWidget, widget, Element}; use apply::Apply; use derive_setters::Setters; @@ -20,6 +21,7 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { start: Vec::new(), center: Vec::new(), end: Vec::new(), + density: None, focused: false, } } @@ -50,9 +52,6 @@ pub struct HeaderBar<'a, Message> { #[setters(strip_option)] on_right_click: Option, - /// Focused state of the window - focused: bool, - /// Elements packed at the start of the headerbar. #[setters(skip)] start: Vec>, @@ -64,6 +63,13 @@ pub struct HeaderBar<'a, Message> { /// Elements packed at the end of the headerbar. #[setters(skip)] end: Vec>, + + /// Controls the density of the headerbar. + #[setters(strip_option)] + density: Option, + + /// Focused state of the window + focused: bool, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -263,7 +269,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { end.push(widget::horizontal_space(Length::Fixed(12.0)).into()); end.push(self.window_controls()); - let (height, padding) = match crate::config::header_size() { + let (height, padding) = match self.density.unwrap_or_else(|| crate::config::header_size()) { crate::config::Density::Compact => (36.0, 2.0), crate::config::Density::Standard => (48.0, 8.0), }; From 8cea42be3ee3a81bd737504b2dab4e622a846c3c Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 29 May 2024 21:52:51 -0600 Subject: [PATCH 25/46] Add a11y feature to docs --- .github/workflows/pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 322bce4f..5f3c4918 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: Build documentation - run: cargo doc --verbose --features tokio,winit + run: cargo doc --verbose --features a11y,tokio,winit - name: Deploy documentation uses: peaceiris/actions-gh-pages@v3 with: From 29b52f470fb574c5b28d9134b1aa00e803683e84 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Wed, 29 May 2024 22:07:47 -0600 Subject: [PATCH 26/46] Remove a11y feature from docs until it works. --- .github/workflows/pages.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 5f3c4918..322bce4f 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -16,7 +16,7 @@ jobs: with: submodules: recursive - name: Build documentation - run: cargo doc --verbose --features a11y,tokio,winit + run: cargo doc --verbose --features tokio,winit - name: Deploy documentation uses: peaceiris/actions-gh-pages@v3 with: From c39fd5103fbcf2b1184cf653b586ce6eecbb5792 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 29 May 2024 23:30:03 +0200 Subject: [PATCH 27/46] fix(grid): use content_size as size of widget's root node --- src/widget/grid/layout.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/widget/grid/layout.rs b/src/widget/grid/layout.rs index 9a193010..d61d13c9 100644 --- a/src/widget/grid/layout.rs +++ b/src/widget/grid/layout.rs @@ -75,7 +75,7 @@ pub fn resolve( match leaf { Ok(leaf) => leafs.push(leaf), Err(why) => { - tracing::error!(%why, "cannot add leaf node to grid"); + tracing::error!(?why, "cannot add leaf node to grid"); continue; } } @@ -134,7 +134,7 @@ pub fn resolve( let root = match root { Ok(root) => root, Err(why) => { - tracing::error!(%why, "grid root style invalid"); + tracing::error!(?why, "grid root style invalid"); return Node::new(Size::ZERO); } }; @@ -146,14 +146,14 @@ pub fn resolve( height: length(max_size.height), }, ) { - tracing::error!(%why, "grid layout did not compute"); + tracing::error!(?why, "grid layout did not compute"); return Node::new(Size::ZERO); } let grid_layout = match taffy.layout(root) { Ok(layout) => layout, Err(why) => { - tracing::error!(%why, "cannot get layout of grid"); + tracing::error!(?why, "cannot get layout of grid"); return Node::new(Size::ZERO); } }; @@ -183,8 +183,8 @@ pub fn resolve( } let grid_size = Size { - width: grid_layout.size.width, - height: grid_layout.size.height, + width: grid_layout.content_size.width, + height: grid_layout.content_size.height, }; Node::with_children(grid_size.expand(padding), nodes) From 4d8c906c5641fed825333e74cd9ec8cb9265cce0 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 29 May 2024 23:40:16 +0200 Subject: [PATCH 28/46] improv(flex-row): rewrite layout with taffy --- src/widget/flex_row/layout.rs | 173 +++++++++++++++++++++++----------- 1 file changed, 116 insertions(+), 57 deletions(-) diff --git a/src/widget/flex_row/layout.rs b/src/widget/flex_row/layout.rs index 22d679f7..df1c862f 100644 --- a/src/widget/flex_row/layout.rs +++ b/src/widget/flex_row/layout.rs @@ -4,8 +4,13 @@ use crate::{Element, Renderer}; use iced_core::layout::{Limits, Node}; use iced_core::widget::Tree; -use iced_core::{Padding, Point, Size}; +use iced_core::{Length, Padding, Point, Size}; +use taffy::geometry::Rect; +use taffy::style::{AlignItems, Dimension, Display, Style}; +use taffy::style_helpers::length; +use taffy::TaffyTree; +#[allow(clippy::too_many_lines)] pub fn resolve( renderer: &Renderer, limits: &Limits, @@ -16,76 +21,130 @@ pub fn resolve( tree: &mut [Tree], ) -> Node { let limits = limits.shrink(padding); + let max_size = limits.max(); + let mut leafs = Vec::with_capacity(items.len()); let mut nodes = Vec::with_capacity(items.len()); - let max_flex_width = limits.max().width; - let mut flex_width = 0.0f32; - let mut flex_height = 0.0f32; + let mut taffy_tree = TaffyTree::<()>::with_capacity(items.len() + 1); - let mut current_row_width = 0.0f32; - let mut current_row_height = 0.0f32; + let style = taffy::Style { + display: Display::Flex, + flex_direction: taffy::FlexDirection::Row, + flex_wrap: taffy::FlexWrap::Wrap, - let mut row_buffer = Vec::::with_capacity(8); + gap: taffy::geometry::Size { + width: length(column_spacing), + height: length(row_spacing), + }, + + min_size: taffy::geometry::Size { + width: length(max_size.width), + height: Dimension::Auto, + }, + + padding: Rect { + left: length(padding.left), + right: length(padding.right), + top: length(padding.top), + bottom: length(padding.bottom), + }, + + ..taffy::Style::default() + }; for (child, tree) in items.iter().zip(tree.iter_mut()) { // Calculate the dimensions of the item. - let child_node = child.as_widget().layout(tree, renderer, &limits); + let child_widget = child.as_widget(); + let child_node = child_widget.layout(tree, renderer, &limits); let size = child_node.size(); - // Calculate the required additional width to fit the item into the current row. - let mut required_width = size.width - + if row_buffer.is_empty() { - 0.0 - } else { - row_spacing + nodes.push(child_node); + + let c_size = child_widget.size(); + let (width, justify_self) = match c_size.width { + Length::Fill | Length::FillPortion(_) => (Dimension::Auto, Some(AlignItems::Stretch)), + _ => (length(size.width), None), + }; + + let child_style = Style { + size: taffy::geometry::Size { + width, + height: match c_size.height { + Length::Fill | Length::FillPortion(_) => Dimension::Auto, + _ => length(size.height), + }, + }, + justify_self, + ..Style::default() + }; + + leafs.push(match taffy_tree.new_leaf(child_style) { + Ok(leaf) => leaf, + Err(why) => { + tracing::error!(?why, "failed to add child element to flex row"); + continue; + } + }); + } + + let root = match taffy_tree.new_with_children(style, &leafs) { + Ok(root) => root, + Err(why) => { + tracing::error!(?why, "flex row style is invalid"); + return Node::new(Size::ZERO); + } + }; + + if let Err(why) = taffy_tree.compute_layout( + root, + taffy::geometry::Size { + width: length(max_size.width), + height: length(max_size.height), + }, + ) { + tracing::error!(?why, "flex row layout invalid"); + return Node::new(Size::ZERO); + } + + let flex_layout = match taffy_tree.layout(root) { + Ok(layout) => layout, + Err(why) => { + tracing::error!(?why, "cannot get flex row layout"); + return Node::new(Size::ZERO); + } + }; + + leafs + .into_iter() + .zip(items.iter()) + .zip(nodes.iter_mut()) + .zip(tree) + .for_each(|(((leaf, child), node), tree)| { + let Ok(leaf_layout) = taffy_tree.layout(leaf) else { + return; }; - // If it fits, add it to the current row, or create a new one. - if current_row_width + required_width > max_flex_width { - if flex_height != 0.0f32 { - flex_height += column_spacing; + let child_widget = child.as_widget(); + let c_size = child_widget.size(); + match c_size.width { + Length::Fill | Length::FillPortion(_) => { + *node = + child_widget.layout(tree, renderer, &limits.width(leaf_layout.size.width)); + } + _ => (), } - let mut pos_x = 0.0f32; - let pos_y = flex_height; + *node = node.clone().move_to(Point { + x: leaf_layout.location.x, + y: leaf_layout.location.y, + }); + }); - for mut child_node in row_buffer.drain(..) { - child_node = child_node.move_to(Point::new(pos_x, pos_y)); - pos_x += row_spacing + child_node.size().width; - nodes.push(child_node); - } + let size = Size { + width: flex_layout.content_size.width, + height: flex_layout.content_size.height, + }; - flex_height += current_row_height; - flex_width = flex_width.max(current_row_width); - required_width -= row_spacing; - current_row_width = 0.0; - } - - current_row_width += required_width; - current_row_height = current_row_height.max(size.height); - - row_buffer.push(child_node); - } - - if !row_buffer.is_empty() { - if flex_height != 0.0f32 { - flex_height += column_spacing; - } - - let mut pos_x = 0.0f32; - let pos_y = flex_height; - - for mut child_node in row_buffer.drain(..) { - child_node = child_node.move_to(Point::new(pos_x, pos_y)); - pos_x += row_spacing + child_node.size().width; - nodes.push(child_node); - } - - flex_height += current_row_height; - flex_width = flex_width.max(current_row_width); - } - - let flex_size = limits.resolve(flex_width, flex_height, Size::new(flex_width, flex_height)); - Node::with_children(flex_size.expand(padding), nodes) + Node::with_children(size.expand(padding), nodes) } From 1bc30121656a907a2c08bdc1f16ac81353a4734a Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 29 May 2024 23:41:48 +0200 Subject: [PATCH 29/46] improv(flex-row): add spacing method to set row and column spacing --- src/widget/flex_row/widget.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 02873e6e..2a445fae 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -17,6 +17,7 @@ pub struct FlexRow<'a, Message> { #[setters(skip)] children: Vec>, /// Sets the padding around the widget. + #[setters(into)] padding: Padding, /// Sets the space between each column of items. column_spacing: u16, @@ -29,7 +30,7 @@ pub struct FlexRow<'a, Message> { } impl<'a, Message> FlexRow<'a, Message> { - pub const fn new(children: Vec>) -> Self { + pub(crate) const fn new(children: Vec>) -> Self { Self { children, padding: Padding::ZERO, @@ -39,6 +40,13 @@ impl<'a, Message> FlexRow<'a, Message> { max_width: f32::INFINITY, } } + + /// Sets the space between each column and row. + pub const fn spacing(mut self, spacing: u16) -> Self { + self.column_spacing = spacing; + self.row_spacing = spacing; + self + } } impl<'a, Message: 'static + Clone> Widget From 7a6421a3e297a3e62ce344cd4e31eca55bf0d3cf Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 29 May 2024 23:55:50 +0200 Subject: [PATCH 30/46] feat(flex-row): add justify_content method --- src/widget/flex_row/layout.rs | 5 ++++- src/widget/flex_row/widget.rs | 5 +++++ src/widget/mod.rs | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/widget/flex_row/layout.rs b/src/widget/flex_row/layout.rs index df1c862f..cf80548e 100644 --- a/src/widget/flex_row/layout.rs +++ b/src/widget/flex_row/layout.rs @@ -8,7 +8,7 @@ use iced_core::{Length, Padding, Point, Size}; use taffy::geometry::Rect; use taffy::style::{AlignItems, Dimension, Display, Style}; use taffy::style_helpers::length; -use taffy::TaffyTree; +use taffy::{AlignContent, TaffyTree}; #[allow(clippy::too_many_lines)] pub fn resolve( @@ -18,6 +18,7 @@ pub fn resolve( padding: Padding, column_spacing: f32, row_spacing: f32, + justify_content: Option, tree: &mut [Tree], ) -> Node { let limits = limits.shrink(padding); @@ -43,6 +44,8 @@ pub fn resolve( height: Dimension::Auto, }, + justify_content, + padding: Rect { left: length(padding.left), right: length(padding.right), diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 2a445fae..2394d078 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -27,6 +27,9 @@ pub struct FlexRow<'a, Message> { width: Length, /// Sets the max width max_width: f32, + /// Defines how the content will be justified. + #[setters(into)] + justify_content: Option, } impl<'a, Message> FlexRow<'a, Message> { @@ -38,6 +41,7 @@ impl<'a, Message> FlexRow<'a, Message> { row_spacing: 4, width: Length::Shrink, max_width: f32::INFINITY, + justify_content: None, } } @@ -83,6 +87,7 @@ impl<'a, Message: 'static + Clone> Widget self.padding, f32::from(self.column_spacing), f32::from(self.row_spacing), + self.justify_content, &mut tree.children, ) } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 280ef26b..9530d060 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -231,6 +231,8 @@ pub use icon::{icon, Icon}; #[cfg(feature = "animated-image")] pub mod frames; +pub use taffy::JustifyContent; + pub mod list; #[doc(inline)] pub use list::{list_column, ListColumn}; From b7cb1b31ff1846d0dcc74bf4020f3414d82b4f22 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 29 May 2024 23:57:26 +0200 Subject: [PATCH 31/46] feat(widget): re-export canvas widget from iced --- src/widget/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 9530d060..a20861d4 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -47,6 +47,9 @@ //! ``` // Re-exports from Iced +#[doc(inline)] +pub use iced::widget::{canvas, Canvas}; + #[doc(inline)] pub use iced::widget::{checkbox, Checkbox}; From 463e07ec4b9901b1069f71551c3bc9a60ad0ef9d Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Wed, 29 May 2024 23:59:27 +0200 Subject: [PATCH 32/46] feat(widget): re-export text_editor from iced --- src/widget/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/widget/mod.rs b/src/widget/mod.rs index a20861d4..9b922470 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -89,6 +89,9 @@ pub use iced::widget::{slider, vertical_slider, Slider, VerticalSlider}; #[doc(inline)] pub use iced::widget::{svg, Svg}; +#[doc(inline)] +pub use iced::widget::{text_editor, TextEditor}; + #[doc(inline)] pub use iced_core::widget::{Id, Operation, Widget}; From 289db873738445fda0527c97e52b91b83c5a843e Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 30 May 2024 11:41:08 +0200 Subject: [PATCH 33/46] improv(grid): add justify_content and fix padding --- src/widget/grid/layout.rs | 22 +++++++++++++++++----- src/widget/grid/widget.rs | 5 +++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/widget/grid/layout.rs b/src/widget/grid/layout.rs index d61d13c9..6423c377 100644 --- a/src/widget/grid/layout.rs +++ b/src/widget/grid/layout.rs @@ -10,8 +10,9 @@ use iced_core::{Alignment, Length, Padding, Point, Size}; use taffy::geometry::{Line, Rect}; use taffy::style::{AlignItems, Dimension, Display, GridPlacement, Style}; use taffy::style_helpers::{auto, length}; -use taffy::TaffyTree; +use taffy::{AlignContent, TaffyTree}; +#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_lines)] pub fn resolve( renderer: &Renderer, @@ -23,6 +24,7 @@ pub fn resolve( padding: Padding, column_alignment: Alignment, row_alignment: Alignment, + justify_content: Option, column_spacing: f32, row_spacing: f32, tree: &mut [Tree], @@ -44,23 +46,29 @@ pub fn resolve( nodes.push(child_node); let c_size = child_widget.size(); - let (width, justify_self) = match c_size.width { - Length::Fill | Length::FillPortion(_) => (Dimension::Auto, Some(AlignItems::Stretch)), - _ => (length(size.width), None), + let (width, flex_grow, justify_self) = match c_size.width { + Length::Fill | Length::FillPortion(_) => { + (Dimension::Auto, 1.0, Some(AlignItems::Stretch)) + } + _ => (length(size.width), 0.0, None), }; // Attach widget as leaf to be later assigned to grid. let leaf = taffy.new_leaf(Style { + flex_grow, + grid_column: Line { start: GridPlacement::Line((assignment.column as i16).into()), end: GridPlacement::Line( (assignment.column as i16 + assignment.width as i16).into(), ), }, + grid_row: Line { start: GridPlacement::Line((assignment.row as i16).into()), end: GridPlacement::Line((assignment.row as i16 + assignment.height as i16).into()), }, + size: taffy::geometry::Size { width, height: match c_size.height { @@ -68,7 +76,9 @@ pub fn resolve( _ => length(size.height), }, }, + justify_self, + ..Style::default() }); @@ -108,6 +118,8 @@ pub fn resolve( }, }), + justify_content, + padding: Rect { left: length(padding.left), right: length(padding.right), @@ -187,5 +199,5 @@ pub fn resolve( height: grid_layout.content_size.height, }; - Node::with_children(grid_size.expand(padding), nodes) + Node::with_children(grid_size, nodes) } diff --git a/src/widget/grid/widget.rs b/src/widget/grid/widget.rs index 6c16b79f..7ba1cfcc 100644 --- a/src/widget/grid/widget.rs +++ b/src/widget/grid/widget.rs @@ -26,6 +26,9 @@ pub struct Grid<'a, Message> { column_alignment: Alignment, /// Alignment across rows row_alignment: Alignment, + /// Defines how the content will be justified. + #[setters(into, strip_option)] + justify_content: Option, /// Sets the space between each column of items. column_spacing: u16, /// Sets the space between each item in a row. @@ -50,6 +53,7 @@ impl<'a, Message> Grid<'a, Message> { padding: Padding::ZERO, column_alignment: Alignment::Start, row_alignment: Alignment::Start, + justify_content: None, column_spacing: 4, row_spacing: 4, width: Length::Shrink, @@ -138,6 +142,7 @@ impl<'a, Message: 'static + Clone> Widget for G self.padding, self.column_alignment, self.row_alignment, + self.justify_content, f32::from(self.column_spacing), f32::from(self.row_spacing), &mut tree.children, From cb6bc86e1e692ac8bbd20fecb46cdad033f4ac2c Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 30 May 2024 12:37:32 +0200 Subject: [PATCH 34/46] feat(flex-row): align_items, justify_items, justify_content, and min_item_width --- src/widget/flex_row/layout.rs | 32 +++++++++++++++++++++++++------- src/widget/flex_row/widget.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/widget/flex_row/layout.rs b/src/widget/flex_row/layout.rs index cf80548e..d781e4f9 100644 --- a/src/widget/flex_row/layout.rs +++ b/src/widget/flex_row/layout.rs @@ -10,6 +10,7 @@ use taffy::style::{AlignItems, Dimension, Display, Style}; use taffy::style_helpers::length; use taffy::{AlignContent, TaffyTree}; +#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_lines)] pub fn resolve( renderer: &Renderer, @@ -18,10 +19,12 @@ pub fn resolve( padding: Padding, column_spacing: f32, row_spacing: f32, + min_item_width: Option, + justify_items: Option, + align_items: Option, justify_content: Option, tree: &mut [Tree], ) -> Node { - let limits = limits.shrink(padding); let max_size = limits.max(); let mut leafs = Vec::with_capacity(items.len()); @@ -44,6 +47,8 @@ pub fn resolve( height: Dimension::Auto, }, + align_items, + justify_items, justify_content, padding: Rect { @@ -57,20 +62,31 @@ pub fn resolve( }; for (child, tree) in items.iter().zip(tree.iter_mut()) { - // Calculate the dimensions of the item. let child_widget = child.as_widget(); - let child_node = child_widget.layout(tree, renderer, &limits); + let child_node = child_widget.layout(tree, renderer, limits); let size = child_node.size(); nodes.push(child_node); let c_size = child_widget.size(); - let (width, justify_self) = match c_size.width { - Length::Fill | Length::FillPortion(_) => (Dimension::Auto, Some(AlignItems::Stretch)), - _ => (length(size.width), None), + let (width, flex_grow, justify_self) = match c_size.width { + Length::Fill | Length::FillPortion(_) => { + (Dimension::Auto, 1.0, Some(AlignItems::Stretch)) + } + _ => (length(size.width), 0.0, None), }; let child_style = Style { + flex_grow, + + min_size: taffy::geometry::Size { + width: match min_item_width { + Some(width) => length(size.width.min(width)), + None => Dimension::Auto, + }, + height: Dimension::Auto, + }, + size: taffy::geometry::Size { width, height: match c_size.height { @@ -78,7 +94,9 @@ pub fn resolve( _ => length(size.height), }, }, + justify_self, + ..Style::default() }; @@ -149,5 +167,5 @@ pub fn resolve( height: flex_layout.content_size.height, }; - Node::with_children(size.expand(padding), nodes) + Node::with_children(size, nodes) } diff --git a/src/widget/flex_row/widget.rs b/src/widget/flex_row/widget.rs index 2394d078..b89397f0 100644 --- a/src/widget/flex_row/widget.rs +++ b/src/widget/flex_row/widget.rs @@ -25,8 +25,17 @@ pub struct FlexRow<'a, Message> { row_spacing: u16, /// Sets the width. width: Length, + /// Sets minimum width of items that grow. + #[setters(into)] + min_item_width: Option, /// Sets the max width max_width: f32, + /// Defines how content will be aligned horizontally. + #[setters(skip)] + align_items: Option, + /// Defines how content will be aligned vertically. + #[setters(skip)] + justify_items: Option, /// Defines how the content will be justified. #[setters(into)] justify_content: Option, @@ -40,11 +49,34 @@ impl<'a, Message> FlexRow<'a, Message> { column_spacing: 4, row_spacing: 4, width: Length::Shrink, + min_item_width: None, max_width: f32::INFINITY, + align_items: None, + justify_items: None, justify_content: None, } } + /// Defines how content will be aligned horizontally. + pub fn align_items(mut self, alignment: iced::Alignment) -> Self { + self.align_items = Some(match alignment { + iced::Alignment::Center => taffy::AlignItems::Center, + iced::Alignment::Start => taffy::AlignItems::Start, + iced::Alignment::End => taffy::AlignItems::End, + }); + self + } + + /// Defines how content will be aligned vertically. + pub fn justify_items(mut self, alignment: iced::Alignment) -> Self { + self.justify_items = Some(match alignment { + iced::Alignment::Center => taffy::AlignItems::Center, + iced::Alignment::Start => taffy::AlignItems::Start, + iced::Alignment::End => taffy::AlignItems::End, + }); + self + } + /// Sets the space between each column and row. pub const fn spacing(mut self, spacing: u16) -> Self { self.column_spacing = spacing; @@ -87,6 +119,9 @@ impl<'a, Message: 'static + Clone> Widget self.padding, f32::from(self.column_spacing), f32::from(self.row_spacing), + self.min_item_width, + self.align_items, + self.justify_items, self.justify_content, &mut tree.children, ) From 095c2b533694c51614ee2b47834d514a2463af74 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 30 May 2024 12:38:07 +0200 Subject: [PATCH 35/46] feat(widget/settings): add flex items and flex controls --- src/widget/settings/item.rs | 44 ++++++++++++++++++++++++++++++++----- src/widget/settings/mod.rs | 2 +- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index 73476cbf..d515624c 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -4,11 +4,13 @@ use std::borrow::Cow; use crate::{ - widget::{column, horizontal_space, row, text, Row}, + widget::{column, container, flex_row, horizontal_space, row, text, FlexRow, Row}, Element, }; use derive_setters::Setters; +use iced::alignment::Horizontal; use iced_core::Length; +use taffy::AlignContent; /// A settings item aligned in a row #[must_use] @@ -29,9 +31,33 @@ pub fn item<'a, Message: 'static>( #[allow(clippy::module_name_repetitions)] pub fn item_row(children: Vec>) -> Row { row::with_children(children) + .spacing(12) .align_items(iced::Alignment::Center) .padding([0, 18]) +} + +/// A settings item aligned in a flex row +#[allow(clippy::module_name_repetitions)] +pub fn flex_item<'a, Message: 'static>( + title: impl Into> + 'a, + widget: impl Into> + 'a, +) -> FlexRow<'a, Message> { + flex_item_row(vec![ + text(title).width(Length::Fill).into(), + container(widget).into(), + ]) +} + +/// A settings item aligned in a flex row +#[allow(clippy::module_name_repetitions)] +pub fn flex_item_row(children: Vec>) -> FlexRow { + flex_row(children) + .padding([0, 18]) .spacing(12) + .min_item_width(200.0) + .justify_items(iced::Alignment::Center) + .justify_content(AlignContent::SpaceBetween) + .width(Length::Fill) } /// Creates a builder for an item, beginning with the title. @@ -61,6 +87,15 @@ pub struct Item<'a, Message> { impl<'a, Message: 'static> Item<'a, Message> { /// Assigns a control to the item. pub fn control(self, widget: impl Into>) -> Row<'a, Message> { + item_row(self.control_(widget)) + } + + /// Assigns a control which flexes. + pub fn flex_control(self, widget: impl Into>) -> FlexRow<'a, Message> { + flex_item_row(self.control_(widget)) + } + + fn control_(self, widget: impl Into>) -> Vec> { let mut contents = Vec::with_capacity(4); if let Some(icon) = self.icon { @@ -80,15 +115,14 @@ impl<'a, Message: 'static> Item<'a, Message> { } contents.push(widget.into()); - - item_row(contents) + contents } pub fn toggler( self, is_checked: bool, message: impl Fn(bool) -> Message + 'static, - ) -> Row<'a, Message> { - self.control(crate::widget::toggler(None, is_checked, message)) + ) -> FlexRow<'a, Message> { + self.flex_control(crate::widget::toggler(None, is_checked, message)) } } diff --git a/src/widget/settings/mod.rs b/src/widget/settings/mod.rs index b6c1de45..dedbc886 100644 --- a/src/widget/settings/mod.rs +++ b/src/widget/settings/mod.rs @@ -4,7 +4,7 @@ pub mod item; mod section; -pub use self::item::{item, item_row}; +pub use self::item::{flex_item, flex_item_row, item, item_row}; pub use self::section::{view_section, Section}; use crate::widget::{column, Column}; From 3a8cbec6ae0ce21fb1d6d691a0b0cccd72098f61 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 30 May 2024 13:07:34 +0200 Subject: [PATCH 36/46] improv(icon): remove static lifetime restriction from messages --- src/widget/icon/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/icon/mod.rs b/src/widget/icon/mod.rs index 783e47ca..752125fe 100644 --- a/src/widget/icon/mod.rs +++ b/src/widget/icon/mod.rs @@ -69,7 +69,7 @@ impl Icon { } #[must_use] - fn into_element(self) -> Element<'static, Message> { + fn view<'a, Message: 'a>(self) -> Element<'a, Message> { let from_image = |handle| { Image::new(handle) .width( @@ -120,8 +120,8 @@ impl Icon { } } -impl From for Element<'static, Message> { +impl<'a, Message: 'a> From for Element<'a, Message> { fn from(icon: Icon) -> Self { - icon.into_element::() + icon.view::() } } From bd48ad0fb54c632915aeaf83e80d4f7a5b327fd4 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 30 May 2024 13:08:07 +0200 Subject: [PATCH 37/46] fix(menu): use icons and fix misaligned button labels --- src/widget/menu/menu_tree.rs | 69 +++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 32 deletions(-) diff --git a/src/widget/menu/menu_tree.rs b/src/widget/menu/menu_tree.rs index 56715f7e..2da9bae1 100644 --- a/src/widget/menu/menu_tree.rs +++ b/src/widget/menu/menu_tree.rs @@ -141,21 +141,19 @@ where } } -/// This macro creates a button for a MenuTree. -#[macro_export] -macro_rules! menu_button { - ($($x:expr),+ $(,)?) => ( - widget::button( - widget::Row::with_children(vec![$($x.into()),+]) +pub fn menu_button<'a, Message: 'a>( + children: Vec>, +) -> crate::widget::Button<'a, Message> { + widget::button( + widget::Row::with_children(children) .align_items(Alignment::Center) .height(Length::Fill) .width(Length::Fill), - ) - .height(Length::Fixed(36.0)) - .padding([4, 16]) - .width(Length::Fill) - .style(theme::Button::MenuItem) - ); + ) + .height(Length::Fixed(36.0)) + .padding([4, 16]) + .width(Length::Fill) + .style(theme::Button::MenuItem) } /// Represents a menu item that performs an action when selected or a separator between menu items. @@ -243,11 +241,11 @@ where match item { MenuItem::Button(label, action) => { let key = find_key(&action, key_binds); - let menu_button = menu_button!( - widget::text(label), - widget::horizontal_space(Length::Fill), - widget::text(key), - ) + let menu_button = menu_button(vec![ + widget::text(label).into(), + widget::horizontal_space(Length::Fill).into(), + widget::text(key).into(), + ]) .on_press(action.message()); trees.push(MenuTree::::new(menu_button)); @@ -255,29 +253,36 @@ where MenuItem::CheckBox(label, value, action) => { let key = find_key(&action, key_binds); trees.push(MenuTree::new( - menu_button!( + menu_button(vec![ if value { - // TODO: add a object-select-symbolic icon, `Message: 'static` is required when using an icon widget. - widget::container(widget::text("✓")) + widget::icon::from_name("object-select-symbolic") + .size(16) + .icon() + .width(Length::Fixed(16.0)) + .into() } else { - widget::container(widget::Space::with_width(Length::Fixed(16.0))) + widget::Space::with_width(Length::Fixed(17.0)).into() }, - widget::Space::with_width(Length::Fixed(8.0)), - widget::text(label), - widget::horizontal_space(Length::Fill), - widget::text(key) - ) + widget::Space::with_width(Length::Fixed(8.0)).into(), + widget::text(label) + .horizontal_alignment(iced::alignment::Horizontal::Left) + .into(), + widget::horizontal_space(Length::Fill).into(), + widget::text(key).into(), + ]) .on_press(action.message()), )); } MenuItem::Folder(label, children) => { trees.push(MenuTree::::with_children( - menu_button!( - widget::text(label), - widget::horizontal_space(Length::Fill), - // TODO: add a pan-end-symbolic icon, `Message: 'static` is required when using an icon widget. - widget::text("▶"), - ), + menu_button(vec![ + widget::text(label).into(), + widget::horizontal_space(Length::Fill).into(), + widget::icon::from_name("pan-end-symbolic") + .size(16) + .icon() + .into(), + ]), menu_items(key_binds, children), )); } From 80879213af715181dcbc7578b5c0e60f28d5aaa8 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 30 May 2024 13:43:36 -0400 Subject: [PATCH 38/46] fix: use the user provided theme even when setting system theme the user may have set a preferred value which should be used. --- src/app/cosmic.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app/cosmic.rs b/src/app/cosmic.rs index 21ddf636..30adc37d 100644 --- a/src/app/cosmic.rs +++ b/src/app/cosmic.rs @@ -409,11 +409,9 @@ impl Cosmic { } Message::AppThemeChange(mut theme) => { - // Apply last-known system theme if the system theme is preferred. if let ThemeType::System { theme: _, .. } = theme.theme_type { self.app.core_mut().theme_sub_counter += 1; - theme = self.app.core().system_theme.clone(); let portal_accent = self.app.core().portal_accent; if let Some(a) = portal_accent { let t_inner = theme.cosmic(); From b937a30050a3012dc774478e344130df16db800b Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 30 May 2024 13:53:09 -0400 Subject: [PATCH 39/46] fix: export menu_button --- src/widget/menu.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/widget/menu.rs b/src/widget/menu.rs index 2348b9af..a4deb345 100644 --- a/src/widget/menu.rs +++ b/src/widget/menu.rs @@ -67,7 +67,9 @@ pub use menu_bar::{menu_bar as bar, MenuBar}; mod menu_inner; mod menu_tree; -pub use menu_tree::{menu_items as items, menu_root as root, MenuItem as Item, MenuTree as Tree}; +pub use menu_tree::{ + menu_button, menu_items as items, menu_root as root, MenuItem as Item, MenuTree as Tree, +}; pub use crate::style::menu_bar::{Appearance, StyleSheet}; pub(crate) use menu_inner::Menu; From 9f10313cd853ed189661ce822241e2cf381ae11d Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 30 May 2024 21:38:25 +0200 Subject: [PATCH 40/46] chore: update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 7c58b8ed..09116a85 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 7c58b8eda9885083e019a5ea14edf4652740e154 +Subproject commit 09116a85e27890df6dbad1906444a0b3e4e8ba14 From 1ef1400d8b816ba261fff52bf947162d976ceb3b Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 30 May 2024 21:39:59 +0200 Subject: [PATCH 41/46] fix: condense app to content first --- src/app/core.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/core.rs b/src/app/core.rs index eeb0ae22..1a57de66 100644 --- a/src/app/core.rs +++ b/src/app/core.rs @@ -104,7 +104,7 @@ impl Default for Core { active: true, context_id: crate::widget::nav_bar::Id::null(), toggled: true, - toggled_condensed: true, + toggled_condensed: false, }, scale_factor: 1.0, title: HashMap::new(), From af58d81c6fc731b5ef32cb3125717fd7ff17af30 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Thu, 30 May 2024 22:00:48 +0200 Subject: [PATCH 42/46] fix: use spacing of 2 when header density is compact --- src/widget/header_bar.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 21701d40..2fd0279a 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -269,7 +269,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { end.push(widget::horizontal_space(Length::Fixed(12.0)).into()); end.push(self.window_controls()); - let (height, padding) = match self.density.unwrap_or_else(|| crate::config::header_size()) { + let (height, padding) = match self.density.unwrap_or_else(crate::config::header_size) { crate::config::Density::Compact => (36.0, 2.0), crate::config::Density::Standard => (48.0, 8.0), }; @@ -358,6 +358,13 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { .on_press(on_press) }; + let density = self.density.unwrap_or_else(crate::config::header_size); + let spacing = if matches!(density, Density::Compact) { + 2 + } else { + 8 + }; + widget::row::with_capacity(3) .push_maybe(self.on_minimize.take().map(|m| { icon( @@ -380,7 +387,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { m, ) })) - .spacing(8) + .spacing(spacing) .apply(widget::container) .height(Length::Fill) .center_y() From 72db8a013babb9aa53ec9a6c87c80f80428134f2 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 30 May 2024 15:48:42 -0400 Subject: [PATCH 43/46] feat: add id_container --- src/widget/id_container.rs | 207 +++++++++++++++++++++++++++++++++++++ src/widget/mod.rs | 4 + 2 files changed, 211 insertions(+) create mode 100644 src/widget/id_container.rs diff --git a/src/widget/id_container.rs b/src/widget/id_container.rs new file mode 100644 index 00000000..90511ff3 --- /dev/null +++ b/src/widget/id_container.rs @@ -0,0 +1,207 @@ +use iced_core::event::{self, Event}; +use iced_core::layout; +use iced_core::mouse; +use iced_core::overlay; +use iced_core::renderer; +use iced_core::widget::{Id, Tree}; +use iced_core::{Clipboard, Element, Layout, Length, Rectangle, Shell, Widget}; +pub use iced_style::container::{Appearance, StyleSheet}; + +pub fn id_container<'a, Message: 'static, Theme, E>( + content: E, + id: Id, +) -> IdContainer<'a, Message, Theme, crate::Renderer> +where + E: Into>, + Theme: iced_style::container::StyleSheet, + ::Style: From, +{ + IdContainer::new(content, id) +} + +/// An element decorating some content. +/// +/// It is normally used for alignment purposes. +#[allow(missing_debug_implementations)] +pub struct IdContainer<'a, Message, Theme, Renderer> +where + Renderer: iced_core::Renderer, +{ + content: Element<'a, Message, Theme, Renderer>, + id: Id, +} + +impl<'a, Message, Theme, Renderer> IdContainer<'a, Message, Theme, Renderer> +where + Renderer: iced_core::Renderer, +{ + /// Creates an empty [`IdContainer`]. + pub(crate) fn new(content: T, id: Id) -> Self + where + T: Into>, + { + IdContainer { + content: content.into(), + id, + } + } +} + +impl<'a, Message, Theme, Renderer> Widget + for IdContainer<'a, Message, Theme, Renderer> +where + Renderer: iced_core::Renderer, +{ + fn children(&self) -> Vec { + vec![Tree::new(&self.content)] + } + + fn diff(&mut self, tree: &mut Tree) { + tree.children[0].diff(&mut self.content); + } + + fn size(&self) -> iced_core::Size { + self.content.as_widget().size() + } + + fn layout( + &self, + tree: &mut Tree, + renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let node = self + .content + .as_widget() + .layout(&mut tree.children[0], renderer, limits); + let size = node.size(); + layout::Node::with_children(size, vec![node]) + } + + fn operate( + &self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn iced_core::widget::Operation< + iced_core::widget::OperationOutputWrapper, + >, + ) { + self.content.as_widget().operate( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + operation, + ); + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + renderer: &Renderer, + clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) -> event::Status { + self.content.as_widget_mut().on_event( + &mut tree.children[0], + event.clone(), + layout.children().next().unwrap(), + cursor_position, + renderer, + clipboard, + shell, + viewport, + ) + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + let content_layout = layout.children().next().unwrap(); + self.content.as_widget().mouse_interaction( + &tree.children[0], + content_layout, + cursor_position, + viewport, + renderer, + ) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + renderer_style: &renderer::Style, + layout: Layout<'_>, + cursor_position: mouse::Cursor, + viewport: &Rectangle, + ) { + let content_layout = layout.children().next().unwrap(); + self.content.as_widget().draw( + &tree.children[0], + renderer, + theme, + renderer_style, + content_layout, + cursor_position, + viewport, + ); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + ) -> Option> { + self.content.as_widget_mut().overlay( + &mut tree.children[0], + layout.children().next().unwrap(), + renderer, + ) + } + + fn drag_destinations( + &self, + state: &Tree, + layout: Layout<'_>, + dnd_rectangles: &mut iced_style::core::clipboard::DndDestinationRectangles, + ) { + let content_layout = layout.children().next().unwrap(); + self.content.as_widget().drag_destinations( + &state.children[0], + content_layout, + dnd_rectangles, + ); + } + + fn id(&self) -> Option { + Some(self.id.clone()) + } + + fn set_id(&mut self, id: crate::widget::Id) { + self.id = id; + } +} + +impl<'a, Message, Theme, Renderer> From> + for Element<'a, Message, Theme, Renderer> +where + Message: 'a, + Renderer: 'a + iced_core::Renderer, + Theme: 'a, +{ + fn from(c: IdContainer<'a, Message, Theme, Renderer>) -> Element<'a, Message, Theme, Renderer> { + Element::new(c) + } +} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 9b922470..a1b6cb51 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -234,6 +234,10 @@ pub mod icon; #[doc(inline)] pub use icon::{icon, Icon}; +pub mod id_container; +#[doc(inline)] +pub use id_container::{id_container, IdContainer}; + #[cfg(feature = "animated-image")] pub mod frames; From 85c925241eeee8614d0e12385116e8ca298c13e9 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 30 May 2024 15:48:51 -0400 Subject: [PATCH 44/46] fix: use tree diff --- src/widget/context_menu.rs | 2 +- src/widget/dnd_destination.rs | 2 +- src/widget/dnd_source.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/widget/context_menu.rs b/src/widget/context_menu.rs index dfec06d1..261779f6 100644 --- a/src/widget/context_menu.rs +++ b/src/widget/context_menu.rs @@ -91,7 +91,7 @@ impl<'a, Message: Clone> Widget } fn diff(&mut self, tree: &mut Tree) { - self.content.as_widget_mut().diff(&mut tree.children[0]); + tree.children[0].diff(self.content.as_widget_mut()); // if let Some(ref mut context_menus) = self.context_menu { // for (menu, tree) in context_menus diff --git a/src/widget/dnd_destination.rs b/src/widget/dnd_destination.rs index 57a52768..0aca0d79 100644 --- a/src/widget/dnd_destination.rs +++ b/src/widget/dnd_destination.rs @@ -253,7 +253,7 @@ impl<'a, Message: 'static> Widget } fn diff(&mut self, tree: &mut Tree) { - self.container.as_widget_mut().diff(&mut tree.children[0]); + tree.children[0].diff(self.container.as_widget_mut()); } fn state(&self) -> iced_core::widget::tree::State { diff --git a/src/widget/dnd_source.rs b/src/widget/dnd_source.rs index e1699ed4..5a378209 100644 --- a/src/widget/dnd_source.rs +++ b/src/widget/dnd_source.rs @@ -132,7 +132,7 @@ impl< } fn diff(&mut self, tree: &mut Tree) { - self.container.as_widget_mut().diff(&mut tree.children[0]); + tree.children[0].diff(self.container.as_widget_mut()); } fn state(&self) -> iced_core::widget::tree::State { From 552f3aefd6e02e7c31c165ba5eaee8059757a9b6 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 30 May 2024 15:49:15 -0400 Subject: [PATCH 45/46] feat: wrap core interface elements in id container --- src/app/mod.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index aa5b5183..275578af 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -51,7 +51,7 @@ pub use self::settings::Settings; use crate::config::CosmicTk; use crate::prelude::*; use crate::theme::THEME; -use crate::widget::{context_drawer, menu, nav_bar, popover}; +use crate::widget::{context_drawer, id_container, menu, nav_bar, popover}; use apply::Apply; use iced::Subscription; #[cfg(all(feature = "winit", feature = "multi-window"))] @@ -643,6 +643,7 @@ impl ApplicationExt for App { Command::none() } + #[allow(clippy::too_many_lines)] /// Creates the view for the main window. fn view_main(&self) -> Element> { let core = self.core(); @@ -655,8 +656,11 @@ impl ApplicationExt for App { let mut widgets = Vec::with_capacity(2); // Insert nav bar onto the left side of the window. - if let Some(nav) = self.nav_bar() { - widgets.push(nav); + if let Some(nav) = self + .nav_bar() + .map(|nav| id_container(nav, iced_core::id::Id::new("COSMIC_nav_bar"))) + { + widgets.push(nav.into()); } if self.nav_model().is_none() || core.show_content() { @@ -669,7 +673,12 @@ impl ApplicationExt for App { main_content, context.map(Message::App), ) - .into() + .apply(|drawer| { + Element::from(id_container( + drawer, + iced_core::id::Id::new("COSMIC_context_drawer"), + )) + }) } else { main_content }); @@ -685,6 +694,7 @@ impl ApplicationExt for App { .width(iced::Length::Fill) .height(iced::Length::Fill) .style(crate::theme::Container::WindowBackground) + .apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_content_container"))) .into() } else { content_row.into() @@ -734,7 +744,7 @@ impl ApplicationExt for App { header = header.end(element.map(Message::App)); } - header + header.apply(|w| id_container(w, iced_core::id::Id::new("COSMIC_header"))) }) } else { None @@ -745,7 +755,10 @@ impl ApplicationExt for App { // Show any current dialog on top and centered over the view content // We have to use a popover even without a dialog to keep the tree from changing let mut popover = popover(view_column).modal(true); - if let Some(dialog) = self.dialog() { + if let Some(dialog) = self + .dialog() + .map(|w| Element::from(id_container(w, iced_core::id::Id::new("COSMIC_dialog")))) + { popover = popover.popup(dialog.map(Message::App)); } From e1b78fd3a0980bf1e1c8b90cb794986d8db0b4e5 Mon Sep 17 00:00:00 2001 From: Ashley Wulber Date: Thu, 30 May 2024 17:07:50 -0400 Subject: [PATCH 46/46] update iced --- iced | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iced b/iced index 09116a85..2dcae592 160000 --- a/iced +++ b/iced @@ -1 +1 @@ -Subproject commit 09116a85e27890df6dbad1906444a0b3e4e8ba14 +Subproject commit 2dcae59258c9cee6862c7fb2193066a1cb1e30c2