From 9a0c3388762c31e0188cc91058bb1349f92d7c75 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Mon, 22 Apr 2024 16:04:06 -0600 Subject: [PATCH 1/2] WIP: segmented_button::Model custom elements --- src/app/mod.rs | 2 +- src/widget/color_picker/mod.rs | 14 ++++++------- src/widget/nav_bar.rs | 6 +++--- src/widget/segmented_button/horizontal.rs | 6 +++--- src/widget/segmented_button/model/builder.rs | 18 ++++++++-------- src/widget/segmented_button/model/entity.rs | 8 +++---- src/widget/segmented_button/model/mod.rs | 21 +++++++++++-------- .../segmented_button/model/selection.rs | 8 +++---- src/widget/segmented_button/vertical.rs | 6 +++--- src/widget/segmented_button/widget.rs | 12 +++++------ src/widget/segmented_control.rs | 8 +++---- src/widget/tab_bar.rs | 8 +++---- 12 files changed, 60 insertions(+), 57 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 93a91a702a7..64aafbd8d20 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -478,7 +478,7 @@ where } /// Allows COSMIC to integrate with your application's [`nav_bar::Model`]. - fn nav_model(&self) -> Option<&nav_bar::Model> { + fn nav_model(&self) -> Option<&nav_bar::Model>> { None } diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 7ff646b87fb..30c8bcf8e4d 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -62,9 +62,9 @@ pub enum ColorPickerUpdate { } #[derive(Setters)] -pub struct ColorPickerModel { +pub struct ColorPickerModel { #[setters(skip)] - segmented_model: Model, + segmented_model: Model, #[setters(skip)] active_color: palette::Hsv, #[setters(skip)] @@ -86,7 +86,7 @@ pub struct ColorPickerModel { copied_at: Option, } -impl ColorPickerModel { +impl ColorPickerModel { #[must_use] pub fn new( hex: impl Into> + Clone, @@ -118,7 +118,7 @@ impl ColorPickerModel { /// Get a color picker button that displays the applied color /// - pub fn picker_button<'a, Message: 'static, T: Fn(ColorPickerUpdate) -> Message>( + pub fn picker_button<'a, T: Fn(ColorPickerUpdate) -> Message>( &self, f: T, icon_portion: Option, @@ -130,7 +130,7 @@ impl ColorPickerModel { ) } - pub fn update(&mut self, update: ColorPickerUpdate) -> Command { + pub fn update(&mut self, update: ColorPickerUpdate) -> Command { match update { ColorPickerUpdate::ActiveColor(c) => { self.must_clear_cache.store(true, Ordering::SeqCst); @@ -222,7 +222,7 @@ impl ColorPickerModel { } #[must_use] - pub fn builder( + pub fn builder( &self, on_update: fn(ColorPickerUpdate) -> Message, ) -> ColorPickerBuilder { @@ -246,7 +246,7 @@ impl ColorPickerModel { #[derive(Setters, Clone)] pub struct ColorPickerBuilder<'a, Message> { #[setters(skip)] - model: &'a Model, + model: &'a Model, #[setters(skip)] active_color: palette::Hsv, #[setters(skip)] diff --git a/src/widget/nav_bar.rs b/src/widget/nav_bar.rs index 764f8050f41..27a72403da5 100644 --- a/src/widget/nav_bar.rs +++ b/src/widget/nav_bar.rs @@ -18,13 +18,13 @@ use crate::{theme, Theme}; use super::dnd_destination::DragId; pub type Id = segmented_button::Entity; -pub type Model = segmented_button::SingleSelectModel; +pub type Model = segmented_button::SingleSelectModel; /// Navigation side panel for switching between views. /// /// For details on the model, see the [`segmented_button`] module for more details. pub fn nav_bar( - model: &segmented_button::SingleSelectModel, + model: &Model, on_activate: fn(segmented_button::Entity) -> Message, ) -> NavBar { NavBar { @@ -35,7 +35,7 @@ pub fn nav_bar( /// Navigation side panel for switching between views. /// Can receive drag and drop events. pub fn nav_bar_dnd( - model: &segmented_button::SingleSelectModel, + model: &Model, on_activate: fn(segmented_button::Entity) -> Message, on_dnd_enter: impl Fn(segmented_button::Entity, Vec) -> Message + 'static, on_dnd_leave: impl Fn(segmented_button::Entity) -> Message + 'static, diff --git a/src/widget/segmented_button/horizontal.rs b/src/widget/segmented_button/horizontal.rs index 36decb28b49..e2c838a4d11 100644 --- a/src/widget/segmented_button/horizontal.rs +++ b/src/widget/segmented_button/horizontal.rs @@ -22,10 +22,10 @@ pub struct Horizontal; /// /// For details on the model, see the [`segmented_button`](super) module for more details. pub fn horizontal( - model: &Model, + model: &Model, ) -> SegmentedButton where - Model: Selectable, + Model: Selectable, { SegmentedButton::new(model) } @@ -33,7 +33,7 @@ where impl<'a, SelectionMode, Message> SegmentedVariant for SegmentedButton<'a, Horizontal, SelectionMode, Message> where - Model: Selectable, + Model: Selectable, SelectionMode: Default, { fn variant_appearance( diff --git a/src/widget/segmented_button/model/builder.rs b/src/widget/segmented_button/model/builder.rs index 87d25adba25..2554b7923b0 100644 --- a/src/widget/segmented_button/model/builder.rs +++ b/src/widget/segmented_button/model/builder.rs @@ -9,37 +9,37 @@ use std::borrow::Cow; /// A builder for a [`Model`]. #[derive(Default)] -pub struct ModelBuilder(Model); +pub struct ModelBuilder(Model); /// Constructs a new item for the [`ModelBuilder`]. -pub struct BuilderEntity { - model: ModelBuilder, +pub struct BuilderEntity { + model: ModelBuilder, id: Entity, } -impl ModelBuilder +impl ModelBuilder where - Model: Selectable, + Model: Selectable, { /// Inserts a new item and its associated data into the model. #[must_use] pub fn insert( mut self, - builder: impl Fn(BuilderEntity) -> BuilderEntity, + builder: impl Fn(BuilderEntity) -> BuilderEntity, ) -> Self { let id = self.0.insert().id(); builder(BuilderEntity { model: self, id }).model } /// Consumes the builder and returns the model. - pub fn build(self) -> Model { + pub fn build(self) -> Model { self.0 } } -impl BuilderEntity +impl BuilderEntity where - Model: Selectable, + Model: Selectable, { /// Activates the newly-inserted item. #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] diff --git a/src/widget/segmented_button/model/entity.rs b/src/widget/segmented_button/model/entity.rs index c9f0997dbb9..0a9ba2e66df 100644 --- a/src/widget/segmented_button/model/entity.rs +++ b/src/widget/segmented_button/model/entity.rs @@ -10,14 +10,14 @@ use crate::widget::Icon; use super::{Entity, Model, Selectable}; /// A newly-inserted item which may have additional actions applied to it. -pub struct EntityMut<'a, SelectionMode: Default> { +pub struct EntityMut<'a, SelectionMode: Default, Message> { pub(super) id: Entity, - pub(super) model: &'a mut Model, + pub(super) model: &'a mut Model, } -impl<'a, SelectionMode: Default> EntityMut<'a, SelectionMode> +impl<'a, SelectionMode: Default, Message> EntityMut<'a, SelectionMode, Message> where - Model: Selectable, + Model: Selectable, { /// Activates the newly-inserted item. /// diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index 49003239c31..9dde8c10a83 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -10,7 +10,7 @@ pub use self::entity::EntityMut; mod selection; pub use self::selection::{MultiSelect, Selectable, SingleSelect}; -use crate::widget::Icon; +use crate::{Element, widget::Icon}; use slotmap::{SecondaryMap, SlotMap}; use std::any::{Any, TypeId}; use std::borrow::Cow; @@ -37,16 +37,16 @@ impl Default for Settings { } /// A model for single-select button selection. -pub type SingleSelectModel = Model; +pub type SingleSelectModel = Model; /// Single-select variant of an [`EntityMut`]. -pub type SingleSelectEntityMut<'a> = EntityMut<'a, SingleSelect>; +pub type SingleSelectEntityMut<'a, Message> = EntityMut<'a, SingleSelect, Message>; /// A model for multi-select button selection. -pub type MultiSelectModel = Model; +pub type MultiSelectModel = Model; /// Multi-select variant of an [`EntityMut`]. -pub type MultiSelectEntityMut<'a> = EntityMut<'a, MultiSelect>; +pub type MultiSelectEntityMut<'a, Message> = EntityMut<'a, MultiSelect, Message>; /// The portion of the model used only by the application. #[derive(Debug, Default)] @@ -54,10 +54,13 @@ pub(super) struct Storage(HashMap>>); /// The model held by the application, containing the unique IDs and data of each inserted item. #[derive(Default)] -pub struct Model { +pub struct Model { /// The content used for drawing segmented items. pub(super) items: SlotMap, + /// Elements optionally-defined for each item. + pub(super) elements: SecondaryMap>, + /// Icons optionally-defined for each item. pub(super) icons: SecondaryMap, @@ -77,7 +80,7 @@ pub struct Model { pub(super) storage: Storage, } -impl Model +impl Model where Self: Selectable, { @@ -110,7 +113,7 @@ where /// .build(); /// ``` #[must_use] - pub fn builder() -> ModelBuilder { + pub fn builder() -> ModelBuilder { ModelBuilder::default() } @@ -259,7 +262,7 @@ where /// let id = model.insert().text("Item A").icon("custom-icon").id(); /// ``` #[must_use] - pub fn insert(&mut self) -> EntityMut { + pub fn insert(&mut self) -> EntityMut { let id = self.items.insert(Settings::default()); self.order.push_back(id); EntityMut { model: self, id } diff --git a/src/widget/segmented_button/model/selection.rs b/src/widget/segmented_button/model/selection.rs index 1366c18ceea..1b10b851ba9 100644 --- a/src/widget/segmented_button/model/selection.rs +++ b/src/widget/segmented_button/model/selection.rs @@ -24,7 +24,7 @@ pub struct SingleSelect { pub active: Entity, } -impl Selectable for Model { +impl Selectable for Model { fn activate(&mut self, id: Entity) { if !self.items.contains_key(id) { return; @@ -44,7 +44,7 @@ impl Selectable for Model { } } -impl Model { +impl Model { /// Get an immutable reference to the data associated with the active item. #[must_use] pub fn active_data(&self) -> Option<&Data> { @@ -75,7 +75,7 @@ pub struct MultiSelect { pub active: HashSet, } -impl Selectable for Model { +impl Selectable for Model { fn activate(&mut self, id: Entity) { if !self.items.contains_key(id) { return; @@ -95,7 +95,7 @@ impl Selectable for Model { } } -impl Model { +impl Model { /// Deactivates the item in the model. pub fn deactivate(&mut self, id: Entity) { Selectable::deactivate(self, id); diff --git a/src/widget/segmented_button/vertical.rs b/src/widget/segmented_button/vertical.rs index 5b5d9ace972..dc7cd015295 100644 --- a/src/widget/segmented_button/vertical.rs +++ b/src/widget/segmented_button/vertical.rs @@ -21,10 +21,10 @@ pub type VerticalSegmentedButton<'a, SelectionMode, Message> = /// /// For details on the model, see the [`segmented_button`](super) module for more details. pub fn vertical( - model: &Model, + model: &Model, ) -> SegmentedButton where - Model: Selectable, + Model: Selectable, SelectionMode: Default, { SegmentedButton::new(model) @@ -33,7 +33,7 @@ where impl<'a, SelectionMode, Message> SegmentedVariant for SegmentedButton<'a, Vertical, SelectionMode, Message> where - Model: Selectable, + Model: Selectable, SelectionMode: Default, { fn variant_appearance( diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 73f7b365095..0560f93f362 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -71,12 +71,12 @@ pub trait SegmentedVariant { #[must_use] pub struct SegmentedButton<'a, Variant, SelectionMode, Message> where - Model: Selectable, + Model: Selectable, SelectionMode: Default, { /// The model borrowed from the application create this widget. #[setters(skip)] - pub(super) model: &'a Model, + pub(super) model: &'a Model, /// iced widget ID pub(super) id: Id, /// The icon used for the close button. @@ -151,10 +151,10 @@ where impl<'a, Variant, SelectionMode, Message> SegmentedButton<'a, Variant, SelectionMode, Message> where Self: SegmentedVariant, - Model: Selectable, + Model: Selectable, SelectionMode: Default, { - pub fn new(model: &'a Model) -> Self { + pub fn new(model: &'a Model) -> Self { Self { model, id: Id::unique(), @@ -536,7 +536,7 @@ impl<'a, Variant, SelectionMode, Message> Widget where Self: SegmentedVariant, - Model: Selectable, + Model: Selectable, SelectionMode: Default, Message: 'static + Clone, { @@ -1555,7 +1555,7 @@ impl<'a, Variant, SelectionMode, Message> From: SegmentedVariant, Variant: 'static, - Model: Selectable, + Model: Selectable, SelectionMode: Default, Message: 'static + Clone, { diff --git a/src/widget/segmented_control.rs b/src/widget/segmented_control.rs index 7ba7a40bad4..2aa6ec20848 100644 --- a/src/widget/segmented_control.rs +++ b/src/widget/segmented_control.rs @@ -15,10 +15,10 @@ use super::segmented_button::{ /// /// For details on the model, see the [`segmented_button`] module for more details. pub fn horizontal( - model: &Model, + model: &Model, ) -> HorizontalSegmentedButton where - Model: Selectable, + Model: Selectable, { let theme = crate::theme::active(); let space_s = theme.cosmic().space_s(); @@ -40,10 +40,10 @@ where /// /// For details on the model, see the [`segmented_button`] module for more details. pub fn vertical( - model: &Model, + model: &Model, ) -> VerticalSegmentedButton where - Model: Selectable, + Model: Selectable, SelectionMode: Default, { let theme = crate::theme::active(); diff --git a/src/widget/tab_bar.rs b/src/widget/tab_bar.rs index bcbb14d76cf..807b58b8a13 100644 --- a/src/widget/tab_bar.rs +++ b/src/widget/tab_bar.rs @@ -15,10 +15,10 @@ use super::segmented_button::{ /// /// For details on the model, see the [`segmented_button`] module for more details. pub fn horizontal( - model: &Model, + model: &Model, ) -> HorizontalSegmentedButton where - Model: Selectable, + Model: Selectable, { let theme = crate::theme::active(); let space_s = theme.cosmic().space_s(); @@ -38,10 +38,10 @@ where /// The data for the widget comes from a model that is maintained the application. /// For details on the model, see the [`segmented_button`] module for more details. pub fn vertical( - model: &Model, + model: &Model, ) -> VerticalSegmentedButton where - Model: Selectable, + Model: Selectable, SelectionMode: Default, { let theme = crate::theme::active(); From f081161d9723e56ca73254c67d8e843e6b8df710 Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 23 Apr 2024 09:25:13 -0600 Subject: [PATCH 2/2] Work around issues with derive(Default) --- src/widget/color_picker/mod.rs | 2 +- src/widget/segmented_button/model/builder.rs | 8 +++++++- src/widget/segmented_button/model/mod.rs | 17 ++++++++++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/widget/color_picker/mod.rs b/src/widget/color_picker/mod.rs index 30c8bcf8e4d..f3366b3ba41 100644 --- a/src/widget/color_picker/mod.rs +++ b/src/widget/color_picker/mod.rs @@ -122,7 +122,7 @@ impl ColorPickerModel { &self, f: T, icon_portion: Option, - ) -> crate::widget::Button<'a, Message, crate::Theme, crate::Renderer> { + ) -> crate::widget::Button<'a, Message, crate::Theme, crate::Renderer> where Message: 'static { color_button( Some(f(ColorPickerUpdate::ToggleColorPicker)), self.applied_color, diff --git a/src/widget/segmented_button/model/builder.rs b/src/widget/segmented_button/model/builder.rs index 2554b7923b0..00085c8ab56 100644 --- a/src/widget/segmented_button/model/builder.rs +++ b/src/widget/segmented_button/model/builder.rs @@ -8,9 +8,15 @@ use crate::widget::icon::Icon; use std::borrow::Cow; /// A builder for a [`Model`]. -#[derive(Default)] pub struct ModelBuilder(Model); +//TODO: Default derive ends up requiring Message to implement Default +impl Default for ModelBuilder { + fn default() -> Self { + Self(Model::default()) + } +} + /// Constructs a new item for the [`ModelBuilder`]. pub struct BuilderEntity { model: ModelBuilder, diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index 9dde8c10a83..b743e76bbd6 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -53,7 +53,6 @@ pub type MultiSelectEntityMut<'a, Message> = EntityMut<'a, MultiSelect, Message> pub(super) struct Storage(HashMap>>); /// The model held by the application, containing the unique IDs and data of each inserted item. -#[derive(Default)] pub struct Model { /// The content used for drawing segmented items. pub(super) items: SlotMap, @@ -80,6 +79,22 @@ pub struct Model { pub(super) storage: Storage, } +//TODO: Default derive ends up requiring Message to implement Default +impl Default for Model { + fn default() -> Self { + Self { + items: SlotMap::default(), + elements: SecondaryMap::default(), + icons: SecondaryMap::default(), + indents: SecondaryMap::default(), + text: SecondaryMap::default(), + order: VecDeque::default(), + selection: SelectionMode::default(), + storage: Storage::default(), + } + } +} + impl Model where Self: Selectable,