From f630d66c679f31883ecc039d8cecc2915a0a4c9d Mon Sep 17 00:00:00 2001 From: Eduardo Flores Date: Fri, 14 Oct 2022 19:24:14 -0700 Subject: [PATCH] Segmented button - Implemented the segmented button widget --- examples/cosmic/src/window.rs | 55 ++++++++++++++++++-- src/widget/mod.rs | 5 +- src/widget/segmented_button.rs | 91 ++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 src/widget/segmented_button.rs diff --git a/examples/cosmic/src/window.rs b/examples/cosmic/src/window.rs index 2ca6775c..1657822d 100644 --- a/examples/cosmic/src/window.rs +++ b/examples/cosmic/src/window.rs @@ -9,7 +9,7 @@ use cosmic::{ iced_lazy::responsive, iced_winit::window::{drag, toggle_maximize, minimize}, theme::{self, Theme}, - widget::{button, nav_button, nav_bar, header_bar, settings, scrollable, toggler, nav_bar_item}, + widget::{button, nav_button, nav_bar, header_bar, settings, scrollable, toggler, nav_bar_item, segmented_button}, Element, ElementExt, }; @@ -62,6 +62,7 @@ pub enum Message { TogglerToggled(bool), PickListSelected(&'static str), RowSelected(usize), + SegmentatedButtonPressed(usize), Close, ToggleSidebar, Drag, @@ -110,6 +111,9 @@ impl Application for Window { Message::Minimize => return minimize(true), Message::Maximize => return toggle_maximize(), Message::RowSelected(row) => println!("Selected row {row}"), + Message::SegmentatedButtonPressed(index) => { + println!("Segmented button at index: {index}") + } } Command::none() @@ -143,6 +147,7 @@ impl Application for Window { let content = responsive(|size| { let condensed = size.width < 900.0; let sidebar: Element<_> = nav_bar() + // TODO: Make this take an iterator .source(BTreeMap::from([ ( nav_bar_item() @@ -272,10 +277,50 @@ impl Application for Window { .width(Length::Units(250)) .height(Length::Units(4)) )) - .into() - ]) - .into(); - + .add(settings::item( + "Segmented Button", + segmented_button() + .options(vec!["Vertical", "Horizontal"]) + .on_button_pressed(Box::new(Message::SegmentatedButtonPressed)) + )) + .add(settings::item( + "Checkbox", + checkbox("Checkbox", self.checkbox_value, Message::CheckboxToggled) + )) + .into(), + settings::view_section("Expander") + .add(settings::item( + "Expander", + expander() + .title("Label") + .subtitle("Caption") + .icon(String::from("edit-paste")) + .on_row_selected(Box::new(Message::RowSelected)) + .rows(vec![ + list_row() + .title("Label") + .subtitle("Caption") + .icon(String::from("help-about")), + list_row().subtitle("Caption").title("Label"), + list_row().title("Label") + ]) + )).into(), + settings::view_section("List Box") + .add(settings::item( + "List Box", + list_box() + .style(theme::Container::Custom(list_section_style)) + .children(vec![ + cosmic::list_box_row!("Title").into(), + cosmic::list_box_row!("Title", "Subtitle").into(), + cosmic::list_box_row!("Title", "", "edit-paste").into(), + cosmic::list_box_row!("", "Subtitle", "edit-paste").into(), + cosmic::list_box_row!("Title", "Subtitle", "edit-paste").into() + ]) + .render() + )).into(), + ]); + let mut widgets = Vec::with_capacity(2); widgets.push(sidebar.debug(self.debug)); diff --git a/src/widget/mod.rs b/src/widget/mod.rs index f1940c37..10ead141 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -28,4 +28,7 @@ mod scrollable; pub use scrollable::*; pub mod separator; -pub use separator::{horizontal_rule, vertical_rule}; +pub use separator::*; + +pub mod segmented_button; +pub use segmented_button::*; diff --git a/src/widget/segmented_button.rs b/src/widget/segmented_button.rs new file mode 100644 index 00000000..0196f44b --- /dev/null +++ b/src/widget/segmented_button.rs @@ -0,0 +1,91 @@ +use derive_setters::Setters; +use iced::{ + alignment::{Horizontal, Vertical}, + theme, + widget::{button, container, text, Row}, + Background, Element, Length, Renderer, Theme, +}; +use iced_lazy::Component; + +#[derive(Setters)] +pub struct SegmentedButton<'a, Message> { + options: Vec<&'a str>, + #[setters(strip_option)] + on_button_pressed: Option Message>>, +} + +pub fn segmented_button<'a, Message>() -> SegmentedButton<'a, Message> { + SegmentedButton { + options: vec![], + on_button_pressed: None, + } +} + +#[derive(Clone)] +pub enum SegmentedButtonEvent { + Pressed(usize), +} + +impl<'a, Message> Component for SegmentedButton<'a, Message> { + type State = usize; + + type Event = SegmentedButtonEvent; + + fn update(&mut self, state: &mut Self::State, event: Self::Event) -> Option { + match event { + SegmentedButtonEvent::Pressed(index) => { + *state = index; + self.on_button_pressed + .as_ref() + .map(|on_button_pressed| (on_button_pressed)(index)) + } + } + } + + fn view(&self, state: &Self::State) -> Element { + let buttons: Vec> = self + .options + .iter() + .enumerate() + .map(|(index, option)| { + button( + container(text(*option)) + .width(Length::Fill) + .height(Length::Fill) + .align_x(Horizontal::Center) + .align_y(Vertical::Center), + ) + .width(Length::Units(136)) + .height(Length::Units(32)) + .style(if *state == index { + theme::Button::Secondary.into() + } else { + theme::Button::Primary.into() + }) + .on_press(SegmentedButtonEvent::Pressed(index)) + .into() + }) + .collect(); + container(Row::with_children(buttons).spacing(5).width(Length::Shrink)) + .padding(2) + .style(theme::Container::Custom(view_switcher_container_style)) + .into() + } +} + +pub fn view_switcher_container_style(theme: &Theme) -> iced_style::container::Appearance { + let accent = &theme.cosmic().accent; + iced_style::container::Appearance { + text_color: Default::default(), + background: Some(Background::Color(accent.base.into())), + border_radius: 24.0, + border_width: 2.0, + border_color: accent.base.into(), + } +} + +impl<'a, Message: 'a> From> for Element<'a, Message> { + fn from(segmented_button: SegmentedButton<'a, Message>) -> Self { + iced_lazy::component(segmented_button) + } +}