From 5c29c530a5bb9fe69262b19e9e47f17fda11269c Mon Sep 17 00:00:00 2001 From: Bryan Hyland Date: Thu, 14 Nov 2024 14:33:22 -0800 Subject: [PATCH] Created vertical spin button and example --- examples/vertical-spin-button/Cargo.toml | 16 +++ examples/vertical-spin-button/src/main.rs | 133 ++++++++++++++++++++++ src/widget/mod.rs | 4 + src/widget/vertical_spin_button.rs | 116 +++++++++++++++++++ 4 files changed, 269 insertions(+) create mode 100644 examples/vertical-spin-button/Cargo.toml create mode 100644 examples/vertical-spin-button/src/main.rs create mode 100644 src/widget/vertical_spin_button.rs diff --git a/examples/vertical-spin-button/Cargo.toml b/examples/vertical-spin-button/Cargo.toml new file mode 100644 index 00000000..65c22606 --- /dev/null +++ b/examples/vertical-spin-button/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "vertical-spin-button" +version = "0.1.0" +edition = "2021" + +[features] +default = ["xdg-portal"] +rfd = ["libcosmic/rfd"] +xdg-portal = ["libcosmic/xdg-portal"] + +[dependencies] + +[dependencies.libcosmic] +features = ["debug", "winit", "wayland", "desktop", "tokio"] +path ="../.." +default-features = false \ No newline at end of file diff --git a/examples/vertical-spin-button/src/main.rs b/examples/vertical-spin-button/src/main.rs new file mode 100644 index 00000000..b3b09349 --- /dev/null +++ b/examples/vertical-spin-button/src/main.rs @@ -0,0 +1,133 @@ +use cosmic::widget::{button, container, text, vertical_spinner}; +use cosmic::{ + app::{Core, Task}, + iced::{ + alignment::{Horizontal, Vertical}, + widget::{column, row, vertical_space}, + Alignment, Size, + }, + Application, Element, +}; + +pub struct VertSpinnerApp { + core: Core, + hours: i32, + mins: i16, + secs: i8, + time_msg: String, +} + +#[derive(Debug, Clone)] +pub enum VertSpinnerMessages { + UpdateHours(i32), + UpdateMins(i16), + UpdateSecs(i8), + UpdateTimeMessage, +} + +impl Application for VertSpinnerApp { + type Executor = cosmic::executor::Default; + type Flags = (); + type Message = VertSpinnerMessages; + + const APP_ID: &'static str = "com.system76.VertSpinnerExample"; + + fn core(&self) -> &Core { + &self.core + } + + fn core_mut(&mut self) -> &mut Core { + &mut self.core + } + + fn init(core: Core, _flags: Self::Flags) -> (Self, Task) { + ( + Self { + core, + hours: 0, + mins: 0, + secs: 0, + time_msg: String::new(), + }, + Task::none(), + ) + } + + fn update(&mut self, message: Self::Message) -> Task { + match message { + VertSpinnerMessages::UpdateHours(hours) => self.hours = hours, + VertSpinnerMessages::UpdateMins(mins) => self.mins = mins, + VertSpinnerMessages::UpdateSecs(secs) => self.secs = secs, + VertSpinnerMessages::UpdateTimeMessage => { + self.time_msg = format!( + "{}:{}:{}", + self.hours, + // Add a zero in front of a single digit number + if self.mins < 10 { + format!("0{}", self.mins) + } else { + self.mins.to_string() + }, + // Add a zero in front of a single digit number + if self.secs < 10 { + format!("0{}", self.secs) + } else { + self.secs.to_string() + } + ) + } + } + + Task::none() + } + + fn view(&self) -> Element { + let spinner_row = row![ + vertical_spinner( + "Hours", + 1, + self.hours, + 0, + 12, + VertSpinnerMessages::UpdateHours + ), + vertical_spinner( + "Minutes", + 1, + self.mins, + 0, + 59, + VertSpinnerMessages::UpdateMins + ), + vertical_spinner( + "Seconds", + 1, + self.secs, + 0, + 59, + VertSpinnerMessages::UpdateSecs + ), + ] + .align_y(Vertical::Center); + + let final_col = column![ + spinner_row, + button::standard("Update Time").on_press(VertSpinnerMessages::UpdateTimeMessage), + vertical_space().height(10), + text(self.time_msg.clone()), + ] + .align_x(Alignment::Center); + + container(final_col) + .align_x(Horizontal::Center) + .align_y(Vertical::Center) + .into() + } +} + +fn main() -> Result<(), Box> { + let settings = cosmic::app::Settings::default().size(Size::new(260., 240.)); + cosmic::app::run::(settings, ())?; + + Ok(()) +} diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 52acf06d..703f88e1 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -358,6 +358,10 @@ pub mod tooltip { } } +pub mod vertical_spin_button; +#[doc(inline)] +pub use vertical_spin_button::*; + pub mod warning; #[doc(inline)] pub use warning::*; diff --git a/src/widget/vertical_spin_button.rs b/src/widget/vertical_spin_button.rs new file mode 100644 index 00000000..768da2c3 --- /dev/null +++ b/src/widget/vertical_spin_button.rs @@ -0,0 +1,116 @@ +// Spinner for integer selection +// Author: Bryan Hyland + +use crate::iced::alignment::Horizontal; +use crate::iced::Alignment; +use crate::widget::{button, column, icon, row, text}; +use crate::Element; +use std::fmt::Display; +use std::marker::PhantomData; +use std::ops::{Add, Sub}; + +/// Spinner is the state/model of the vertical spinner widget. +/// Restricts T to Add, Sub, Eq, Ord, Display, and Copy so that +/// T can only be numerical values. +pub struct VerticalSpinner<'a, T, M> +where + T: Add + Sub + Eq + Ord + Display + Copy, +{ + label: String, + step: T, + value: T, + min: T, + max: T, + on_select: Box M>, + phantom_data: PhantomData<&'a M>, +} + +impl<'a, T, M> VerticalSpinner<'a, T, M> +where + T: Add + Sub + Eq + Ord + Display + Copy, +{ + /// Creates a new vertical spinner widget + pub fn new( + label: impl Into, + step: T, + value: T, + min: T, + max: T, + on_select: impl Fn(T) -> M + 'static, + ) -> Self { + VerticalSpinner { + label: label.into(), + step, + value, + min, + max, + on_select: Box::new(on_select), + phantom_data: PhantomData, + } + } +} + +pub fn vertical_spinner( + label: impl Into, + step: T, + value: T, + min: T, + max: T, + on_select: impl Fn(T) -> M + 'static, +) -> VerticalSpinner<'static, T, M> +where + T: Add + Sub + Eq + Ord + Display + Copy, +{ + VerticalSpinner::new(label, step, value, min, max, on_select) +} + +fn increment(step: T, val: T, min: T, max: T) -> T +where + T: Add + Sub + Eq + Ord + Display + Copy, +{ + std::cmp::min(std::cmp::max(val + step, min), max) +} + +fn decrement(step: T, val: T, min: T, max: T) -> T +where + T: Add + Sub + Eq + Ord + Display + Copy, +{ + std::cmp::max(std::cmp::min(val - step, max), min) +} + +impl<'a, T, Message> From> for Element<'a, Message> +where + Message: Clone + 'static, + T: Add + Sub + Eq + Ord + Display + Copy, +{ + fn from(this: VerticalSpinner) -> Self { + let val_text = text(format!("{}", this.value)).size(14); + let spinner_container = column::with_capacity(3) + .push( + button::icon(icon::from_name("list-add-symbolic")) + .padding([0, 12]) + .on_press((this.on_select)(increment::( + this.step, this.value, this.min, this.max, + ))), + ) + .push(val_text) + .push( + button::icon(icon::from_name("list-remove-symbolic")) + .padding([0, 12]) + .on_press((this.on_select)(decrement::( + this.step, this.value, this.min, this.max, + ))), + ) + .align_x(Horizontal::Center); + + let content_list = column::with_children(vec![ + row::with_capacity(1).push(text(this.label)).into(), + row::with_children(vec![Element::from(spinner_container)]).into(), + ]) + .width(75) + .padding([8, 0]) + .align_x(Alignment::Center); + + Self::new(content_list) + } +}