Updated spin button widget and example usage

This commit is contained in:
Bryan Hyland 2024-11-17 12:28:32 -08:00
parent 8036be52dc
commit c8aabca37d
4 changed files with 146 additions and 123 deletions

View file

@ -1,5 +1,5 @@
use cosmic::widget::divider::horizontal; use cosmic::widget::divider::horizontal;
use cosmic::widget::{button, container, text, spin_button, spin_button::Direction}; use cosmic::widget::{button, container, text, spin_button, spin_button::Orientation};
use cosmic::{ use cosmic::{
app::{Core, Task}, app::{Core, Task},
iced::{ iced::{
@ -10,7 +10,7 @@ use cosmic::{
Application, Element, Application, Element,
}; };
pub struct VertSpinnerApp { pub struct SpinButtonExamplApp {
core: Core, core: Core,
i8_num: i8, i8_num: i8,
i16_num: i16, i16_num: i16,
@ -34,12 +34,12 @@ pub enum SpinBtnMessages {
UpdateSpinnerMsg, UpdateSpinnerMsg,
} }
impl Application for VertSpinnerApp { impl Application for SpinButtonExamplApp {
type Executor = cosmic::executor::Default; type Executor = cosmic::executor::Default;
type Flags = (); type Flags = ();
type Message = SpinBtnMessages; type Message = SpinBtnMessages;
const APP_ID: &'static str = "com.system76.VertSpinnerExample"; const APP_ID: &'static str = "com.system76.SpinButtonExample";
fn core(&self) -> &Core { fn core(&self) -> &Core {
&self.core &self.core
@ -68,10 +68,7 @@ impl Application for VertSpinnerApp {
fn update(&mut self, message: Self::Message) -> Task<Self::Message> { fn update(&mut self, message: Self::Message) -> Task<Self::Message> {
match message { match message {
SpinBtnMessages::UpdateI8Num(new_i8) => { SpinBtnMessages::UpdateI8Num(new_i8) => self.i8_num = new_i8,
self.i8_num = new_i8;
SpinBtnMessages::UpdateSpinnerMsg;
},
SpinBtnMessages::UpdateI16Num(new_i16) => self.i16_num = new_i16, SpinBtnMessages::UpdateI16Num(new_i16) => self.i16_num = new_i16,
SpinBtnMessages::UpdateI32Num(new_i32) => self.i32_num = new_i32, SpinBtnMessages::UpdateI32Num(new_i32) => self.i32_num = new_i32,
SpinBtnMessages::UpdateI64Num(new_i64) => self.i64_num = new_i64, SpinBtnMessages::UpdateI64Num(new_i64) => self.i64_num = new_i64,
@ -95,7 +92,7 @@ impl Application for VertSpinnerApp {
self.i8_num, // current value, this is also what's displayed in the center of the widget self.i8_num, // current value, this is also what's displayed in the center of the widget
-5, // minimum value, if decremented below this the widget's current value rolls to the max value -5, // minimum value, if decremented below this the widget's current value rolls to the max value
5, // maximum value, if incremented above this the widget's current value rolls to the min value 5, // maximum value, if incremented above this the widget's current value rolls to the min value
Direction::Vertical, // oreintation of the widget Some(Orientation::Vertical), // oreintation of the widget
SpinBtnMessages::UpdateI8Num // message to send to the application's update function SpinBtnMessages::UpdateI8Num // message to send to the application's update function
), ),
spin_button( spin_button(
@ -104,7 +101,7 @@ impl Application for VertSpinnerApp {
self.i16_num, self.i16_num,
0, 0,
10, 10,
Direction::Vertical, Some(Orientation::Vertical),
SpinBtnMessages::UpdateI16Num SpinBtnMessages::UpdateI16Num
), ),
spin_button( spin_button(
@ -113,7 +110,7 @@ impl Application for VertSpinnerApp {
self.i32_num, self.i32_num,
0, 0,
12, 12,
Direction::Vertical, Some(Orientation::Vertical),
SpinBtnMessages::UpdateI32Num SpinBtnMessages::UpdateI32Num
), ),
spin_button( spin_button(
@ -122,7 +119,7 @@ impl Application for VertSpinnerApp {
self.i64_num, self.i64_num,
15, 15,
35, 35,
Direction::Vertical, Some(Orientation::Vertical),
SpinBtnMessages::UpdateI64Num SpinBtnMessages::UpdateI64Num
), ),
] ]
@ -136,7 +133,7 @@ impl Application for VertSpinnerApp {
self.i128_num, self.i128_num,
-1000, -1000,
500, 500,
Direction::Horizontal, None, // Passing None gives a default of a Horizontal Spin Button widget
SpinBtnMessages::UpdateI128Num SpinBtnMessages::UpdateI128Num
), ),
], ],
@ -148,7 +145,7 @@ impl Application for VertSpinnerApp {
self.f32_num, self.f32_num,
-35.3, -35.3,
12.3, 12.3,
Direction::Horizontal, Some(Orientation::Horizontal), // It can also be explicitly passed as Orientation::Horizontal
SpinBtnMessages::UpdateF32Num SpinBtnMessages::UpdateF32Num
) )
], ],
@ -160,7 +157,7 @@ impl Application for VertSpinnerApp {
self.f64_num, self.f64_num,
0.0, 0.0,
3.0, 3.0,
Direction::Horizontal, None,
SpinBtnMessages::UpdateF64Num SpinBtnMessages::UpdateF64Num
) )
], ],

2
iced

@ -1 +1 @@
Subproject commit 256863574bacfb1d2797c2a48cba7a3388cbeb59 Subproject commit d0c2a0371b476866cb0f7045fba0215425493d62

View file

@ -102,7 +102,7 @@ pub(crate) fn iced_settings<App: Application>(
iced.default_font = settings.default_font; iced.default_font = settings.default_font;
iced.default_text_size = iced::Pixels(settings.default_text_size); iced.default_text_size = iced::Pixels(settings.default_text_size);
let exit_on_close = settings.exit_on_close; let exit_on_close = settings.exit_on_close;
iced.is_daemon = false; //iced.is_daemon = false;
iced.exit_on_close_request = settings.is_daemon; iced.exit_on_close_request = settings.is_daemon;
let mut window_settings = iced::window::Settings::default(); let mut window_settings = iced::window::Settings::default();
window_settings.exit_on_close_request = exit_on_close; window_settings.exit_on_close_request = exit_on_close;

View file

@ -1,16 +1,17 @@
// Copyright 2022 System76 <info@system76.com> // Copyright 2022 System76 <info@system76.com>
// SPDX-License-Identifier: MPL-2.0 // SPDX-License-Identifier: MPL-2.0
// Updated by Bryan Hyland <bryan.hyland32@gmail.com>
// Updated on: 17Nov24
//! A control for incremental adjustments of a value. //! A control for incremental adjustments of a value.
use std::borrow::{Borrow, Cow}; use std::fmt::Display;
use std::fmt::{Display, Formatter};
use std::ops::{Add, Sub}; use std::ops::{Add, Sub};
use std::marker::PhantomData; use std::marker::PhantomData;
use apply::Apply; use apply::Apply;
use fraction::Decimal;
use iced::alignment::Horizontal; use iced::alignment::Horizontal;
use iced::{Alignment, Border, Length, Shadow}; use iced::{Alignment, Length};
use crate::{ use crate::{
Element, Element,
widget::{ widget::{
@ -23,7 +24,8 @@ use crate::{
}, },
}; };
pub enum Direction { #[derive(Clone, Copy)]
pub enum Orientation {
Horizontal, Horizontal,
Vertical, Vertical,
} }
@ -32,17 +34,23 @@ pub struct SpinButton<'a, T, M>
where where
T: Add<Output = T> + Sub<Output = T> + PartialEq + PartialOrd + Display + Copy T: Add<Output = T> + Sub<Output = T> + PartialEq + PartialOrd + Display + Copy
{ {
/// The label that the spin button widget will have.
/// It is placed on the top of and centered on the spin button widget itself.
label: String, label: String,
/// The amount to increment or decrement the value. /// The amount to increment or decrement the value.
pub step: T, step: T,
/// The current value of the spin button. /// The current value of the spin button.
pub value: T, /// It is displayed in the center of the spin button widget, no matter the orientation.
value: T,
/// The minimum value permitted. /// The minimum value permitted.
pub min: T, /// If the value is decremented below this value the current value will rollover to the max value.
min: T,
/// The maximum value permitted. /// The maximum value permitted.
pub max: T, /// If the value is incremented above this value the current value will rollover to the min value.
max: T,
/// The direction that the spin button is laid out; Horizontal (default) or Vertical /// The direction that the spin button is laid out; Horizontal (default) or Vertical
pub direction: Direction, orientation: Option<Orientation>,
/// The message that the spin button emits to the application's update function.
on_press: Box<dyn Fn(T) -> M>, on_press: Box<dyn Fn(T) -> M>,
phantom_data: PhantomData<&'a M>, phantom_data: PhantomData<&'a M>,
} }
@ -58,7 +66,7 @@ where
value: T, value: T,
min: T, min: T,
max: T, max: T,
direction: Direction, orientation: Option<Orientation>,
on_press: impl Fn(T) -> M + 'static, on_press: impl Fn(T) -> M + 'static,
) -> Self { ) -> Self {
Self { Self {
@ -67,7 +75,7 @@ where
value, value,
min, min,
max, max,
direction, orientation,
on_press: Box::from(on_press), on_press: Box::from(on_press),
phantom_data: PhantomData, phantom_data: PhantomData,
} }
@ -81,13 +89,13 @@ pub fn spin_button<'a, T, M>(
value: T, value: T,
min: T, min: T,
max: T, max: T,
direction: Direction, orientation: Option<Orientation>,
on_press: impl Fn(T) -> M + 'static, on_press: impl Fn(T) -> M + 'static,
) -> SpinButton<'a, T, M> ) -> SpinButton<'a, T, M>
where where
T: Add<Output = T> + Sub<Output = T> + PartialEq + PartialOrd + Display + Copy T: Add<Output = T> + Sub<Output = T> + PartialEq + PartialOrd + Display + Copy
{ {
SpinButton::new(label, step, value, min, max, direction, on_press) SpinButton::new(label, step, value, min, max, orientation, on_press)
} }
fn increment<T>(step: T, value: T, min: T, max: T) -> T fn increment<T>(step: T, value: T, min: T, max: T) -> T
@ -120,100 +128,118 @@ where
T: Add<Output = T> + Sub<Output = T> + PartialEq + PartialOrd + Display + Copy T: Add<Output = T> + Sub<Output = T> + PartialEq + PartialOrd + Display + Copy
{ {
fn from(this: SpinButton<'a, T, Message>) -> Self { fn from(this: SpinButton<'a, T, Message>) -> Self {
//! Matching on the direction enum given by the developer when the // Matching on the direction enum given by the developer when the
//! widget is initially created in the application's view function. // widget is initially created in the application's view function.
match this.direction { match this.orientation {
Direction::Horizontal => { Some(orien) => match orien {
// Create a spinner container variable that contains the row with all of Orientation::Horizontal => create_horizontal_spin_button(&this),
// the combined widgets that make up the widget. Orientation::Vertical => create_vertical_spin_button(&this)
let spinner_container = column::with_capacity(2)
.push(
row::with_children(
vec![
// Using the title4 variant of text, just like the original spin button did.
text::title4(this.label.clone())
.apply(container)
.center_x(Length::Fill)
.align_y(Alignment::Center)
.into(),
]
)
)
.push(
row::with_children(
vec![
// Using an button instead of an icon for the decrement functionality.
button::icon(icon::from_name("list-remove-symbolic"))
.padding([0, 12])
.on_press((this.on_press)(decrement::<T>(
this.step, this.value, this.min, this.max,
))
)
.into(),
// Using the title4 variant of text for consistency.
text::title4(format!("{}", this.value))
.apply(container)
.center_x(Length::Fixed(48.0))
.align_y(Alignment::Center)
.into(),
// Using another button for the increment functionality.
button::icon(icon::from_name("list-add-symbolic"))
.padding([0, 12])
.on_press((this.on_press)(increment::<T>(
this.step, this.value, this.min, this.max,
))
)
.into(),
]
)
.align_y(Alignment::Center)
)
.align_x(Alignment::Center);
// Return the horizontal spin button from the match statement.
Self::new(spinner_container)
},
Direction::Vertical => {
// Create a text widget that holds the value
let val_text = text(format!("{}", this.value)).size(14);
// Create a spinner container variable that contains the column with all of
// the combined widgets that make up the widget.
let spinner_container = column::with_capacity(3)
.push(
// Use a button for the increment functionality
button::icon(icon::from_name("list-add-symbolic"))
.padding([0, 12])
.on_press((this.on_press)(increment::<T>(
this.step, this.value, this.min, this.max,
))),
)
// Add the text widget that holds the current value
.push(val_text)
.push(
// Use a button for the decrement functionality
button::icon(icon::from_name("list-remove-symbolic"))
.padding([0, 12])
.on_press((this.on_press)(decrement::<T>(
this.step, this.value, this.min, this.max,
))),
)
.align_x(Horizontal::Center);
// Create a column that contains two rows:
// First Row -> The label/title for the spin button.
// Second Row -> The spin button container from above.
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);
// Return the vertical spin button from the match statement.
Self::new(content_list)
} }
// Default behavior is to create a horizontal spin button widget
None => create_horizontal_spin_button(&this),
} }
} }
} }
// Helper Functions
// Create a horizontal spin button
// Implemented to make the creation easier to read in the from function for Element implementation.
fn create_horizontal_spin_button<'a, T, Message>(spin_btn: &SpinButton<T, Message>) -> Element<'a, Message>
where
Message: Clone + 'static,
T: Add<Output = T> + Sub<Output = T> + PartialEq + PartialOrd + Display + Copy
{
// Create a spinner container variable that contains the row with all of
// the combined widgets that make up the widget.
let spinner_container = column::with_capacity(2)
.push(
row::with_children(
vec![
// Using the title4 variant of text, just like the original spin button did.
text::title4(spin_btn.label.clone())
.apply(container)
.center_x(Length::Fill)
.align_y(Alignment::Center)
.into(),
]
)
)
.push(
row::with_children(
vec![
// Using an button instead of an icon for the decrement functionality.
button::icon(icon::from_name("list-remove-symbolic"))
.padding([0, 12])
.on_press((spin_btn.on_press)(decrement::<T>(
spin_btn.step, spin_btn.value, spin_btn.min, spin_btn.max,
))
).into(),
// Using the title4 variant of text for consistency.
text::title4(format!("{}", spin_btn.value))
.apply(container)
.center_x(Length::Fixed(48.0))
.align_y(Alignment::Center)
.into(),
// Using another button for the increment functionality.
button::icon(icon::from_name("list-add-symbolic"))
.padding([0, 12])
.on_press((spin_btn.on_press)(increment::<T>(
spin_btn.step, spin_btn.value, spin_btn.min, spin_btn.max,
))
).into(),
]
)
.align_y(Alignment::Center)
)
.align_x(Alignment::Center);
// Return the horizontal spin button from the match statement.
Element::new(spinner_container)
}
// Used to create a vertical spin button widget.
// Implemented to make the creation easier to read in the from function for Element implementation.
fn create_vertical_spin_button<'a, T, Message>(spin_btn: &SpinButton<T, Message>) -> Element<'a, Message>
where
Message: Clone + 'static,
T: Add<Output = T> + Sub<Output = T> + PartialEq + PartialOrd + Display + Copy
{
// Create a text widget that holds the value
let val_text = text(format!("{}", spin_btn.value)).size(14);
// Create a spinner container variable that contains the column with all of
// the combined widgets that make up the widget.
let spinner_container = column::with_capacity(3)
.push(
// Use a button for the increment functionality
button::icon(icon::from_name("list-add-symbolic"))
.padding([0, 12])
.on_press((spin_btn.on_press)(increment::<T>(
spin_btn.step, spin_btn.value, spin_btn.min, spin_btn.max,
))),
)
// Add the text widget that holds the current value
.push(val_text)
.push(
// Use a button for the decrement functionality
button::icon(icon::from_name("list-remove-symbolic"))
.padding([0, 12])
.on_press((spin_btn.on_press)(decrement::<T>(
spin_btn.step, spin_btn.value, spin_btn.min, spin_btn.max,
))),
)
.align_x(Horizontal::Center);
// Create a column that contains two rows:
// First Row -> The label/title for the spin button.
// Second Row -> The spin button container from above.
let content_list = column::with_children(vec![
row::with_capacity(1).push(text(spin_btn.label.clone())).into(),
row::with_children(vec![Element::from(spinner_container)]).into(),
])
.width(75)
.padding([8, 0])
.align_x(Alignment::Center);
// Return the vertical spin button from the match statement.
Element::new(content_list)
}