From c4bd0fa3d8b248dd9a009ec11998004bb8a1da50 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Mon, 9 Jan 2023 19:26:31 +0100 Subject: [PATCH] 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. --- examples/cosmic/src/window/demo.rs | 4 +- src/widget/segmented_button/model.rs | 55 +++++++++++++++++++++++---- src/widget/segmented_button/widget.rs | 31 ++++++++------- 3 files changed, 68 insertions(+), 22 deletions(-) diff --git a/examples/cosmic/src/window/demo.rs b/examples/cosmic/src/window/demo.rs index db407932..25f32a9c 100644 --- a/examples/cosmic/src/window/demo.rs +++ b/examples/cosmic/src/window/demo.rs @@ -63,6 +63,7 @@ pub struct State { pub icon_theme: segmented_button::SingleSelectModel<&'static str>, pub multi_selection: segmented_button::MultiSelectModel, pub pick_list_selected: Option<&'static str>, + pub pick_list_options: Vec<&'static str>, pub selection: segmented_button::SingleSelectModel<()>, pub slider_value: f32, pub spin_button: SpinButtonModel, @@ -75,6 +76,7 @@ impl Default for State { State { checkbox_value: false, pick_list_selected: Some("Option 1"), + pick_list_options: vec!["Option 1", "Option 2", "Option 3", "Option 4"], slider_value: 50.0, spin_button: SpinButtonModel::default().min(-10).max(10), toggler_value: false, @@ -200,7 +202,7 @@ impl State { .add(settings::item( "Pick List (TODO)", pick_list( - vec!["Option 1", "Option 2", "Option 3", "Option 4"], + &self.pick_list_options, self.pick_list_selected, Message::PickListSelected, ) diff --git a/src/widget/segmented_button/model.rs b/src/widget/segmented_button/model.rs index c4d7caa5..e57bf0da 100644 --- a/src/widget/segmented_button/model.rs +++ b/src/widget/segmented_button/model.rs @@ -1,6 +1,8 @@ // Copyright 2022 System76 // SPDX-License-Identifier: MPL-2.0 +use std::collections::VecDeque; + use super::selection_modes::{MultiSelect, Selectable, SingleSelect}; use super::SegmentedItem; use slotmap::{SecondaryMap, SlotMap}; @@ -38,30 +40,37 @@ pub struct WidgetModel { /// The content used for drawing segmented items. pub(super) items: SlotMap, + /// Order which the items will be displayed. + pub(super) order: VecDeque, + /// Manages selections pub(super) selection: SelectionMode, } impl Model { + /// Activates the item in the model. pub fn activate(&mut self, key: Key) { self.widget.selection.active = key; } + /// Get an immutable reference to the component associated with the active item. #[must_use] pub fn active_component(&self) -> Option<&Component> { self.component(self.active()) } + /// Get a mutable reference to the component associated with the active item. #[must_use] pub fn active_component_mut(&mut self) -> Option<&mut Component> { self.component_mut(self.active()) } + /// Deactivates the active item. pub fn deactivate(&mut self) { self.widget.selection.active = Key::default(); } - /// The ID of the active button. + /// The ID of the active item. #[must_use] pub fn active(&self) -> Key { self.widget.selection.active @@ -69,12 +78,14 @@ impl Model { } impl Model { + /// Activates the item in the model. pub fn activate(&mut self, key: Key) { if !self.widget.selection.active.insert(key) { self.widget.selection.active.remove(&key); } } + /// Deactivates the item in the model. pub fn deactivate(&mut self, key: Key) { self.widget.selection.active.remove(&key); } @@ -89,6 +100,7 @@ impl Model where SelectionMode: Selectable, { + /// Creates a builder for initializing a model. #[must_use] pub fn builder() -> ModelBuilder { ModelBuilder(Self { @@ -103,34 +115,37 @@ where Batch(self) } - /// Enables or disables a button + /// Get an immutable reference to an item in the model. #[must_use] pub fn content(&self, key: Key) -> Option<&SegmentedItem> { self.widget.items.get(key) } - /// Enables or disables a button + /// Get a mutable reference to an item in the model. #[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) } + /// Get an immutable reference to a component associated with an item. pub fn component(&self, key: Key) -> Option<&Component> { 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> { 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, component: Component) -> Key { let key = self.widget.items.insert(content.into()); + self.widget.order.push_back(key); self.app.0.insert(key, component); key } - /// Inserts and activates a button. + /// Inserts and activates an item into the model. pub fn insert_active( &mut self, content: impl Into, @@ -141,27 +156,53 @@ where key } + /// Checks if the item is active in the model. #[must_use] pub fn is_active(&self, key: Key) -> bool { self.widget.selection.is_active(key) } - /// Removes a button. + /// The position of the item in the model. + pub fn position(&self, key: Key) -> Option { + self.widget.order.iter().position(|k| *k == key) + } + + /// Removes an item from the model. pub fn remove(&mut self, key: Key) { self.widget.items.remove(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(Model); impl ModelBuilder { + /// Inserts a new item and its associated component into the model. #[must_use] pub fn insert(mut self, content: impl Into, component: Component) -> Self { self.0.insert(content, component); self } + /// Inserts and activates an new item. #[must_use] pub fn insert_active( mut self, diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 2d5e2b44..1e2fff67 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -148,21 +148,22 @@ where /// Focus the previous item in the widget. 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 let Some(key) = previous_key { + for key in keys { + // Skip disabled buttons. + if !self.is_enabled(key) { + continue; + } + state.focused_key = key; return event::Status::Captured; } break; } - - if self.is_enabled(key) { - previous_key = Some(key); - } } state.focused_key = Key::default(); @@ -171,7 +172,7 @@ where /// Focus the next item in the widget. 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() { if key == state.focused_key { @@ -258,7 +259,7 @@ where fn state(&self) -> tree::State { 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() }) } @@ -289,10 +290,10 @@ where let state = tree.state.downcast_mut::(); 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); 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 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) | Event::Touch(touch::Event::FingerLifted { .. }) = event @@ -363,12 +364,12 @@ where let bounds = layout.bounds(); 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 .variant_button_bounds(bounds, nth) .contains(cursor_position) { - return if content.enabled { + return if self.model.items[key].enabled { iced_native::mouse::Interaction::Pointer } else { iced_native::mouse::Interaction::Idle @@ -410,7 +411,9 @@ where } // 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 (status_appearance, font) = if state.focused_key == key {