feat!(segmented-button): improved interfaces and documentation

BREAKING CHANGE: Various type and function names have changed to reflect
themselves better in documentation. Code has been reorganized into
separate modules with a better placement in libcosmic. Most of the
functions, types, and modules now have documentation and examples.

These changes no longer require the `Model` type to define the
data/component type that it stores. The component functionality is now
optional, and it's also possible to associate many components to an item
with one component per type. This has had a side effect of simplifying a
lot of the type signatures in the implementation.

Before, to insert an item into the model, you had to define a
`SegmentedItem` and a `Component` on insert, and get back an ID for that
item. Which makes it difficult to define an item that contains only an
icon or has no components. And requires an extra insert function to
activate the item on insert.

Now, there is a flexible builder-style API for configuring
newly-inserted items in the model. So the complexity for inserting and
retrieving values from the model has decreased significantly
This commit is contained in:
Michael Aaron Murphy 2023-01-17 18:49:40 +01:00 committed by Michael Murphy
parent 095e4c1acd
commit b3d550cc5e
22 changed files with 1097 additions and 675 deletions

View file

@ -0,0 +1,127 @@
// Copyright 2023 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0
use std::borrow::Cow;
use slotmap::{SecondaryMap, SparseSecondaryMap};
use crate::widget::IconSource;
use super::{Entity, Model, Selectable};
/// A newly-inserted item which may have additional actions applied to it.
pub struct EntityMut<'a, SelectionMode: Default> {
pub(super) id: Entity,
pub(super) model: &'a mut Model<SelectionMode>,
}
impl<'a, SelectionMode: Default> EntityMut<'a, SelectionMode>
where
Model<SelectionMode>: Selectable,
{
/// Activates the newly-inserted item.
///
/// ```ignore
/// model.insert().text("Item A").activate();
/// ```
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
pub fn activate(self) -> Self {
self.model.activate(self.id);
self
}
/// Associates extra data with an external secondary map.
///
/// The secondary map internally uses a `Vec`, so should only be used for data that
/// is commonly associated.
///
/// ```ignore
/// let mut secondary_data = segmented_button::SecondaryMap::default();
/// model.insert().text("Item A").secondary(&mut secondary_data, String::new("custom data"));
/// ```
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
pub fn secondary<Data>(self, map: &mut SecondaryMap<Entity, Data>, data: Data) -> Self {
map.insert(self.id, data);
self
}
/// Associates extra data with an external sparse secondary map.
///
/// Sparse maps internally use a `HashMap`, for data that is sparsely associated.
///
/// ```ignore
/// let mut secondary_data = segmented_button::SparseSecondaryMap::default();
/// model.insert().text("Item A").secondary(&mut secondary_data, String::new("custom data"));
/// ```
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
pub fn secondary_sparse<Data>(
self,
map: &mut SparseSecondaryMap<Entity, Data>,
data: Data,
) -> Self {
map.insert(self.id, data);
self
}
/// Associates data with the item.
///
/// There may only be one data component per Rust type.
///
/// ```ignore
/// model.insert().text("Item A").data(String::from("custom string"));
/// ```
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
pub fn data<Data: 'static>(self, data: Data) -> Self {
self.model.data_set(self.id, data);
self
}
/// Define an icon for the item.
///
/// ```ignore
/// model.insert().text("Item A").icon(IconSource::Name("icon-a".into()));
/// ```
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
pub fn icon(self, icon: impl Into<IconSource<'static>>) -> Self {
self.model.icon_set(self.id, icon);
self
}
/// Returns the ID of the item that was inserted.
///
/// ```ignore
/// let id = model.insert("Item A").id();
/// ```
#[must_use]
pub fn id(self) -> Entity {
self.id
}
/// Define the position of the item.
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
pub fn position(self, position: u16) -> Self {
self.model.position_set(self.id, position);
self
}
/// Swap the position with another item in the model.
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
pub fn position_swap(self, other: Entity) -> Self {
self.model.position_swap(self.id, other);
self
}
/// Defines the text for the item.
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
pub fn text(self, text: impl Into<Cow<'static, str>>) -> Self {
self.model.text_set(self.id, text);
self
}
/// Calls a function with the ID without consuming the wrapper.
#[allow(clippy::must_use_candidate, clippy::return_self_not_must_use)]
pub fn with_id(self, func: impl FnOnce(Entity)) -> Self {
func(self.id);
self
}
}