feat: Re-orderable positioning of items in segmented button

Calling `model.swap_position(key1, key2)` will swap the positions of
these two items in the model.
This commit is contained in:
Michael Aaron Murphy 2023-01-09 19:26:31 +01:00 committed by Michael Murphy
parent a55f41fc42
commit c4bd0fa3d8
3 changed files with 68 additions and 22 deletions

View file

@ -63,6 +63,7 @@ pub struct State {
pub icon_theme: segmented_button::SingleSelectModel<&'static str>, pub icon_theme: segmented_button::SingleSelectModel<&'static str>,
pub multi_selection: segmented_button::MultiSelectModel<MultiOption>, pub multi_selection: segmented_button::MultiSelectModel<MultiOption>,
pub pick_list_selected: Option<&'static str>, pub pick_list_selected: Option<&'static str>,
pub pick_list_options: Vec<&'static str>,
pub selection: segmented_button::SingleSelectModel<()>, pub selection: segmented_button::SingleSelectModel<()>,
pub slider_value: f32, pub slider_value: f32,
pub spin_button: SpinButtonModel<i32>, pub spin_button: SpinButtonModel<i32>,
@ -75,6 +76,7 @@ impl Default for State {
State { State {
checkbox_value: false, checkbox_value: false,
pick_list_selected: Some("Option 1"), pick_list_selected: Some("Option 1"),
pick_list_options: vec!["Option 1", "Option 2", "Option 3", "Option 4"],
slider_value: 50.0, slider_value: 50.0,
spin_button: SpinButtonModel::default().min(-10).max(10), spin_button: SpinButtonModel::default().min(-10).max(10),
toggler_value: false, toggler_value: false,
@ -200,7 +202,7 @@ impl State {
.add(settings::item( .add(settings::item(
"Pick List (TODO)", "Pick List (TODO)",
pick_list( pick_list(
vec!["Option 1", "Option 2", "Option 3", "Option 4"], &self.pick_list_options,
self.pick_list_selected, self.pick_list_selected,
Message::PickListSelected, Message::PickListSelected,
) )

View file

@ -1,6 +1,8 @@
// Copyright 2022 System76 <info@system76.com> // Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
use std::collections::VecDeque;
use super::selection_modes::{MultiSelect, Selectable, SingleSelect}; use super::selection_modes::{MultiSelect, Selectable, SingleSelect};
use super::SegmentedItem; use super::SegmentedItem;
use slotmap::{SecondaryMap, SlotMap}; use slotmap::{SecondaryMap, SlotMap};
@ -38,30 +40,37 @@ pub struct WidgetModel<SelectionMode> {
/// The content used for drawing segmented items. /// The content used for drawing segmented items.
pub(super) items: SlotMap<Key, SegmentedItem>, pub(super) items: SlotMap<Key, SegmentedItem>,
/// Order which the items will be displayed.
pub(super) order: VecDeque<Key>,
/// Manages selections /// Manages selections
pub(super) selection: SelectionMode, pub(super) selection: SelectionMode,
} }
impl<Component> Model<SingleSelect, Component> { impl<Component> Model<SingleSelect, Component> {
/// Activates the item in the model.
pub fn activate(&mut self, key: Key) { pub fn activate(&mut self, key: Key) {
self.widget.selection.active = key; self.widget.selection.active = key;
} }
/// Get an immutable reference to the component associated with the active item.
#[must_use] #[must_use]
pub fn active_component(&self) -> Option<&Component> { pub fn active_component(&self) -> Option<&Component> {
self.component(self.active()) self.component(self.active())
} }
/// Get a mutable reference to the component associated with the active item.
#[must_use] #[must_use]
pub fn active_component_mut(&mut self) -> Option<&mut Component> { pub fn active_component_mut(&mut self) -> Option<&mut Component> {
self.component_mut(self.active()) self.component_mut(self.active())
} }
/// Deactivates the active item.
pub fn deactivate(&mut self) { pub fn deactivate(&mut self) {
self.widget.selection.active = Key::default(); self.widget.selection.active = Key::default();
} }
/// The ID of the active button. /// The ID of the active item.
#[must_use] #[must_use]
pub fn active(&self) -> Key { pub fn active(&self) -> Key {
self.widget.selection.active self.widget.selection.active
@ -69,12 +78,14 @@ impl<Component> Model<SingleSelect, Component> {
} }
impl<Component> Model<MultiSelect, Component> { impl<Component> Model<MultiSelect, Component> {
/// Activates the item in the model.
pub fn activate(&mut self, key: Key) { pub fn activate(&mut self, key: Key) {
if !self.widget.selection.active.insert(key) { if !self.widget.selection.active.insert(key) {
self.widget.selection.active.remove(&key); self.widget.selection.active.remove(&key);
} }
} }
/// Deactivates the item in the model.
pub fn deactivate(&mut self, key: Key) { pub fn deactivate(&mut self, key: Key) {
self.widget.selection.active.remove(&key); self.widget.selection.active.remove(&key);
} }
@ -89,6 +100,7 @@ impl<SelectionMode, Component> Model<SelectionMode, Component>
where where
SelectionMode: Selectable, SelectionMode: Selectable,
{ {
/// Creates a builder for initializing a model.
#[must_use] #[must_use]
pub fn builder() -> ModelBuilder<SelectionMode, Component> { pub fn builder() -> ModelBuilder<SelectionMode, Component> {
ModelBuilder(Self { ModelBuilder(Self {
@ -103,34 +115,37 @@ where
Batch(self) Batch(self)
} }
/// Enables or disables a button /// Get an immutable reference to an item in the model.
#[must_use] #[must_use]
pub fn content(&self, key: Key) -> Option<&SegmentedItem> { pub fn content(&self, key: Key) -> Option<&SegmentedItem> {
self.widget.items.get(key) self.widget.items.get(key)
} }
/// Enables or disables a button /// Get a mutable reference to an item in the model.
#[must_use] #[must_use]
pub fn content_mut(&mut self, key: Key) -> Option<&mut SegmentedItem> { pub fn item_mut(&mut self, key: Key) -> Option<&mut SegmentedItem> {
self.widget.items.get_mut(key) self.widget.items.get_mut(key)
} }
/// Get an immutable reference to a component associated with an item.
pub fn component(&self, key: Key) -> Option<&Component> { pub fn component(&self, key: Key) -> Option<&Component> {
self.app.0.get(key) self.app.0.get(key)
} }
/// Get a mutable reference to a component associated with an item.
pub fn component_mut(&mut self, key: Key) -> Option<&mut Component> { pub fn component_mut(&mut self, key: Key) -> Option<&mut Component> {
self.app.0.get_mut(key) self.app.0.get_mut(key)
} }
/// Insert a new button. /// Insert a new item in the model.
pub fn insert(&mut self, content: impl Into<SegmentedItem>, component: Component) -> Key { pub fn insert(&mut self, content: impl Into<SegmentedItem>, component: Component) -> Key {
let key = self.widget.items.insert(content.into()); let key = self.widget.items.insert(content.into());
self.widget.order.push_back(key);
self.app.0.insert(key, component); self.app.0.insert(key, component);
key key
} }
/// Inserts and activates a button. /// Inserts and activates an item into the model.
pub fn insert_active( pub fn insert_active(
&mut self, &mut self,
content: impl Into<SegmentedItem>, content: impl Into<SegmentedItem>,
@ -141,27 +156,53 @@ where
key key
} }
/// Checks if the item is active in the model.
#[must_use] #[must_use]
pub fn is_active(&self, key: Key) -> bool { pub fn is_active(&self, key: Key) -> bool {
self.widget.selection.is_active(key) self.widget.selection.is_active(key)
} }
/// Removes a button. /// The position of the item in the model.
pub fn position(&self, key: Key) -> Option<usize> {
self.widget.order.iter().position(|k| *k == key)
}
/// Removes an item from the model.
pub fn remove(&mut self, key: Key) { pub fn remove(&mut self, key: Key) {
self.widget.items.remove(key); self.widget.items.remove(key);
self.widget.selection.deactivate(key); self.widget.selection.deactivate(key);
self.app.0.remove(key);
if let Some(index) = self.position(key) {
self.widget.order.remove(index);
}
}
/// Swap the position of two items in the model.
pub fn swap_position(&mut self, first: Key, second: Key) {
let Some(first_index) = self.position(first) else {
return
};
let Some(second_index) = self.position(second) else {
return
};
self.widget.order.swap(first_index, second_index);
} }
} }
pub struct ModelBuilder<SelectionMode, Component>(Model<SelectionMode, Component>); pub struct ModelBuilder<SelectionMode, Component>(Model<SelectionMode, Component>);
impl<SelectionMode: Selectable, Component> ModelBuilder<SelectionMode, Component> { impl<SelectionMode: Selectable, Component> ModelBuilder<SelectionMode, Component> {
/// Inserts a new item and its associated component into the model.
#[must_use] #[must_use]
pub fn insert(mut self, content: impl Into<SegmentedItem>, component: Component) -> Self { pub fn insert(mut self, content: impl Into<SegmentedItem>, component: Component) -> Self {
self.0.insert(content, component); self.0.insert(content, component);
self self
} }
/// Inserts and activates an new item.
#[must_use] #[must_use]
pub fn insert_active( pub fn insert_active(
mut self, mut self,

View file

@ -148,21 +148,22 @@ where
/// Focus the previous item in the widget. /// Focus the previous item in the widget.
fn focus_previous(&mut self, state: &mut LocalState) -> event::Status { fn focus_previous(&mut self, state: &mut LocalState) -> event::Status {
let mut previous_key = None; let mut keys = self.model.order.iter().copied().rev();
for key in self.model.items.keys() { while let Some(key) = keys.next() {
if key == state.focused_key { if key == state.focused_key {
if let Some(key) = previous_key { for key in keys {
// Skip disabled buttons.
if !self.is_enabled(key) {
continue;
}
state.focused_key = key; state.focused_key = key;
return event::Status::Captured; return event::Status::Captured;
} }
break; break;
} }
if self.is_enabled(key) {
previous_key = Some(key);
}
} }
state.focused_key = Key::default(); state.focused_key = Key::default();
@ -171,7 +172,7 @@ where
/// Focus the next item in the widget. /// Focus the next item in the widget.
fn focus_next(&mut self, state: &mut LocalState) -> event::Status { fn focus_next(&mut self, state: &mut LocalState) -> event::Status {
let mut keys = self.model.items.keys(); let mut keys = self.model.order.iter().copied();
while let Some(key) = keys.next() { while let Some(key) = keys.next() {
if key == state.focused_key { if key == state.focused_key {
@ -258,7 +259,7 @@ where
fn state(&self) -> tree::State { fn state(&self) -> tree::State {
tree::State::new(LocalState { tree::State::new(LocalState {
first: self.model.items.keys().next().unwrap_or_default(), first: self.model.order.iter().copied().next().unwrap_or_default(),
..LocalState::default() ..LocalState::default()
}) })
} }
@ -289,10 +290,10 @@ where
let state = tree.state.downcast_mut::<LocalState>(); let state = tree.state.downcast_mut::<LocalState>();
if bounds.contains(cursor_position) { if bounds.contains(cursor_position) {
for (nth, (key, content)) in self.model.items.iter().enumerate() { for (nth, key) in self.model.order.iter().copied().enumerate() {
let bounds = self.variant_button_bounds(bounds, nth); let bounds = self.variant_button_bounds(bounds, nth);
if bounds.contains(cursor_position) { if bounds.contains(cursor_position) {
if content.enabled { if self.model.items[key].enabled {
if let Some(on_activate) = self.on_activate.as_ref() { if let Some(on_activate) = self.on_activate.as_ref() {
if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
| Event::Touch(touch::Event::FingerLifted { .. }) = event | Event::Touch(touch::Event::FingerLifted { .. }) = event
@ -363,12 +364,12 @@ where
let bounds = layout.bounds(); let bounds = layout.bounds();
if bounds.contains(cursor_position) { if bounds.contains(cursor_position) {
for (nth, content) in self.model.items.values().enumerate() { for (nth, key) in self.model.order.iter().copied().enumerate() {
if self if self
.variant_button_bounds(bounds, nth) .variant_button_bounds(bounds, nth)
.contains(cursor_position) .contains(cursor_position)
{ {
return if content.enabled { return if self.model.items[key].enabled {
iced_native::mouse::Interaction::Pointer iced_native::mouse::Interaction::Pointer
} else { } else {
iced_native::mouse::Interaction::Idle iced_native::mouse::Interaction::Idle
@ -410,7 +411,9 @@ where
} }
// Draw each of the items in the widget. // Draw each of the items in the widget.
for (nth, (key, content)) in self.model.items.iter().enumerate() { for (nth, key) in self.model.order.iter().copied().enumerate() {
let content = &self.model.items[key];
let mut bounds = self.variant_button_bounds(bounds, nth); let mut bounds = self.variant_button_bounds(bounds, nth);
let (status_appearance, font) = if state.focused_key == key { let (status_appearance, font) = if state.focused_key == key {