diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index 33199c0..2b5e2af 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -10,7 +10,7 @@ use cosmic::{ iced_lazy::responsive, iced_winit::window::{drag, toggle_maximize, minimize}, theme::{self, Theme}, - widget::{button, nav_button, nav_bar, nav_bar_page, nav_bar_section, header_bar, settings, scrollable, toggler, spin_button}, + widget::{button, nav_button, nav_bar, nav_bar_page, nav_bar_section, header_bar, settings, scrollable, toggler, SpinButtonModel, SpinMessage}, Element, ElementExt, }; @@ -24,7 +24,7 @@ pub struct Window { debug: bool, theme: Theme, slider_value: f32, - spin_value: i32, + spin_button: SpinButtonModel, checkbox_value: bool, toggler_value: bool, pick_list_selected: Option<&'static str>, @@ -72,12 +72,6 @@ pub enum Message { SpinButton(SpinMessage) } -#[derive(Clone, Copy, Debug, Hash)] -pub enum SpinMessage { - Increment, - Decrement, -} - impl Application for Window { type Executor = iced::executor::Default; type Flags = (); @@ -93,6 +87,8 @@ impl Application for Window { // window.theme = Theme::Light; window.pick_list_selected = Some("Option 1"); window.title = String::from("COSMIC Design System - Iced"); + window.spin_button.min = -10; + window.spin_button.max = 10; (window, Command::none()) } @@ -119,8 +115,7 @@ impl Application for Window { Message::Maximize => return toggle_maximize(window::Id::new(0)), Message::RowSelected(row) => println!("Selected row {row}"), Message::InputChanged => {}, - Message::SpinButton(SpinMessage::Decrement) => self.spin_value -= 1, - Message::SpinButton(SpinMessage::Increment) => self.spin_value += 1, + Message::SpinButton(msg) => self.spin_button.update(msg), } @@ -304,10 +299,8 @@ impl Application for Window { checkbox("Checkbox", self.checkbox_value, Message::CheckboxToggled).into() ])) .add(settings::item( - "Spin Button", - spin_button(self.spin_value, SpinMessage::Increment, SpinMessage::Decrement) - .into_element() - .map(Message::SpinButton) + format!("Spin Button (Range {}:{})", self.spin_button.min, self.spin_button.max), + self.spin_button.view().map(Message::SpinButton) )) .into() ]) diff --git a/src/widget/settings/item.rs b/src/widget/settings/item.rs index d244751..167ef31 100644 --- a/src/widget/settings/item.rs +++ b/src/widget/settings/item.rs @@ -6,11 +6,14 @@ use crate::{Element, Renderer}; /// A setting within a settings view section. #[must_use] #[allow(clippy::module_name_repetitions)] -pub fn item<'a, Message: 'static>(title: &'a str, widget: impl Into>) -> iced::widget::Row<'a, Message, Renderer> { +pub fn item<'a, Message: 'static>( + title: impl ToString, + widget: impl Into>, +) -> iced::widget::Row<'a, Message, Renderer> { item_row(vec![ iced::widget::text(title).into(), iced::widget::horizontal_space(iced::Length::Fill).into(), - widget.into() + widget.into(), ]) } @@ -23,4 +26,3 @@ pub fn item_row(children: Vec>) -> iced::widget::Row // SPDX-License-Identifier: MPL-2.0 -use crate::{Element}; -use crate::widget::{ListColumn}; +use crate::widget::ListColumn; +use crate::Element; use iced::widget::{column, text}; +use std::borrow::Cow; /// A section within a settings view column. #[must_use] -pub fn view_section( - title: &str, -) -> Section { - Section { title, children: ListColumn::default() } +pub fn view_section<'a, Message: 'static>(title: impl Into>) -> Section<'a, Message> { + Section { + title: title.into(), + children: ListColumn::default(), + } } pub struct Section<'a, Message> { - title: &'a str, - children: ListColumn<'a, Message> + title: Cow<'a, str>, + children: ListColumn<'a, Message>, } impl<'a, Message: 'static> Section<'a, Message> { @@ -28,12 +30,10 @@ impl<'a, Message: 'static> Section<'a, Message> { impl<'a, Message: 'static> From> for Element<'a, Message> { fn from(data: Section<'a, Message>) -> Self { - let title = text(data.title) - .font(crate::font::FONT_SEMIBOLD) - .into(); + let title = text(data.title).font(crate::font::FONT_SEMIBOLD).into(); column(vec![title, data.children.into_element()]) .spacing(8) .into() } -} \ No newline at end of file +} diff --git a/src/widget/spin_button.rs b/src/widget/spin_button.rs deleted file mode 100644 index 18f826b..0000000 --- a/src/widget/spin_button.rs +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2022 System76 -// SPDX-License-Identifier: MPL-2.0 - -use std::{hash::{Hash, Hasher}, collections::hash_map::DefaultHasher}; - -use crate::{theme, Element}; -use iced::{ - alignment::{Horizontal, Vertical}, - widget::{button, container, row, text}, - Alignment, Background, Length, -}; - -pub fn spin_button(value: T, on_increment: Message, on_decrement: Message) -> SpinButton -where T: 'static + Clone + Hash + ToString, - Message: 'static + Clone + Hash -{ - SpinButton::new(value, on_increment, on_decrement) -} - -#[derive(Hash)] -pub struct SpinButton { - on_increment: Message, - on_decrement: Message, - value: T, -} - -impl SpinButton -where T: 'static + Clone + Hash + ToString, - Message: 'static + Clone + Hash -{ - pub fn new(value: T, on_increment: Message, on_decrement: Message) -> Self { - Self { on_increment, on_decrement, value } - } - - pub fn into_element(self) -> Element<'static, Message> { - let mut hasher = DefaultHasher::new(); - self.hash(&mut hasher); - - iced_lazy::lazy(hasher.finish(), move || -> Element<'static, Message> { - container( - row![ - button( - container(text("-").size(26).vertical_alignment(Vertical::Center)) - .width(Length::Fill) - .height(Length::Fill) - .align_x(Horizontal::Center) - .align_y(Vertical::Center), - ) - .width(Length::Fill) - .height(Length::Fill) - .style(theme::Button::Text) - .on_press(self.on_decrement.clone()), - container(text(self.value.clone()).vertical_alignment(Vertical::Center)) - .width(Length::Fill) - .height(Length::Fill) - .align_x(Horizontal::Center) - .align_y(Vertical::Center), - button( - container(text("+").size(26).vertical_alignment(Vertical::Center)) - .width(Length::Fill) - .height(Length::Fill) - .align_x(Horizontal::Center) - .align_y(Vertical::Center), - ) - .width(Length::Fill) - .height(Length::Fill) - .style(theme::Button::Text) - .on_press(self.on_increment.clone()), - ] - .width(Length::Fill) - .height(Length::Units(32)) - .align_items(Alignment::Center), - ) - .padding([4, 4]) - .align_y(Vertical::Center) - .width(Length::Units(95)) - .height(Length::Units(32)) - .style(theme::Container::Custom(container_style)) - .into() - }).into() - } -} - -impl<'a, T, Message> From> for Element<'a, Message> -where T: 'static + Clone + Hash + ToString, - Message: 'static + Clone + Hash -{ - fn from(spin_button: SpinButton) -> Self { - spin_button.into_element() - } -} - -fn container_style(theme: &crate::Theme) -> iced_style::container::Appearance { - let secondary = &theme.cosmic().secondary; - let accent = &theme.cosmic().accent; - iced_style::container::Appearance { - text_color: None, - background: Some(Background::Color(secondary.component.base.into())), - border_radius: 24.0, - border_width: 0.0, - border_color: accent.base.into(), - } -} \ No newline at end of file diff --git a/src/widget/spin_button/mod.rs b/src/widget/spin_button/mod.rs new file mode 100644 index 0000000..8948370 --- /dev/null +++ b/src/widget/spin_button/mod.rs @@ -0,0 +1,101 @@ +// Copyright 2022 System76 +// SPDX-License-Identifier: MPL-2.0 + +mod model; +pub use self::model::SpinButtonModel; + +use crate::{theme, Element}; +use iced::{ + alignment::{Horizontal, Vertical}, + widget::{button, container, row, text}, + Alignment, Background, Length, +}; +use std::hash::Hash; + +pub struct SpinButton { + value: T, +} + +/// A message emitted by the [`SpinButton`] widget. +#[derive(Clone, Copy, Debug, Hash)] +pub enum SpinMessage { + Increment, + Decrement, +} + +pub fn spin_button(value: T) -> SpinButton { + SpinButton::new(value) +} + +impl SpinButton { + pub fn new(value: T) -> Self { + Self { value } + } + + pub fn into_element(self) -> Element<'static, SpinMessage> { + let Self { value } = self; + Element::from(iced_lazy::lazy( + value.clone(), + move || -> Element<'static, SpinMessage> { + container( + row![ + button( + container(text("-").size(26).vertical_alignment(Vertical::Center)) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center), + ) + .width(Length::Fill) + .height(Length::Fill) + .style(theme::Button::Text) + .on_press(SpinMessage::Decrement), + container(text(value.clone()).vertical_alignment(Vertical::Center)) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center), + button( + container(text("+").size(26).vertical_alignment(Vertical::Center)) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center), + ) + .width(Length::Fill) + .height(Length::Fill) + .style(theme::Button::Text) + .on_press(SpinMessage::Increment), + ] + .width(Length::Fill) + .height(Length::Units(32)) + .align_items(Alignment::Center), + ) + .padding([4, 4]) + .align_y(Vertical::Center) + .width(Length::Units(95)) + .height(Length::Units(32)) + .style(theme::Container::Custom(container_style)) + .into() + }, + )) + } +} + +impl<'a, T: 'static + Clone + Hash + ToString> From> for Element<'a, SpinMessage> { + fn from(spin_button: SpinButton) -> Self { + spin_button.into_element() + } +} + +fn container_style(theme: &crate::Theme) -> iced_style::container::Appearance { + let secondary = &theme.cosmic().secondary; + let accent = &theme.cosmic().accent; + iced_style::container::Appearance { + text_color: None, + background: Some(Background::Color(secondary.component.base.into())), + border_radius: 24.0, + border_width: 0.0, + border_color: accent.base.into(), + } +} diff --git a/src/widget/spin_button/model.rs b/src/widget/spin_button/model.rs new file mode 100644 index 0000000..6565159 --- /dev/null +++ b/src/widget/spin_button/model.rs @@ -0,0 +1,150 @@ +// Copyright 2022 System76 +// SPDX-License-Identifier: MPL-2.0 + +use super::{SpinButton, SpinMessage}; +use crate::Element; +use derive_setters::Setters; +use std::hash::Hash; +use std::ops::{Add, Sub}; + +#[derive(Setters)] +pub struct SpinButtonModel { + /// The current value of the spin button. + pub value: T, + /// The amount to increment the value. + pub step: T, + /// The minimum value permitted. + pub min: T, + /// The maximum value permitted. + pub max: T, +} + +impl SpinButtonModel +where + T: Copy + Hash + ToString + Sub + Add + Ord, +{ + pub fn view(&self) -> Element<'static, SpinMessage> { + SpinButton::new(self.value).into_element() + } + + pub fn update(&mut self, message: SpinMessage) { + self.value = match message { + SpinMessage::Increment => { + std::cmp::min(std::cmp::max(self.value + self.step, self.min), self.max) + } + SpinMessage::Decrement => { + std::cmp::max(std::cmp::min(self.value - self.step, self.max), self.min) + } + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0, + step: 1, + min: i8::MIN, + max: i8::MAX, + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0, + step: 1, + min: i16::MIN, + max: i16::MAX, + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0, + step: 1, + min: i32::MIN, + max: i32::MAX, + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0, + step: 1, + min: isize::MIN, + max: isize::MAX, + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0, + step: 1, + min: u8::MIN, + max: u8::MAX, + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0, + step: 1, + min: u16::MIN, + max: u16::MAX, + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0, + step: 1, + min: u32::MIN, + max: u32::MAX, + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0, + step: 1, + min: usize::MIN, + max: usize::MAX, + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0.0, + step: 1.0, + min: f32::MIN, + max: f32::MAX, + } + } +} + +impl Default for SpinButtonModel { + fn default() -> Self { + Self { + value: 0.0, + step: 1.0, + min: f64::MIN, + max: f64::MAX, + } + } +}