From 57766199cfcf9fba1f920db9958ff45ac4fff1dd Mon Sep 17 00:00:00 2001 From: Finn Evers Date: Wed, 30 Jul 2025 00:47:04 +0200 Subject: [PATCH] ui: Clean up toggle button group component (#35303) This change cleans up the toggle button component a bit by utilizing const parameters instead and also removes some clones by consuming the values where possible instead. Release Notes: - N/A --- .../ui/src/components/button/toggle_button.rs | 238 +++++++----------- 1 file changed, 94 insertions(+), 144 deletions(-) diff --git a/crates/ui/src/components/button/toggle_button.rs b/crates/ui/src/components/button/toggle_button.rs index c6cf7ac62c..30683e60f3 100644 --- a/crates/ui/src/components/button/toggle_button.rs +++ b/crates/ui/src/components/button/toggle_button.rs @@ -291,14 +291,18 @@ impl Component for ToggleButton { } } -mod private { - pub trait Sealed {} +pub struct ButtonConfiguration { + label: SharedString, + icon: Option, + on_click: Box, } -pub trait ButtonBuilder: 'static + private::Sealed { - fn label(&self) -> impl Into; - fn icon(&self) -> Option; - fn on_click(self) -> Box; +mod private { + pub trait ToggleButtonStyle {} +} + +pub trait ButtonBuilder: 'static + private::ToggleButtonStyle { + fn into_configuration(self) -> ButtonConfiguration; } pub struct ToggleButtonSimple { @@ -318,19 +322,15 @@ impl ToggleButtonSimple { } } -impl private::Sealed for ToggleButtonSimple {} +impl private::ToggleButtonStyle for ToggleButtonSimple {} impl ButtonBuilder for ToggleButtonSimple { - fn label(&self) -> impl Into { - self.label.clone() - } - - fn icon(&self) -> Option { - None - } - - fn on_click(self) -> Box { - self.on_click + fn into_configuration(self) -> ButtonConfiguration { + ButtonConfiguration { + label: self.label, + icon: None, + on_click: self.on_click, + } } } @@ -354,58 +354,14 @@ impl ToggleButtonWithIcon { } } -impl private::Sealed for ToggleButtonWithIcon {} +impl private::ToggleButtonStyle for ToggleButtonWithIcon {} impl ButtonBuilder for ToggleButtonWithIcon { - fn label(&self) -> impl Into { - self.label.clone() - } - - fn icon(&self) -> Option { - Some(self.icon) - } - - fn on_click(self) -> Box { - self.on_click - } -} - -struct ToggleButtonRow { - items: Vec, - index_offset: usize, - last_item_idx: usize, - is_last_row: bool, -} - -impl ToggleButtonRow { - fn new(items: Vec, index_offset: usize, is_last_row: bool) -> Self { - Self { - index_offset, - last_item_idx: index_offset + items.len() - 1, - is_last_row, - items, - } - } -} - -enum ToggleButtonGroupRows { - Single(Vec), - Multiple(Vec, Vec), -} - -impl ToggleButtonGroupRows { - fn items(self) -> impl IntoIterator> { - match self { - ToggleButtonGroupRows::Single(items) => { - vec![ToggleButtonRow::new(items, 0, true)] - } - ToggleButtonGroupRows::Multiple(first_row, second_row) => { - let row_len = first_row.len(); - vec![ - ToggleButtonRow::new(first_row, 0, false), - ToggleButtonRow::new(second_row, row_len, true), - ] - } + fn into_configuration(self) -> ButtonConfiguration { + ButtonConfiguration { + label: self.label, + icon: Some(self.icon), + on_click: self.on_click, } } } @@ -418,48 +374,42 @@ pub enum ToggleButtonGroupStyle { } #[derive(IntoElement)] -pub struct ToggleButtonGroup +pub struct ToggleButtonGroup where T: ButtonBuilder, { - group_name: SharedString, - rows: ToggleButtonGroupRows, + group_name: &'static str, + rows: [[T; COLS]; ROWS], style: ToggleButtonGroupStyle, button_width: Rems, selected_index: usize, } -impl ToggleButtonGroup { - pub fn single_row( - group_name: impl Into, - buttons: impl IntoIterator, - ) -> Self { +impl ToggleButtonGroup { + pub fn single_row(group_name: &'static str, buttons: [T; COLS]) -> Self { Self { - group_name: group_name.into(), - rows: ToggleButtonGroupRows::Single(Vec::from_iter(buttons)), + group_name, + rows: [buttons], style: ToggleButtonGroupStyle::Transparent, button_width: rems_from_px(100.), selected_index: 0, } } +} - pub fn multiple_rows( - group_name: impl Into, - first_row: [T; ROWS], - second_row: [T; ROWS], - ) -> Self { +impl ToggleButtonGroup { + pub fn two_rows(group_name: &'static str, first_row: [T; COLS], second_row: [T; COLS]) -> Self { Self { - group_name: group_name.into(), - rows: ToggleButtonGroupRows::Multiple( - Vec::from_iter(first_row), - Vec::from_iter(second_row), - ), + group_name, + rows: [first_row, second_row], style: ToggleButtonGroupStyle::Transparent, button_width: rems_from_px(100.), selected_index: 0, } } +} +impl ToggleButtonGroup { pub fn style(mut self, style: ToggleButtonGroupStyle) -> Self { self.style = style; self @@ -476,60 +426,56 @@ impl ToggleButtonGroup { } } -impl RenderOnce for ToggleButtonGroup { +impl RenderOnce + for ToggleButtonGroup +{ fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { - let rows = self.rows.items().into_iter().map(|row| { - ( - row.items - .into_iter() - .enumerate() - .map(move |(index, item)| (index + row.index_offset, row.last_item_idx, item)) - .map(|(index, last_item_idx, item)| { - ( - ButtonLike::new((self.group_name.clone(), index)) - .when(index == self.selected_index, |this| { - this.toggle_state(true) - .selected_style(ButtonStyle::Tinted(TintColor::Accent)) - }) - .rounding(None) - .when(self.style == ToggleButtonGroupStyle::Filled, |button| { - button.style(ButtonStyle::Filled) - }) - .child( - h_flex() - .min_w(self.button_width) - .gap_1p5() - .justify_center() - .when_some(item.icon(), |this, icon| { - this.child(Icon::new(icon).size(IconSize::XSmall).map( - |this| { - if index == self.selected_index { - this.color(Color::Accent) - } else { - this.color(Color::Muted) - } - }, - )) - }) - .child( - Label::new(item.label()) - .when(index == self.selected_index, |this| { - this.color(Color::Accent) - }), - ), - ) - .on_click(item.on_click()), - index == last_item_idx, - ) - }), - row.is_last_row, - ) + let entries = self.rows.into_iter().enumerate().map(|(row_index, row)| { + row.into_iter().enumerate().map(move |(index, button)| { + let ButtonConfiguration { + label, + icon, + on_click, + } = button.into_configuration(); + + ButtonLike::new((self.group_name, row_index * COLS + index)) + .when(index == self.selected_index, |this| { + this.toggle_state(true) + .selected_style(ButtonStyle::Tinted(TintColor::Accent)) + }) + .rounding(None) + .when(self.style == ToggleButtonGroupStyle::Filled, |button| { + button.style(ButtonStyle::Filled) + }) + .child( + h_flex() + .min_w(self.button_width) + .gap_1p5() + .justify_center() + .when_some(icon, |this, icon| { + this.child(Icon::new(icon).size(IconSize::XSmall).map(|this| { + if index == self.selected_index { + this.color(Color::Accent) + } else { + this.color(Color::Muted) + } + })) + }) + .child( + Label::new(label).when(index == self.selected_index, |this| { + this.color(Color::Accent) + }), + ), + ) + .on_click(on_click) + .into_any_element() + }) }); + let border_color = cx.theme().colors().border.opacity(0.6); let is_outlined_or_filled = self.style == ToggleButtonGroupStyle::Outlined || self.style == ToggleButtonGroupStyle::Filled; let is_transparent = self.style == ToggleButtonGroupStyle::Transparent; - let border_color = cx.theme().colors().border.opacity(0.6); v_flex() .rounded_md() @@ -541,13 +487,15 @@ impl RenderOnce for ToggleButtonGroup { this.border_1().border_color(border_color) } }) - .children(rows.map(|(items, last_row)| { + .children(entries.enumerate().map(|(row_index, row)| { + let last_row = row_index == ROWS - 1; h_flex() .when(!is_outlined_or_filled, |this| this.gap_px()) .when(is_outlined_or_filled && !last_row, |this| { this.border_b_1().border_color(border_color) }) - .children(items.map(|(item, last_item)| { + .children(row.enumerate().map(|(item_index, item)| { + let last_item = item_index == COLS - 1; div() .when(is_outlined_or_filled && !last_item, |this| { this.border_r_1().border_color(border_color) @@ -566,7 +514,9 @@ component::__private::inventory::submit! { component::ComponentFn::new(register_toggle_button_group) } -impl Component for ToggleButtonGroup { +impl Component + for ToggleButtonGroup +{ fn name() -> &'static str { "ToggleButtonGroup" } @@ -628,7 +578,7 @@ impl Component for ToggleButtonGroup { ), single_example( "Multiple Row Group", - ToggleButtonGroup::multiple_rows( + ToggleButtonGroup::two_rows( "multiple_row_test", [ ToggleButtonSimple::new("First", |_, _, _| {}), @@ -647,7 +597,7 @@ impl Component for ToggleButtonGroup { ), single_example( "Multiple Row Group with Icons", - ToggleButtonGroup::multiple_rows( + ToggleButtonGroup::two_rows( "multiple_row_test_icons", [ ToggleButtonWithIcon::new( @@ -736,7 +686,7 @@ impl Component for ToggleButtonGroup { ), single_example( "Multiple Row Group", - ToggleButtonGroup::multiple_rows( + ToggleButtonGroup::two_rows( "multiple_row_test", [ ToggleButtonSimple::new("First", |_, _, _| {}), @@ -756,7 +706,7 @@ impl Component for ToggleButtonGroup { ), single_example( "Multiple Row Group with Icons", - ToggleButtonGroup::multiple_rows( + ToggleButtonGroup::two_rows( "multiple_row_test", [ ToggleButtonWithIcon::new( @@ -846,7 +796,7 @@ impl Component for ToggleButtonGroup { ), single_example( "Multiple Row Group", - ToggleButtonGroup::multiple_rows( + ToggleButtonGroup::two_rows( "multiple_row_test", [ ToggleButtonSimple::new("First", |_, _, _| {}), @@ -866,7 +816,7 @@ impl Component for ToggleButtonGroup { ), single_example( "Multiple Row Group with Icons", - ToggleButtonGroup::multiple_rows( + ToggleButtonGroup::two_rows( "multiple_row_test", [ ToggleButtonWithIcon::new(