feat: focusable segmented items in segmented button
This commit is contained in:
parent
a89ec01297
commit
29c7444a30
14 changed files with 794 additions and 611 deletions
|
|
@ -10,23 +10,24 @@ use iced_core::Color;
|
|||
|
||||
use crate::{theme, Theme};
|
||||
|
||||
use super::segmented_button::{self, cosmic::vertical_view_switcher, SingleSelect};
|
||||
use super::segmented_button::{self, vertical_segmented_button};
|
||||
|
||||
/// A container holding a vertical view switcher with the n style
|
||||
pub fn nav_bar<Data, Message>(
|
||||
state: &segmented_button::State<SingleSelect, Data>,
|
||||
pub fn nav_bar<Component, Message>(
|
||||
model: &segmented_button::SingleSelectModel<Component>,
|
||||
on_activate: impl Fn(segmented_button::Key) -> Message + 'static,
|
||||
) -> iced::widget::Container<Message, crate::Renderer>
|
||||
where
|
||||
Message: Clone + 'static,
|
||||
{
|
||||
vertical_view_switcher(state)
|
||||
.on_activate(on_activate)
|
||||
vertical_segmented_button(model)
|
||||
.button_height(32)
|
||||
.button_padding([16, 10, 16, 10])
|
||||
.button_spacing(8)
|
||||
.icon_size(16)
|
||||
.on_activate(on_activate)
|
||||
.spacing(8)
|
||||
.style(crate::theme::SegmentedButton::ViewSwitcher)
|
||||
.apply(scrollable)
|
||||
.apply(container)
|
||||
.height(Length::Fill)
|
||||
|
|
|
|||
|
|
@ -2,20 +2,21 @@
|
|||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::{
|
||||
state::Selectable, HorizontalSegmentedButton, SegmentedButton, State, VerticalSegmentedButton,
|
||||
horizontal_segmented_button, HorizontalSegmentedButton, Model, SegmentedButton, Selectable,
|
||||
VerticalSegmentedButton,
|
||||
};
|
||||
|
||||
/// Appears as a collection of tabs for developing a tabbed interface.
|
||||
///
|
||||
/// The data for the widget comes from a [`State`] that is maintained the application.
|
||||
/// The data for the widget comes from a model supplied by the application.
|
||||
#[must_use]
|
||||
pub fn horizontal_view_switcher<Selection, Message, Data>(
|
||||
state: &State<Selection, Data>,
|
||||
) -> HorizontalSegmentedButton<Selection, Message, crate::Renderer>
|
||||
pub fn horizontal_view_switcher<SelectionMode, Component, Message>(
|
||||
model: &Model<SelectionMode, Component>,
|
||||
) -> HorizontalSegmentedButton<SelectionMode, Message, crate::Renderer>
|
||||
where
|
||||
Selection: Selectable,
|
||||
SelectionMode: Selectable,
|
||||
{
|
||||
SegmentedButton::new(&state.inner)
|
||||
horizontal_segmented_button(model)
|
||||
.button_padding([16, 0, 16, 0])
|
||||
.button_height(48)
|
||||
.style(crate::theme::SegmentedButton::ViewSwitcher)
|
||||
|
|
@ -24,15 +25,15 @@ where
|
|||
|
||||
/// Appears as a selection of choices for choosing between.
|
||||
///
|
||||
/// The data for the widget comes from a [`State`] that is maintained the application.
|
||||
/// The data for the widget comes from a model that is maintained the application.
|
||||
#[must_use]
|
||||
pub fn horizontal_segmented_selection<Selection, Message, Data>(
|
||||
state: &State<Selection, Data>,
|
||||
) -> HorizontalSegmentedButton<Selection, Message, crate::Renderer>
|
||||
pub fn horizontal_segmented_selection<SelectionMode, Component, Message>(
|
||||
model: &Model<SelectionMode, Component>,
|
||||
) -> HorizontalSegmentedButton<SelectionMode, Message, crate::Renderer>
|
||||
where
|
||||
Selection: Selectable,
|
||||
SelectionMode: Selectable,
|
||||
{
|
||||
SegmentedButton::new(&state.inner)
|
||||
SegmentedButton::new(model)
|
||||
.button_padding([16, 0, 16, 0])
|
||||
.button_height(32)
|
||||
.style(crate::theme::SegmentedButton::Selection)
|
||||
|
|
@ -41,15 +42,15 @@ where
|
|||
|
||||
/// Appears as a selection of choices for choosing between.
|
||||
///
|
||||
/// The data for the widget comes from a [`State`] that is maintained the application.
|
||||
/// The data for the widget comes from a model that is maintained the application.
|
||||
#[must_use]
|
||||
pub fn vertical_segmented_selection<Selection, Message, Data>(
|
||||
state: &State<Selection, Data>,
|
||||
) -> VerticalSegmentedButton<Selection, Message, crate::Renderer>
|
||||
pub fn vertical_segmented_selection<SelectionMode, Component, Message>(
|
||||
model: &Model<SelectionMode, Component>,
|
||||
) -> VerticalSegmentedButton<SelectionMode, Message, crate::Renderer>
|
||||
where
|
||||
Selection: Selectable,
|
||||
SelectionMode: Selectable,
|
||||
{
|
||||
SegmentedButton::new(&state.inner)
|
||||
SegmentedButton::new(model)
|
||||
.button_padding([16, 0, 16, 0])
|
||||
.button_height(32)
|
||||
.style(crate::theme::SegmentedButton::Selection)
|
||||
|
|
@ -58,15 +59,15 @@ where
|
|||
|
||||
/// Appears as a collection of tabs for developing a tabbed interface.
|
||||
///
|
||||
/// The data for the widget comes from a [`State`] that is maintained the application.
|
||||
/// The data for the widget comes from a model that is maintained the application.
|
||||
#[must_use]
|
||||
pub fn vertical_view_switcher<Selection, Message, Data>(
|
||||
state: &State<Selection, Data>,
|
||||
) -> VerticalSegmentedButton<Selection, Message, crate::Renderer>
|
||||
pub fn vertical_view_switcher<SelectionMode, Component, Message>(
|
||||
model: &Model<SelectionMode, Component>,
|
||||
) -> VerticalSegmentedButton<SelectionMode, Message, crate::Renderer>
|
||||
where
|
||||
Selection: Selectable,
|
||||
SelectionMode: Selectable,
|
||||
{
|
||||
SegmentedButton::new(&state.inner)
|
||||
SegmentedButton::new(model)
|
||||
.button_padding([16, 0, 16, 0])
|
||||
.button_height(48)
|
||||
.style(crate::theme::SegmentedButton::ViewSwitcher)
|
||||
|
|
|
|||
|
|
@ -1,45 +1,48 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::state::{Selectable, State};
|
||||
//! Implementation details for the horizontal layout of a segmented button.
|
||||
|
||||
use super::model::Model;
|
||||
use super::selection_modes::Selectable;
|
||||
use super::style::StyleSheet;
|
||||
use super::widget::{SegmentedButton, SegmentedVariant};
|
||||
|
||||
use iced::{Length, Rectangle, Size};
|
||||
use iced_native::layout;
|
||||
|
||||
/// Horizontal [`SegmentedButton`].
|
||||
pub type HorizontalSegmentedButton<'a, SelectionMode, Message, Renderer> =
|
||||
SegmentedButton<'a, Horizontal, SelectionMode, Message, Renderer>;
|
||||
|
||||
/// A type marker defining the horizontal variant of a [`SegmentedButton`].
|
||||
pub struct Horizontal;
|
||||
|
||||
/// Horizontal [`SegmentedButton`].
|
||||
pub type HorizontalSegmentedButton<'a, Selection, Message, Renderer> =
|
||||
SegmentedButton<'a, Horizontal, Selection, Message, Renderer>;
|
||||
|
||||
/// Horizontal implementation of the [`SegmentedButton`].
|
||||
/// Row implementation of the [`SegmentedButton`].
|
||||
#[must_use]
|
||||
pub fn horizontal_segmented_button<Selection, Message, Renderer, Data>(
|
||||
state: &State<Selection, Data>,
|
||||
) -> SegmentedButton<Horizontal, Selection, Message, Renderer>
|
||||
pub fn horizontal_segmented_button<SelectionMode, Component, Message, Renderer>(
|
||||
model: &Model<SelectionMode, Component>,
|
||||
) -> SegmentedButton<Horizontal, SelectionMode, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer
|
||||
+ iced_native::text::Renderer
|
||||
+ iced_native::image::Renderer
|
||||
+ iced_native::svg::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Selection: Selectable,
|
||||
SelectionMode: Selectable,
|
||||
{
|
||||
SegmentedButton::new(&state.inner)
|
||||
SegmentedButton::new(model)
|
||||
}
|
||||
|
||||
impl<'a, Selection, Message, Renderer> SegmentedVariant
|
||||
for SegmentedButton<'a, Horizontal, Selection, Message, Renderer>
|
||||
impl<'a, SelectionMode, Message, Renderer> SegmentedVariant
|
||||
for SegmentedButton<'a, Horizontal, SelectionMode, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer
|
||||
+ iced_native::text::Renderer
|
||||
+ iced_native::image::Renderer
|
||||
+ iced_native::svg::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Selection: Selectable,
|
||||
SelectionMode: Selectable,
|
||||
{
|
||||
type Renderer = Renderer;
|
||||
|
||||
|
|
@ -52,7 +55,7 @@ where
|
|||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn variant_button_bounds(&self, mut bounds: Rectangle, nth: usize) -> Rectangle {
|
||||
let num = self.state.buttons.len();
|
||||
let num = self.model.items.len();
|
||||
if num != 0 {
|
||||
let spacing = f32::from(self.spacing);
|
||||
bounds.width = (bounds.width - (num as f32 * spacing) + spacing) / num as f32;
|
||||
|
|
@ -74,7 +77,7 @@ where
|
|||
|
||||
let (mut width, height) = self.max_button_dimensions(renderer, text_size, limits.max());
|
||||
|
||||
let num = self.state.buttons.len();
|
||||
let num = self.model.items.len();
|
||||
let spacing = f32::from(self.spacing);
|
||||
|
||||
if num != 0 {
|
||||
|
|
|
|||
57
src/widget/segmented_button/item.rs
Normal file
57
src/widget/segmented_button/item.rs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Defines the model that's used by each button in the widget.
|
||||
|
||||
use crate::widget::IconSource;
|
||||
use derive_setters::Setters;
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Describes a button in a segmented button
|
||||
#[must_use]
|
||||
pub fn item() -> SegmentedItem {
|
||||
SegmentedItem::default()
|
||||
}
|
||||
|
||||
/// Information about a specific button in a segmented button
|
||||
#[derive(Setters)]
|
||||
pub struct SegmentedItem {
|
||||
#[setters(into, strip_option)]
|
||||
/// The label to display in this button.
|
||||
pub text: Option<Cow<'static, str>>,
|
||||
|
||||
#[setters(into, strip_option)]
|
||||
/// An optionally-displayed icon beside the label.
|
||||
pub icon: Option<IconSource<'static>>,
|
||||
|
||||
/// Whether the button is clickable or not.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for SegmentedItem {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
text: None,
|
||||
icon: None,
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SegmentedItem {
|
||||
fn from(text: String) -> Self {
|
||||
Self::from(Cow::Owned(text))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for SegmentedItem {
|
||||
fn from(text: &'static str) -> Self {
|
||||
Self::from(Cow::Borrowed(text))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for SegmentedItem {
|
||||
fn from(text: Cow<'static, str>) -> Self {
|
||||
SegmentedItem::default().text(text)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! A widget providing a conjoined set of linear buttons for choosing between.
|
||||
//! A widget providing a conjoined set of linear buttons that function in conjunction with each other.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
|
|
@ -17,27 +17,30 @@
|
|||
//! }
|
||||
//!
|
||||
//! struct App {
|
||||
//! ...
|
||||
//! state: segmented_button::State<u16>(),
|
||||
//! ...
|
||||
//! state: segmented_button::SingleSelectModel<u16>(),
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Then add choices to the state, while activating the first.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! let first_key = application.state.insert("Choice A", 0);
|
||||
//! application.state.insert("Choice B", 1);
|
||||
//! application.state.insert("Choice C", 2);
|
||||
//! application.state.activate(first_key);
|
||||
//! application.model = SingleSelectModel::builder()
|
||||
//! .insert_activate("Choice A", 0)
|
||||
//! .insert("Choice B", 1)
|
||||
//! .insert("Choice C", 2)
|
||||
//! .build();
|
||||
//! ```
|
||||
//!
|
||||
//! Then use it in the view method to create segmented button widgets.
|
||||
//!
|
||||
//! ```ignore
|
||||
//! let widget = horizontal_segmentend_button(&application.state)
|
||||
//! .style(theme::SegmentedButton::Selection)
|
||||
//! .height(Length::Units(32))
|
||||
//! let widget = horizontal_segmented_button(&application.model)
|
||||
//! .style(theme::SegmentedButton::ViewSeitcher)
|
||||
//! .button_height(32)
|
||||
//! .button_padding([16, 10, 16, 10])
|
||||
//! .button_spacing(8)
|
||||
//! .icon_size(16)
|
||||
//! .spacing(8)
|
||||
//! .on_activate(AppMessage::Selected);
|
||||
//! ```
|
||||
|
||||
|
|
@ -45,16 +48,17 @@
|
|||
pub mod cosmic;
|
||||
|
||||
mod horizontal;
|
||||
|
||||
mod state;
|
||||
mod item;
|
||||
mod model;
|
||||
mod selection_modes;
|
||||
mod style;
|
||||
mod vertical;
|
||||
mod widget;
|
||||
|
||||
pub use self::horizontal::{horizontal_segmented_button, Horizontal, HorizontalSegmentedButton};
|
||||
pub use self::state::{
|
||||
Content, Key, MultiSelect, SecondaryState, Selectable, SharedWidgetState, SingleSelect, State,
|
||||
};
|
||||
pub use self::horizontal::{horizontal_segmented_button, HorizontalSegmentedButton};
|
||||
pub use self::item::{item, SegmentedItem};
|
||||
pub use self::model::{Batch, Key, Model, ModelBuilder, MultiSelectModel, SingleSelectModel};
|
||||
pub use self::selection_modes::Selectable;
|
||||
pub use self::style::{Appearance, ButtonAppearance, ButtonStatusAppearance, StyleSheet};
|
||||
pub use self::vertical::{vertical_segmented_button, Vertical, VerticalSegmentedButton};
|
||||
pub use self::widget::{SegmentedButton, SegmentedVariant};
|
||||
pub use self::vertical::{vertical_segmented_button, VerticalSegmentedButton};
|
||||
pub use self::widget::{focus, Id, SegmentedButton, SegmentedVariant};
|
||||
|
|
|
|||
202
src/widget/segmented_button/model.rs
Normal file
202
src/widget/segmented_button/model.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::selection_modes::{MultiSelect, Selectable, SingleSelect};
|
||||
use super::SegmentedItem;
|
||||
use slotmap::{SecondaryMap, SlotMap};
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// A unique ID for a segmented button
|
||||
pub struct Key;
|
||||
}
|
||||
|
||||
/// A model for single-select button selection.
|
||||
pub type SingleSelectModel<Component> = Model<SingleSelect, Component>;
|
||||
|
||||
/// A model for multi-select button selection.
|
||||
pub type MultiSelectModel<Component> = Model<MultiSelect, Component>;
|
||||
|
||||
/// The model held by the application, containing the unique IDs of each item and their respective contents.
|
||||
#[derive(Default)]
|
||||
pub struct Model<SelectionMode, Component> {
|
||||
pub(super) widget: WidgetModel<SelectionMode>,
|
||||
pub(super) app: AppModel<Component>,
|
||||
}
|
||||
|
||||
/// The portion of the model used only by the application.
|
||||
pub struct AppModel<Component>(SecondaryMap<Key, Component>);
|
||||
|
||||
impl<Component> Default for AppModel<Component> {
|
||||
fn default() -> Self {
|
||||
Self(SecondaryMap::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// The portion of the model useful to the widget.
|
||||
#[derive(Default)]
|
||||
pub struct WidgetModel<SelectionMode> {
|
||||
/// The content used for drawing segmented items.
|
||||
pub(super) items: SlotMap<Key, SegmentedItem>,
|
||||
|
||||
/// Manages selections
|
||||
pub(super) selection: SelectionMode,
|
||||
}
|
||||
|
||||
impl<Component> Model<SingleSelect, Component> {
|
||||
pub fn activate(&mut self, key: Key) {
|
||||
self.widget.selection.active = key;
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn active_component(&self) -> Option<&Component> {
|
||||
self.component(self.active())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn active_component_mut(&mut self) -> Option<&mut Component> {
|
||||
self.component_mut(self.active())
|
||||
}
|
||||
|
||||
pub fn deactivate(&mut self) {
|
||||
self.widget.selection.active = Key::default();
|
||||
}
|
||||
|
||||
/// The ID of the active button.
|
||||
#[must_use]
|
||||
pub fn active(&self) -> Key {
|
||||
self.widget.selection.active
|
||||
}
|
||||
}
|
||||
|
||||
impl<Component> Model<MultiSelect, Component> {
|
||||
pub fn activate(&mut self, key: Key) {
|
||||
if !self.widget.selection.active.insert(key) {
|
||||
self.widget.selection.active.remove(&key);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deactivate(&mut self, key: Key) {
|
||||
self.widget.selection.active.remove(&key);
|
||||
}
|
||||
|
||||
/// The IDs of the active items.
|
||||
pub fn active(&self) -> impl Iterator<Item = Key> + '_ {
|
||||
self.widget.selection.active.iter().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl<SelectionMode, Component> Model<SelectionMode, Component>
|
||||
where
|
||||
SelectionMode: Selectable,
|
||||
{
|
||||
#[must_use]
|
||||
pub fn builder() -> ModelBuilder<SelectionMode, Component> {
|
||||
ModelBuilder(Self {
|
||||
widget: WidgetModel::default(),
|
||||
app: AppModel::default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Convenience method for batching multiple operations
|
||||
#[must_use]
|
||||
pub fn batch(&mut self) -> Batch<SelectionMode, Component> {
|
||||
Batch(self)
|
||||
}
|
||||
|
||||
/// Enables or disables a button
|
||||
#[must_use]
|
||||
pub fn content(&self, key: Key) -> Option<&SegmentedItem> {
|
||||
self.widget.items.get(key)
|
||||
}
|
||||
|
||||
/// Enables or disables a button
|
||||
#[must_use]
|
||||
pub fn content_mut(&mut self, key: Key) -> Option<&mut SegmentedItem> {
|
||||
self.widget.items.get_mut(key)
|
||||
}
|
||||
|
||||
pub fn component(&self, key: Key) -> Option<&Component> {
|
||||
self.app.0.get(key)
|
||||
}
|
||||
|
||||
pub fn component_mut(&mut self, key: Key) -> Option<&mut Component> {
|
||||
self.app.0.get_mut(key)
|
||||
}
|
||||
|
||||
/// Insert a new button.
|
||||
pub fn insert(&mut self, content: impl Into<SegmentedItem>, component: Component) -> Key {
|
||||
let key = self.widget.items.insert(content.into());
|
||||
self.app.0.insert(key, component);
|
||||
key
|
||||
}
|
||||
|
||||
/// Inserts and activates a button.
|
||||
pub fn insert_active(
|
||||
&mut self,
|
||||
content: impl Into<SegmentedItem>,
|
||||
component: Component,
|
||||
) -> Key {
|
||||
let key = self.insert(content, component);
|
||||
self.widget.selection.activate(key);
|
||||
key
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_active(&self, key: Key) -> bool {
|
||||
self.widget.selection.is_active(key)
|
||||
}
|
||||
|
||||
/// Removes a button.
|
||||
pub fn remove(&mut self, key: Key) {
|
||||
self.widget.items.remove(key);
|
||||
self.widget.selection.deactivate(key);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModelBuilder<SelectionMode, Component>(Model<SelectionMode, Component>);
|
||||
|
||||
impl<SelectionMode: Selectable, Component> ModelBuilder<SelectionMode, Component> {
|
||||
#[must_use]
|
||||
pub fn insert(mut self, content: impl Into<SegmentedItem>, component: Component) -> Self {
|
||||
self.0.insert(content, component);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn insert_active(
|
||||
mut self,
|
||||
content: impl Into<SegmentedItem>,
|
||||
component: Component,
|
||||
) -> Self {
|
||||
self.0.insert_active(content, component);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> Model<SelectionMode, Component> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience type for batching multiple operations
|
||||
pub struct Batch<'a, SelectionMode, Component>(&'a mut Model<SelectionMode, Component>);
|
||||
|
||||
impl<'a, SelectionMode: Selectable, Component> Batch<'a, SelectionMode, Component> {
|
||||
/// Insert a new button.
|
||||
#[must_use]
|
||||
pub fn insert(self, content: impl Into<SegmentedItem>, component: Component) -> Self {
|
||||
self.0.insert(content, component);
|
||||
self
|
||||
}
|
||||
|
||||
/// Inserts and activates a button.
|
||||
#[must_use]
|
||||
pub fn insert_active(self, content: impl Into<SegmentedItem>, component: Component) -> Self {
|
||||
self.0.insert_active(content, component);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes a button.
|
||||
pub fn remove(&mut self, key: Key) {
|
||||
self.0.remove(key);
|
||||
}
|
||||
}
|
||||
59
src/widget/segmented_button/selection_modes.rs
Normal file
59
src/widget/segmented_button/selection_modes.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
//! Describes logic specific to the single-select and multi-select modes of a model.
|
||||
|
||||
use super::Key;
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// Describes a type that has selectable components.
|
||||
pub trait Selectable: Default {
|
||||
/// Activate a component.
|
||||
fn activate(&mut self, key: Key);
|
||||
|
||||
/// Deactivate a component.
|
||||
fn deactivate(&mut self, key: Key);
|
||||
|
||||
/// Checks if the component is active.
|
||||
fn is_active(&self, key: Key) -> bool;
|
||||
}
|
||||
|
||||
/// Ensures that only one key may be selected.
|
||||
#[derive(Default)]
|
||||
pub struct SingleSelect {
|
||||
pub active: Key,
|
||||
}
|
||||
|
||||
impl Selectable for SingleSelect {
|
||||
fn activate(&mut self, key: Key) {
|
||||
self.active = key;
|
||||
}
|
||||
|
||||
fn deactivate(&mut self, _key: Key) {
|
||||
self.active = Key::default();
|
||||
}
|
||||
|
||||
fn is_active(&self, key: Key) -> bool {
|
||||
self.active == key
|
||||
}
|
||||
}
|
||||
|
||||
/// Permits multiple keys to be active at a time.
|
||||
#[derive(Default)]
|
||||
pub struct MultiSelect {
|
||||
pub active: HashSet<Key>,
|
||||
}
|
||||
|
||||
impl Selectable for MultiSelect {
|
||||
fn activate(&mut self, key: Key) {
|
||||
self.active.insert(key);
|
||||
}
|
||||
|
||||
fn deactivate(&mut self, key: Key) {
|
||||
self.active.remove(&key);
|
||||
}
|
||||
|
||||
fn is_active(&self, key: Key) -> bool {
|
||||
self.active.contains(&key)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,292 +0,0 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use derive_setters::Setters;
|
||||
use slotmap::{SecondaryMap, SlotMap};
|
||||
use std::{borrow::Cow, collections::HashSet};
|
||||
|
||||
use crate::widget::IconSource;
|
||||
|
||||
slotmap::new_key_type! {
|
||||
/// An ID for a segmented button
|
||||
pub struct Key;
|
||||
}
|
||||
|
||||
/// Contains all state for interacting with a segmented button.
|
||||
pub struct State<Selection, Data> {
|
||||
/// State that is shared with widget drawing.
|
||||
pub inner: SharedWidgetState<Selection>,
|
||||
|
||||
/// State unique to the application.
|
||||
pub data: SecondaryState<Data>,
|
||||
}
|
||||
|
||||
impl<Selection: Default, Data> Default for State<Selection, Data> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
inner: SharedWidgetState::default(),
|
||||
data: SecondaryState::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// State which is most useful to the widget.
|
||||
#[derive(Default)]
|
||||
pub struct SharedWidgetState<Variant> {
|
||||
/// The content used for drawing segmented buttons.
|
||||
pub buttons: SlotMap<Key, Content>,
|
||||
|
||||
/// Manages selections
|
||||
pub selection: Variant,
|
||||
}
|
||||
|
||||
impl<Data> State<SingleSelect, Data> {
|
||||
pub fn activate(&mut self, key: Key) {
|
||||
self.inner.selection.activate(key);
|
||||
}
|
||||
|
||||
pub fn deactivate(&mut self, key: Key) {
|
||||
self.inner.selection.deactivate(key);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_active(&self, key: Key) -> bool {
|
||||
self.inner.selection.is_active(key)
|
||||
}
|
||||
|
||||
/// The ID of the active button.
|
||||
#[must_use]
|
||||
pub fn active(&self) -> Key {
|
||||
self.inner.selection.active
|
||||
}
|
||||
|
||||
/// Get the application data for the active button.
|
||||
#[must_use]
|
||||
pub fn active_data(&self) -> Option<&Data> {
|
||||
self.data.get(self.inner.selection.active)
|
||||
}
|
||||
|
||||
/// Mutable application data for the active button.
|
||||
#[must_use]
|
||||
pub fn active_data_mut(&mut self) -> Option<&mut Data> {
|
||||
self.data.get_mut(self.inner.selection.active)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Data> State<MultiSelect, Data> {
|
||||
pub fn activate(&mut self, key: Key) {
|
||||
if self.inner.selection.is_active(key) {
|
||||
self.inner.selection.deactivate(key);
|
||||
} else {
|
||||
self.inner.selection.activate(key);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deactivate(&mut self, key: Key) {
|
||||
self.inner.selection.deactivate(key);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn is_active(&self, key: Key) -> bool {
|
||||
self.inner.selection.is_active(key)
|
||||
}
|
||||
|
||||
/// The IDs of the active buttons.
|
||||
pub fn active(&self) -> impl Iterator<Item = Key> + '_ {
|
||||
self.inner.selection.active.iter().copied()
|
||||
}
|
||||
|
||||
/// Get the application data for the active buttons.
|
||||
pub fn active_data(&self) -> impl Iterator<Item = (Key, &Data)> {
|
||||
self.inner.buttons.keys().filter_map(|key| {
|
||||
if self.inner.selection.is_active(key) {
|
||||
self.data.get(key).map(|data| (key, data))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// State which is most useful to the application.
|
||||
pub type SecondaryState<Data> = SecondaryMap<Key, Data>;
|
||||
|
||||
impl<Selection, Data> State<Selection, Data>
|
||||
where
|
||||
Selection: Selectable,
|
||||
{
|
||||
#[must_use]
|
||||
pub fn builder() -> Builder<Selection, Data> {
|
||||
Builder(Self::default())
|
||||
}
|
||||
|
||||
/// Convenience method for batching multiple operations
|
||||
#[must_use]
|
||||
pub fn batch(&mut self) -> Batch<Selection, Data> {
|
||||
Batch(self)
|
||||
}
|
||||
|
||||
/// Enables or disables a button
|
||||
#[must_use]
|
||||
pub fn content(&self, key: Key) -> Option<&Content> {
|
||||
self.inner.buttons.get(key)
|
||||
}
|
||||
|
||||
/// Enables or disables a button
|
||||
#[must_use]
|
||||
pub fn content_mut(&mut self, key: Key) -> Option<&mut Content> {
|
||||
self.inner.buttons.get_mut(key)
|
||||
}
|
||||
|
||||
/// Get the application data for a button.
|
||||
#[must_use]
|
||||
pub fn data(&self, key: Key) -> Option<&Data> {
|
||||
self.data.get(key)
|
||||
}
|
||||
|
||||
/// Get mutable application data for a button.
|
||||
#[must_use]
|
||||
pub fn data_mut(&mut self, key: Key) -> Option<&mut Data> {
|
||||
self.data.get_mut(key)
|
||||
}
|
||||
|
||||
/// Insert a new button.
|
||||
pub fn insert(&mut self, content: impl Into<Content>, data: Data) -> Key {
|
||||
let key = self.inner.buttons.insert(content.into());
|
||||
self.data.insert(key, data);
|
||||
key
|
||||
}
|
||||
|
||||
/// Inserts and activates a button.
|
||||
pub fn insert_active(&mut self, content: impl Into<Content>, data: Data) -> Key {
|
||||
let key = self.insert(content, data);
|
||||
self.inner.selection.activate(key);
|
||||
key
|
||||
}
|
||||
|
||||
/// Removes a button.
|
||||
pub fn remove(&mut self, key: Key) -> Option<Data> {
|
||||
self.inner.buttons.remove(key);
|
||||
self.inner.selection.deactivate(key);
|
||||
self.data.remove(key)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Selectable: Default {
|
||||
fn activate(&mut self, key: Key);
|
||||
|
||||
fn deactivate(&mut self, key: Key);
|
||||
|
||||
fn is_active(&self, key: Key) -> bool;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SingleSelect {
|
||||
pub active: Key,
|
||||
}
|
||||
|
||||
impl Selectable for SingleSelect {
|
||||
fn activate(&mut self, key: Key) {
|
||||
self.active = key;
|
||||
}
|
||||
|
||||
fn deactivate(&mut self, _key: Key) {
|
||||
self.active = Key::default();
|
||||
}
|
||||
|
||||
fn is_active(&self, key: Key) -> bool {
|
||||
self.active == key
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MultiSelect {
|
||||
pub active: HashSet<Key>,
|
||||
}
|
||||
|
||||
impl Selectable for MultiSelect {
|
||||
fn activate(&mut self, key: Key) {
|
||||
self.active.insert(key);
|
||||
}
|
||||
|
||||
fn deactivate(&mut self, key: Key) {
|
||||
self.active.remove(&key);
|
||||
}
|
||||
|
||||
fn is_active(&self, key: Key) -> bool {
|
||||
self.active.contains(&key)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Builder<Selection, Data>(State<Selection, Data>);
|
||||
|
||||
impl<Selection: Selectable, Data> Builder<Selection, Data> {
|
||||
pub fn insert(mut self, content: impl Into<Content>, data: Data) -> Self {
|
||||
self.0.insert(content, data);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn insert_active(mut self, content: impl Into<Content>, data: Data) -> Self {
|
||||
self.0.insert_active(content, data);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> State<Selection, Data> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience type for batching multiple operations
|
||||
pub struct Batch<'a, Selection, Data>(&'a mut State<Selection, Data>);
|
||||
|
||||
impl<'a, Selection: Selectable, Data> Batch<'a, Selection, Data> {
|
||||
/// Insert a new button.
|
||||
pub fn insert(self, content: impl Into<Content>, data: Data) -> Self {
|
||||
self.0.insert(content, data);
|
||||
self
|
||||
}
|
||||
|
||||
/// Inserts and activates a button.
|
||||
pub fn insert_active(self, content: impl Into<Content>, data: Data) -> Self {
|
||||
self.0.insert_active(content, data);
|
||||
self
|
||||
}
|
||||
|
||||
/// Removes a button.
|
||||
pub fn remove(&mut self, key: Key) {
|
||||
self.0.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
/// Data to be drawn in a segmented button.
|
||||
#[derive(Default, Setters)]
|
||||
pub struct Content {
|
||||
#[setters(strip_option, into)]
|
||||
/// The label to display in this button.
|
||||
pub text: Option<Cow<'static, str>>,
|
||||
|
||||
#[setters(strip_option, into)]
|
||||
/// An optionally-displayed icon beside the label.
|
||||
pub icon: Option<IconSource<'static>>,
|
||||
|
||||
/// Whether the button is clickable or not.
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
impl From<String> for Content {
|
||||
fn from(text: String) -> Self {
|
||||
Self::from(Cow::Owned(text))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for Content {
|
||||
fn from(text: &'static str) -> Self {
|
||||
Self::from(Cow::Borrowed(text))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Cow<'static, str>> for Content {
|
||||
fn from(text: Cow<'static, str>) -> Self {
|
||||
Content::default().text(text)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ pub struct Appearance {
|
|||
pub active: ButtonStatusAppearance,
|
||||
pub inactive: ButtonStatusAppearance,
|
||||
pub hover: ButtonStatusAppearance,
|
||||
pub focus: ButtonStatusAppearance,
|
||||
}
|
||||
|
||||
/// The appearance of a button in the segmented button
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright 2022 System76 <info@system76.com>
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
use super::state::{Selectable, State};
|
||||
//! Implementation details for the vertical layout of a segmented button.
|
||||
|
||||
use super::model::Model;
|
||||
use super::selection_modes::Selectable;
|
||||
use super::style::StyleSheet;
|
||||
use super::widget::{SegmentedButton, SegmentedVariant};
|
||||
|
||||
|
|
@ -12,34 +15,34 @@ use iced_native::layout;
|
|||
pub struct Vertical;
|
||||
|
||||
/// Vertical [`SegmentedButton`].
|
||||
pub type VerticalSegmentedButton<'a, Selection, Message, Renderer> =
|
||||
SegmentedButton<'a, Vertical, Selection, Message, Renderer>;
|
||||
pub type VerticalSegmentedButton<'a, SelectionMode, Message, Renderer> =
|
||||
SegmentedButton<'a, Vertical, SelectionMode, Message, Renderer>;
|
||||
|
||||
/// Vertical implementation of the [`SegmentedButton`].
|
||||
#[must_use]
|
||||
pub fn vertical_segmented_button<Selection, Message, Renderer, Data>(
|
||||
state: &State<Selection, Data>,
|
||||
) -> SegmentedButton<Vertical, Selection, Message, Renderer>
|
||||
pub fn vertical_segmented_button<SelectionMode, Component, Message, Renderer>(
|
||||
model: &Model<SelectionMode, Component>,
|
||||
) -> SegmentedButton<Vertical, SelectionMode, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer
|
||||
+ iced_native::text::Renderer
|
||||
+ iced_native::image::Renderer
|
||||
+ iced_native::svg::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Selection: Selectable,
|
||||
SelectionMode: Selectable,
|
||||
{
|
||||
SegmentedButton::new(&state.inner)
|
||||
SegmentedButton::new(model)
|
||||
}
|
||||
|
||||
impl<'a, Selection, Message, Renderer> SegmentedVariant
|
||||
for SegmentedButton<'a, Vertical, Selection, Message, Renderer>
|
||||
impl<'a, SelectionMode, Message, Renderer> SegmentedVariant
|
||||
for SegmentedButton<'a, Vertical, SelectionMode, Message, Renderer>
|
||||
where
|
||||
Renderer: iced_native::Renderer
|
||||
+ iced_native::text::Renderer
|
||||
+ iced_native::image::Renderer
|
||||
+ iced_native::svg::Renderer,
|
||||
Renderer::Theme: StyleSheet,
|
||||
Selection: Selectable,
|
||||
SelectionMode: Selectable,
|
||||
{
|
||||
type Renderer = Renderer;
|
||||
|
||||
|
|
@ -52,7 +55,7 @@ where
|
|||
|
||||
#[allow(clippy::cast_precision_loss)]
|
||||
fn variant_button_bounds(&self, mut bounds: Rectangle, nth: usize) -> Rectangle {
|
||||
let num = self.state.buttons.len();
|
||||
let num = self.model.items.len();
|
||||
if num != 0 {
|
||||
let spacing = f32::from(self.spacing);
|
||||
bounds.height = (bounds.height - (num as f32 * spacing) + spacing) / num as f32;
|
||||
|
|
@ -74,7 +77,7 @@ where
|
|||
|
||||
let (width, mut height) = self.max_button_dimensions(renderer, text_size, limits.max());
|
||||
|
||||
let num = self.state.buttons.len();
|
||||
let num = self.model.items.len();
|
||||
let spacing = f32::from(self.spacing);
|
||||
|
||||
if num != 0 {
|
||||
|
|
|
|||
|
|
@ -3,18 +3,48 @@
|
|||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use super::state::{Key, Selectable, SharedWidgetState};
|
||||
use super::model::{Key, Model, WidgetModel};
|
||||
use super::selection_modes::Selectable;
|
||||
use super::style::StyleSheet;
|
||||
|
||||
use derive_setters::Setters;
|
||||
use iced::{
|
||||
alignment, event, mouse, touch, Background, Color, Element, Event, Length, Point, Rectangle,
|
||||
Size,
|
||||
alignment, event, keyboard, mouse, touch, Background, Color, Command, Element, Event, Length,
|
||||
Point, Rectangle, Size,
|
||||
};
|
||||
use iced_core::BorderRadius;
|
||||
use iced_native::widget::tree;
|
||||
use iced_native::widget::{self, operation, tree, Operation};
|
||||
use iced_native::{layout, renderer, widget::Tree, Clipboard, Layout, Shell, Widget};
|
||||
|
||||
/// State that is maintained by each individual widget.
|
||||
#[derive(Default)]
|
||||
struct LocalState {
|
||||
/// The first focusable key.
|
||||
first: Key,
|
||||
/// If the widget is focused or not.
|
||||
focused: bool,
|
||||
/// The key inside the widget that is currently focused.
|
||||
focused_key: Key,
|
||||
/// The ID of the button that is being hovered. Defaults to null.
|
||||
hovered: Key,
|
||||
}
|
||||
|
||||
impl operation::Focusable for LocalState {
|
||||
fn is_focused(&self) -> bool {
|
||||
self.focused
|
||||
}
|
||||
|
||||
fn focus(&mut self) {
|
||||
self.focused = true;
|
||||
self.focused_key = self.first;
|
||||
}
|
||||
|
||||
fn unfocus(&mut self) {
|
||||
self.focused = false;
|
||||
self.focused_key = Key::default();
|
||||
}
|
||||
}
|
||||
|
||||
/// Isolates variant-specific behaviors from [`SegmentedButton`].
|
||||
pub trait SegmentedVariant {
|
||||
type Renderer: iced_native::Renderer;
|
||||
|
|
@ -44,9 +74,10 @@ where
|
|||
Renderer::Theme: StyleSheet,
|
||||
Selection: Selectable,
|
||||
{
|
||||
/// Contains application state also used for drawing.
|
||||
/// The model borrowed from the application create this widget.
|
||||
#[setters(skip)]
|
||||
pub(super) state: &'a SharedWidgetState<Selection>,
|
||||
pub(super) model: &'a WidgetModel<Selection>,
|
||||
pub(super) id: Option<Id>,
|
||||
/// Padding around a button.
|
||||
pub(super) button_padding: [u16; 4],
|
||||
/// Desired height of a button.
|
||||
|
|
@ -65,7 +96,7 @@ where
|
|||
pub(super) width: Length,
|
||||
/// Desired height of the widget.
|
||||
pub(super) height: Length,
|
||||
/// Desired spacing between buttons.
|
||||
/// Desired spacing between items.
|
||||
pub(super) spacing: u16,
|
||||
/// Style to draw the widget in.
|
||||
#[setters(into)]
|
||||
|
|
@ -90,9 +121,10 @@ where
|
|||
Selection: Selectable,
|
||||
{
|
||||
#[must_use]
|
||||
pub fn new(state: &'a SharedWidgetState<Selection>) -> Self {
|
||||
pub fn new<Component>(model: &'a Model<Selection, Component>) -> Self {
|
||||
Self {
|
||||
state,
|
||||
model: &model.widget,
|
||||
id: None,
|
||||
button_padding: [4, 4, 4, 4],
|
||||
button_height: 32,
|
||||
button_spacing: 4,
|
||||
|
|
@ -109,6 +141,46 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn focus_previous(&mut self, state: &mut LocalState) -> event::Status {
|
||||
let mut previous_key: Option<Key> = None;
|
||||
|
||||
for key in self.model.items.keys() {
|
||||
if key == state.focused_key {
|
||||
return match previous_key {
|
||||
Some(next_focus) => {
|
||||
state.focused_key = next_focus;
|
||||
event::Status::Captured
|
||||
}
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
|
||||
previous_key = Some(key);
|
||||
}
|
||||
|
||||
state.focused_key = Key::default();
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn focus_next(&mut self, state: &mut LocalState) -> event::Status {
|
||||
let mut keys = self.model.items.keys();
|
||||
|
||||
while let Some(key) = keys.next() {
|
||||
if key == state.focused_key {
|
||||
return match keys.next() {
|
||||
Some(next_focus) => {
|
||||
state.focused_key = next_focus;
|
||||
event::Status::Captured
|
||||
}
|
||||
None => break,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
state.focused_key = Key::default();
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
/// Emits the ID of the activated widget on selection.
|
||||
#[must_use]
|
||||
pub fn on_activate(mut self, on_activate: impl Fn(Key) -> Message + 'static) -> Self {
|
||||
|
|
@ -125,7 +197,7 @@ where
|
|||
let mut width = 0.0f32;
|
||||
let mut height = 0.0f32;
|
||||
|
||||
for (_, content) in self.state.buttons.iter() {
|
||||
for content in self.model.items.values() {
|
||||
let mut button_width = 0.0f32;
|
||||
let mut button_height = 0.0f32;
|
||||
|
||||
|
|
@ -169,11 +241,14 @@ where
|
|||
Message: 'static + Clone,
|
||||
{
|
||||
fn tag(&self) -> tree::Tag {
|
||||
tree::Tag::of::<UniqueWidgetState>()
|
||||
tree::Tag::of::<LocalState>()
|
||||
}
|
||||
|
||||
fn state(&self) -> tree::State {
|
||||
tree::State::new(UniqueWidgetState::default())
|
||||
tree::State::new(LocalState {
|
||||
first: self.model.items.keys().next().unwrap_or_default(),
|
||||
..LocalState::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn width(&self) -> Length {
|
||||
|
|
@ -199,32 +274,72 @@ where
|
|||
shell: &mut Shell<'_, Message>,
|
||||
) -> event::Status {
|
||||
let bounds = layout.bounds();
|
||||
let state = tree.state.downcast_mut::<UniqueWidgetState>();
|
||||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
|
||||
if bounds.contains(cursor_position) {
|
||||
for (nth, (key, _)) in self.state.buttons.iter().enumerate() {
|
||||
for (nth, (key, content)) in self.model.items.iter().enumerate() {
|
||||
let bounds = self.variant_button_bounds(bounds, nth);
|
||||
if bounds.contains(cursor_position) {
|
||||
// Record that the mouse is hovering over this button.
|
||||
state.hovered = key;
|
||||
if content.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
|
||||
{
|
||||
// Record that the mouse is hovering over this button.
|
||||
state.hovered = key;
|
||||
|
||||
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
|
||||
{
|
||||
shell.publish(on_activate(key));
|
||||
return event::Status::Captured;
|
||||
shell.publish(on_activate(key));
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
state.hovered = Key::default();
|
||||
}
|
||||
|
||||
if state.focused {
|
||||
if let Event::Keyboard(keyboard::Event::KeyPressed {
|
||||
key_code: keyboard::KeyCode::Tab,
|
||||
modifiers,
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
return if modifiers.shift() {
|
||||
self.focus_previous(state)
|
||||
} else {
|
||||
self.focus_next(state)
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(on_activate) = self.on_activate.as_ref() {
|
||||
if let Event::Keyboard(keyboard::Event::KeyReleased {
|
||||
key_code: keyboard::KeyCode::Enter,
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
shell.publish(on_activate(state.focused_key));
|
||||
return event::Status::Captured;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event::Status::Ignored
|
||||
}
|
||||
|
||||
fn operate(
|
||||
&self,
|
||||
tree: &mut Tree,
|
||||
_layout: Layout<'_>,
|
||||
operation: &mut dyn Operation<Message>,
|
||||
) {
|
||||
let state = tree.state.downcast_mut::<LocalState>();
|
||||
operation.focusable(state, self.id.as_ref().map(|id| &id.0));
|
||||
}
|
||||
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
_tree: &Tree,
|
||||
|
|
@ -234,14 +349,23 @@ where
|
|||
_renderer: &Renderer,
|
||||
) -> iced_native::mouse::Interaction {
|
||||
let bounds = layout.bounds();
|
||||
if (0..self.state.buttons.len()).any(|nth| {
|
||||
self.variant_button_bounds(bounds, nth)
|
||||
.contains(cursor_position)
|
||||
}) {
|
||||
iced_native::mouse::Interaction::Pointer
|
||||
} else {
|
||||
iced_native::mouse::Interaction::Idle
|
||||
|
||||
if bounds.contains(cursor_position) {
|
||||
for (nth, content) in self.model.items.values().enumerate() {
|
||||
if self
|
||||
.variant_button_bounds(bounds, nth)
|
||||
.contains(cursor_position)
|
||||
{
|
||||
return if content.enabled {
|
||||
iced_native::mouse::Interaction::Pointer
|
||||
} else {
|
||||
iced_native::mouse::Interaction::Idle
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iced_native::mouse::Interaction::Idle
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
|
|
@ -255,10 +379,10 @@ where
|
|||
_cursor_position: iced::Point,
|
||||
_viewport: &iced::Rectangle,
|
||||
) {
|
||||
let state = tree.state.downcast_ref::<UniqueWidgetState>();
|
||||
let state = tree.state.downcast_ref::<LocalState>();
|
||||
let appearance = Self::variant_appearance(theme, &self.style);
|
||||
let bounds = layout.bounds();
|
||||
let button_amount = self.state.buttons.len();
|
||||
let button_amount = self.model.items.len();
|
||||
|
||||
// Draw the background, if a background was defined.
|
||||
if let Some(background) = appearance.background {
|
||||
|
|
@ -273,11 +397,13 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
// Draw each of the buttons in the widget.
|
||||
for (nth, (key, content)) in self.state.buttons.iter().enumerate() {
|
||||
// Draw each of the items in the widget.
|
||||
for (nth, (key, content)) in self.model.items.iter().enumerate() {
|
||||
let mut bounds = self.variant_button_bounds(bounds, nth);
|
||||
|
||||
let (status_appearance, font) = if self.state.selection.is_active(key) {
|
||||
let (status_appearance, font) = if state.focused_key == key {
|
||||
(appearance.focus, &self.font_active)
|
||||
} else if self.model.selection.is_active(key) {
|
||||
(appearance.active, &self.font_active)
|
||||
} else if state.hovered == key {
|
||||
(appearance.hover, &self.font_hovered)
|
||||
|
|
@ -413,7 +539,7 @@ where
|
|||
Message: 'static + Clone,
|
||||
{
|
||||
fn from(mut widget: SegmentedButton<'a, Variant, Selection, Message, Renderer>) -> Self {
|
||||
if widget.state.buttons.is_empty() {
|
||||
if widget.model.items.is_empty() {
|
||||
widget.spacing = 0;
|
||||
}
|
||||
|
||||
|
|
@ -421,9 +547,33 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// State that is maintained by each individual widget.
|
||||
#[derive(Default)]
|
||||
struct UniqueWidgetState {
|
||||
/// The ID of the button that is being hovered. Defaults to null.
|
||||
hovered: Key,
|
||||
/// A command that focuses a segmented item stored in a widget.
|
||||
#[must_use]
|
||||
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
|
||||
Command::widget(operation::focusable::focus(id.0))
|
||||
}
|
||||
|
||||
/// The identifier of a segmented item.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Id(widget::Id);
|
||||
|
||||
impl Id {
|
||||
/// Creates a custom [`Id`].
|
||||
pub fn new(id: impl Into<std::borrow::Cow<'static, str>>) -> Self {
|
||||
Self(widget::Id::new(id))
|
||||
}
|
||||
|
||||
/// Creates a unique [`Id`].
|
||||
///
|
||||
/// This function produces a different [`Id`] every time it is called.
|
||||
#[must_use]
|
||||
pub fn unique() -> Self {
|
||||
Self(widget::Id::unique())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Id> for widget::Id {
|
||||
fn from(id: Id) -> Self {
|
||||
id.0
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue