Compare commits

...
Sign in to create a new pull request.

2 commits

Author SHA1 Message Date
Jeremy Soller
f081161d97
Work around issues with derive(Default) 2024-04-23 09:25:13 -06:00
Jeremy Soller
9a0c338876
WIP: segmented_button::Model custom elements 2024-04-22 16:04:06 -06:00
12 changed files with 84 additions and 60 deletions

View file

@ -478,7 +478,7 @@ where
} }
/// Allows COSMIC to integrate with your application's [`nav_bar::Model`]. /// 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<Message<Self::Message>>> {
None None
} }

View file

@ -62,9 +62,9 @@ pub enum ColorPickerUpdate {
} }
#[derive(Setters)] #[derive(Setters)]
pub struct ColorPickerModel { pub struct ColorPickerModel<Message> {
#[setters(skip)] #[setters(skip)]
segmented_model: Model<SingleSelect>, segmented_model: Model<SingleSelect, Message>,
#[setters(skip)] #[setters(skip)]
active_color: palette::Hsv, active_color: palette::Hsv,
#[setters(skip)] #[setters(skip)]
@ -86,7 +86,7 @@ pub struct ColorPickerModel {
copied_at: Option<Instant>, copied_at: Option<Instant>,
} }
impl ColorPickerModel { impl<Message> ColorPickerModel<Message> {
#[must_use] #[must_use]
pub fn new( pub fn new(
hex: impl Into<Cow<'static, str>> + Clone, hex: impl Into<Cow<'static, str>> + Clone,
@ -118,11 +118,11 @@ impl ColorPickerModel {
/// Get a color picker button that displays the applied color /// 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, &self,
f: T, f: T,
icon_portion: Option<u16>, icon_portion: Option<u16>,
) -> crate::widget::Button<'a, Message, crate::Theme, crate::Renderer> { ) -> crate::widget::Button<'a, Message, crate::Theme, crate::Renderer> where Message: 'static {
color_button( color_button(
Some(f(ColorPickerUpdate::ToggleColorPicker)), Some(f(ColorPickerUpdate::ToggleColorPicker)),
self.applied_color, self.applied_color,
@ -130,7 +130,7 @@ impl ColorPickerModel {
) )
} }
pub fn update<Message>(&mut self, update: ColorPickerUpdate) -> Command<Message> { pub fn update(&mut self, update: ColorPickerUpdate) -> Command<Message> {
match update { match update {
ColorPickerUpdate::ActiveColor(c) => { ColorPickerUpdate::ActiveColor(c) => {
self.must_clear_cache.store(true, Ordering::SeqCst); self.must_clear_cache.store(true, Ordering::SeqCst);
@ -222,7 +222,7 @@ impl ColorPickerModel {
} }
#[must_use] #[must_use]
pub fn builder<Message>( pub fn builder(
&self, &self,
on_update: fn(ColorPickerUpdate) -> Message, on_update: fn(ColorPickerUpdate) -> Message,
) -> ColorPickerBuilder<Message> { ) -> ColorPickerBuilder<Message> {
@ -246,7 +246,7 @@ impl ColorPickerModel {
#[derive(Setters, Clone)] #[derive(Setters, Clone)]
pub struct ColorPickerBuilder<'a, Message> { pub struct ColorPickerBuilder<'a, Message> {
#[setters(skip)] #[setters(skip)]
model: &'a Model<SingleSelect>, model: &'a Model<SingleSelect, Message>,
#[setters(skip)] #[setters(skip)]
active_color: palette::Hsv, active_color: palette::Hsv,
#[setters(skip)] #[setters(skip)]

View file

@ -18,13 +18,13 @@ use crate::{theme, Theme};
use super::dnd_destination::DragId; use super::dnd_destination::DragId;
pub type Id = segmented_button::Entity; pub type Id = segmented_button::Entity;
pub type Model = segmented_button::SingleSelectModel; pub type Model<Message> = segmented_button::SingleSelectModel<Message>;
/// Navigation side panel for switching between views. /// Navigation side panel for switching between views.
/// ///
/// For details on the model, see the [`segmented_button`] module for more details. /// For details on the model, see the [`segmented_button`] module for more details.
pub fn nav_bar<Message: Clone + 'static>( pub fn nav_bar<Message: Clone + 'static>(
model: &segmented_button::SingleSelectModel, model: &Model<Message>,
on_activate: fn(segmented_button::Entity) -> Message, on_activate: fn(segmented_button::Entity) -> Message,
) -> NavBar<Message> { ) -> NavBar<Message> {
NavBar { NavBar {
@ -35,7 +35,7 @@ pub fn nav_bar<Message: Clone + 'static>(
/// Navigation side panel for switching between views. /// Navigation side panel for switching between views.
/// Can receive drag and drop events. /// Can receive drag and drop events.
pub fn nav_bar_dnd<Message, D: AllowedMimeTypes>( pub fn nav_bar_dnd<Message, D: AllowedMimeTypes>(
model: &segmented_button::SingleSelectModel, model: &Model<Message>,
on_activate: fn(segmented_button::Entity) -> Message, on_activate: fn(segmented_button::Entity) -> Message,
on_dnd_enter: impl Fn(segmented_button::Entity, Vec<String>) -> Message + 'static, on_dnd_enter: impl Fn(segmented_button::Entity, Vec<String>) -> Message + 'static,
on_dnd_leave: impl Fn(segmented_button::Entity) -> Message + 'static, on_dnd_leave: impl Fn(segmented_button::Entity) -> Message + 'static,

View file

@ -22,10 +22,10 @@ pub struct Horizontal;
/// ///
/// For details on the model, see the [`segmented_button`](super) module for more details. /// For details on the model, see the [`segmented_button`](super) module for more details.
pub fn horizontal<SelectionMode: Default, Message>( pub fn horizontal<SelectionMode: Default, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode, Message>,
) -> SegmentedButton<Horizontal, SelectionMode, Message> ) -> SegmentedButton<Horizontal, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
{ {
SegmentedButton::new(model) SegmentedButton::new(model)
} }
@ -33,7 +33,7 @@ where
impl<'a, SelectionMode, Message> SegmentedVariant impl<'a, SelectionMode, Message> SegmentedVariant
for SegmentedButton<'a, Horizontal, SelectionMode, Message> for SegmentedButton<'a, Horizontal, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
SelectionMode: Default, SelectionMode: Default,
{ {
fn variant_appearance( fn variant_appearance(

View file

@ -8,38 +8,44 @@ use crate::widget::icon::Icon;
use std::borrow::Cow; use std::borrow::Cow;
/// A builder for a [`Model`]. /// A builder for a [`Model`].
#[derive(Default)] pub struct ModelBuilder<SelectionMode: Default, Message>(Model<SelectionMode, Message>);
pub struct ModelBuilder<SelectionMode: Default>(Model<SelectionMode>);
//TODO: Default derive ends up requiring Message to implement Default
impl<SelectionMode: Default, Message> Default for ModelBuilder<SelectionMode, Message> {
fn default() -> Self {
Self(Model::default())
}
}
/// Constructs a new item for the [`ModelBuilder`]. /// Constructs a new item for the [`ModelBuilder`].
pub struct BuilderEntity<SelectionMode: Default> { pub struct BuilderEntity<SelectionMode: Default, Message> {
model: ModelBuilder<SelectionMode>, model: ModelBuilder<SelectionMode, Message>,
id: Entity, id: Entity,
} }
impl<SelectionMode: Default> ModelBuilder<SelectionMode> impl<SelectionMode: Default, Message> ModelBuilder<SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
{ {
/// Inserts a new item and its associated data into the model. /// Inserts a new item and its associated data into the model.
#[must_use] #[must_use]
pub fn insert( pub fn insert(
mut self, mut self,
builder: impl Fn(BuilderEntity<SelectionMode>) -> BuilderEntity<SelectionMode>, builder: impl Fn(BuilderEntity<SelectionMode, Message>) -> BuilderEntity<SelectionMode, Message>,
) -> Self { ) -> Self {
let id = self.0.insert().id(); let id = self.0.insert().id();
builder(BuilderEntity { model: self, id }).model builder(BuilderEntity { model: self, id }).model
} }
/// Consumes the builder and returns the model. /// Consumes the builder and returns the model.
pub fn build(self) -> Model<SelectionMode> { pub fn build(self) -> Model<SelectionMode, Message> {
self.0 self.0
} }
} }
impl<SelectionMode: Default> BuilderEntity<SelectionMode> impl<SelectionMode: Default, Message> BuilderEntity<SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
{ {
/// Activates the newly-inserted item. /// Activates the newly-inserted item.
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)] #[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]

View file

@ -10,14 +10,14 @@ use crate::widget::Icon;
use super::{Entity, Model, Selectable}; use super::{Entity, Model, Selectable};
/// A newly-inserted item which may have additional actions applied to it. /// 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) id: Entity,
pub(super) model: &'a mut Model<SelectionMode>, pub(super) model: &'a mut Model<SelectionMode, Message>,
} }
impl<'a, SelectionMode: Default> EntityMut<'a, SelectionMode> impl<'a, SelectionMode: Default, Message> EntityMut<'a, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
{ {
/// Activates the newly-inserted item. /// Activates the newly-inserted item.
/// ///

View file

@ -10,7 +10,7 @@ pub use self::entity::EntityMut;
mod selection; mod selection;
pub use self::selection::{MultiSelect, Selectable, SingleSelect}; pub use self::selection::{MultiSelect, Selectable, SingleSelect};
use crate::widget::Icon; use crate::{Element, widget::Icon};
use slotmap::{SecondaryMap, SlotMap}; use slotmap::{SecondaryMap, SlotMap};
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::borrow::Cow; use std::borrow::Cow;
@ -37,27 +37,29 @@ impl Default for Settings {
} }
/// A model for single-select button selection. /// A model for single-select button selection.
pub type SingleSelectModel = Model<SingleSelect>; pub type SingleSelectModel<Message> = Model<SingleSelect, Message>;
/// Single-select variant of an [`EntityMut`]. /// 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. /// A model for multi-select button selection.
pub type MultiSelectModel = Model<MultiSelect>; pub type MultiSelectModel<Message> = Model<MultiSelect, Message>;
/// Multi-select variant of an [`EntityMut`]. /// 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. /// The portion of the model used only by the application.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub(super) struct Storage(HashMap<TypeId, SecondaryMap<Entity, Box<dyn Any>>>); pub(super) struct Storage(HashMap<TypeId, SecondaryMap<Entity, Box<dyn Any>>>);
/// The model held by the application, containing the unique IDs and data of each inserted item. /// The model held by the application, containing the unique IDs and data of each inserted item.
#[derive(Default)] pub struct Model<SelectionMode: Default, Message> {
pub struct Model<SelectionMode: Default> {
/// The content used for drawing segmented items. /// The content used for drawing segmented items.
pub(super) items: SlotMap<Entity, Settings>, pub(super) items: SlotMap<Entity, Settings>,
/// Elements optionally-defined for each item.
pub(super) elements: SecondaryMap<Entity, Element<'static, Message>>,
/// Icons optionally-defined for each item. /// Icons optionally-defined for each item.
pub(super) icons: SecondaryMap<Entity, Icon>, pub(super) icons: SecondaryMap<Entity, Icon>,
@ -77,7 +79,23 @@ pub struct Model<SelectionMode: Default> {
pub(super) storage: Storage, pub(super) storage: Storage,
} }
impl<SelectionMode: Default> Model<SelectionMode> //TODO: Default derive ends up requiring Message to implement Default
impl<SelectionMode: Default, Message> Default for Model<SelectionMode, Message> {
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<SelectionMode: Default, Message> Model<SelectionMode, Message>
where where
Self: Selectable, Self: Selectable,
{ {
@ -110,7 +128,7 @@ where
/// .build(); /// .build();
/// ``` /// ```
#[must_use] #[must_use]
pub fn builder() -> ModelBuilder<SelectionMode> { pub fn builder() -> ModelBuilder<SelectionMode, Message> {
ModelBuilder::default() ModelBuilder::default()
} }
@ -259,7 +277,7 @@ where
/// let id = model.insert().text("Item A").icon("custom-icon").id(); /// let id = model.insert().text("Item A").icon("custom-icon").id();
/// ``` /// ```
#[must_use] #[must_use]
pub fn insert(&mut self) -> EntityMut<SelectionMode> { pub fn insert(&mut self) -> EntityMut<SelectionMode, Message> {
let id = self.items.insert(Settings::default()); let id = self.items.insert(Settings::default());
self.order.push_back(id); self.order.push_back(id);
EntityMut { model: self, id } EntityMut { model: self, id }

View file

@ -24,7 +24,7 @@ pub struct SingleSelect {
pub active: Entity, pub active: Entity,
} }
impl Selectable for Model<SingleSelect> { impl<Message> Selectable for Model<SingleSelect, Message> {
fn activate(&mut self, id: Entity) { fn activate(&mut self, id: Entity) {
if !self.items.contains_key(id) { if !self.items.contains_key(id) {
return; return;
@ -44,7 +44,7 @@ impl Selectable for Model<SingleSelect> {
} }
} }
impl Model<SingleSelect> { impl<Message> Model<SingleSelect, Message> {
/// Get an immutable reference to the data associated with the active item. /// Get an immutable reference to the data associated with the active item.
#[must_use] #[must_use]
pub fn active_data<Data: 'static>(&self) -> Option<&Data> { pub fn active_data<Data: 'static>(&self) -> Option<&Data> {
@ -75,7 +75,7 @@ pub struct MultiSelect {
pub active: HashSet<Entity>, pub active: HashSet<Entity>,
} }
impl Selectable for Model<MultiSelect> { impl<Message> Selectable for Model<MultiSelect, Message> {
fn activate(&mut self, id: Entity) { fn activate(&mut self, id: Entity) {
if !self.items.contains_key(id) { if !self.items.contains_key(id) {
return; return;
@ -95,7 +95,7 @@ impl Selectable for Model<MultiSelect> {
} }
} }
impl Model<MultiSelect> { impl<Message> Model<MultiSelect, Message> {
/// Deactivates the item in the model. /// Deactivates the item in the model.
pub fn deactivate(&mut self, id: Entity) { pub fn deactivate(&mut self, id: Entity) {
Selectable::deactivate(self, id); Selectable::deactivate(self, id);

View file

@ -21,10 +21,10 @@ pub type VerticalSegmentedButton<'a, SelectionMode, Message> =
/// ///
/// For details on the model, see the [`segmented_button`](super) module for more details. /// For details on the model, see the [`segmented_button`](super) module for more details.
pub fn vertical<SelectionMode, Message>( pub fn vertical<SelectionMode, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode, Message>,
) -> SegmentedButton<Vertical, SelectionMode, Message> ) -> SegmentedButton<Vertical, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
SelectionMode: Default, SelectionMode: Default,
{ {
SegmentedButton::new(model) SegmentedButton::new(model)
@ -33,7 +33,7 @@ where
impl<'a, SelectionMode, Message> SegmentedVariant impl<'a, SelectionMode, Message> SegmentedVariant
for SegmentedButton<'a, Vertical, SelectionMode, Message> for SegmentedButton<'a, Vertical, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
SelectionMode: Default, SelectionMode: Default,
{ {
fn variant_appearance( fn variant_appearance(

View file

@ -71,12 +71,12 @@ pub trait SegmentedVariant {
#[must_use] #[must_use]
pub struct SegmentedButton<'a, Variant, SelectionMode, Message> pub struct SegmentedButton<'a, Variant, SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
SelectionMode: Default, SelectionMode: Default,
{ {
/// The model borrowed from the application create this widget. /// The model borrowed from the application create this widget.
#[setters(skip)] #[setters(skip)]
pub(super) model: &'a Model<SelectionMode>, pub(super) model: &'a Model<SelectionMode, Message>,
/// iced widget ID /// iced widget ID
pub(super) id: Id, pub(super) id: Id,
/// The icon used for the close button. /// The icon used for the close button.
@ -151,10 +151,10 @@ where
impl<'a, Variant, SelectionMode, Message> SegmentedButton<'a, Variant, SelectionMode, Message> impl<'a, Variant, SelectionMode, Message> SegmentedButton<'a, Variant, SelectionMode, Message>
where where
Self: SegmentedVariant, Self: SegmentedVariant,
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
SelectionMode: Default, SelectionMode: Default,
{ {
pub fn new(model: &'a Model<SelectionMode>) -> Self { pub fn new(model: &'a Model<SelectionMode, Message>) -> Self {
Self { Self {
model, model,
id: Id::unique(), id: Id::unique(),
@ -536,7 +536,7 @@ impl<'a, Variant, SelectionMode, Message> Widget<Message, crate::Theme, Renderer
for SegmentedButton<'a, Variant, SelectionMode, Message> for SegmentedButton<'a, Variant, SelectionMode, Message>
where where
Self: SegmentedVariant, Self: SegmentedVariant,
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
SelectionMode: Default, SelectionMode: Default,
Message: 'static + Clone, Message: 'static + Clone,
{ {
@ -1555,7 +1555,7 @@ impl<'a, Variant, SelectionMode, Message> From<SegmentedButton<'a, Variant, Sele
where where
SegmentedButton<'a, Variant, SelectionMode, Message>: SegmentedVariant, SegmentedButton<'a, Variant, SelectionMode, Message>: SegmentedVariant,
Variant: 'static, Variant: 'static,
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
SelectionMode: Default, SelectionMode: Default,
Message: 'static + Clone, Message: 'static + Clone,
{ {

View file

@ -15,10 +15,10 @@ use super::segmented_button::{
/// ///
/// For details on the model, see the [`segmented_button`] module for more details. /// For details on the model, see the [`segmented_button`] module for more details.
pub fn horizontal<SelectionMode: Default, Message>( pub fn horizontal<SelectionMode: Default, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode, Message>,
) -> HorizontalSegmentedButton<SelectionMode, Message> ) -> HorizontalSegmentedButton<SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
{ {
let theme = crate::theme::active(); let theme = crate::theme::active();
let space_s = theme.cosmic().space_s(); 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. /// For details on the model, see the [`segmented_button`] module for more details.
pub fn vertical<SelectionMode, Message>( pub fn vertical<SelectionMode, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode, Message>,
) -> VerticalSegmentedButton<SelectionMode, Message> ) -> VerticalSegmentedButton<SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
SelectionMode: Default, SelectionMode: Default,
{ {
let theme = crate::theme::active(); let theme = crate::theme::active();

View file

@ -15,10 +15,10 @@ use super::segmented_button::{
/// ///
/// For details on the model, see the [`segmented_button`] module for more details. /// For details on the model, see the [`segmented_button`] module for more details.
pub fn horizontal<SelectionMode: Default, Message>( pub fn horizontal<SelectionMode: Default, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode, Message>,
) -> HorizontalSegmentedButton<SelectionMode, Message> ) -> HorizontalSegmentedButton<SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
{ {
let theme = crate::theme::active(); let theme = crate::theme::active();
let space_s = theme.cosmic().space_s(); 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. /// 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. /// For details on the model, see the [`segmented_button`] module for more details.
pub fn vertical<SelectionMode, Message>( pub fn vertical<SelectionMode, Message>(
model: &Model<SelectionMode>, model: &Model<SelectionMode, Message>,
) -> VerticalSegmentedButton<SelectionMode, Message> ) -> VerticalSegmentedButton<SelectionMode, Message>
where where
Model<SelectionMode>: Selectable, Model<SelectionMode, Message>: Selectable,
SelectionMode: Default, SelectionMode: Default,
{ {
let theme = crate::theme::active(); let theme = crate::theme::active();