ui: Make toggle button group responsive (#36100)

This PR improves the toggle button group to be more responsive across
different layouts. This is accomplished by ensuring each button takes up
the same amount of space in the parent containers layout.

Ideally, this should be done with grids instead of a flexbox container,
as this would be much better suited for this purpose. Yet, since we lack
support for this, we go with this route for now.

| Before | After |
| --- | --- |
| <img width="1608" height="1094" alt="Bildschirmfoto 2025-08-13 um 11
24 26"
src="https://github.com/user-attachments/assets/2a4b5a59-6483-4f79-8fcb-e26e22071795"
/> | <img width="1608" height="1094" alt="Bildschirmfoto 2025-08-13 um
11 29 36"
src="https://github.com/user-attachments/assets/e6402729-6a8f-4a44-b79e-a569406edfff"
/> |


Release Notes:

- N/A
This commit is contained in:
Finn Evers 2025-08-13 14:02:20 +02:00 committed by GitHub
parent 6307105976
commit 7f1a5c6ad7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 50 additions and 36 deletions

View file

@ -3011,7 +3011,7 @@ impl EditorElement {
.icon_color(Color::Custom(cx.theme().colors().editor_line_number)) .icon_color(Color::Custom(cx.theme().colors().editor_line_number))
.selected_icon_color(Color::Custom(cx.theme().colors().editor_foreground)) .selected_icon_color(Color::Custom(cx.theme().colors().editor_foreground))
.icon_size(IconSize::Custom(rems(editor_font_size / window.rem_size()))) .icon_size(IconSize::Custom(rems(editor_font_size / window.rem_size())))
.width(width.into()) .width(width)
.on_click(move |_, window, cx| { .on_click(move |_, window, cx| {
editor.update(cx, |editor, cx| { editor.update(cx, |editor, cx| {
editor.expand_excerpt(excerpt_id, direction, window, cx); editor.expand_excerpt(excerpt_id, direction, window, cx);
@ -3627,7 +3627,7 @@ impl EditorElement {
ButtonLike::new("toggle-buffer-fold") ButtonLike::new("toggle-buffer-fold")
.style(ui::ButtonStyle::Transparent) .style(ui::ButtonStyle::Transparent)
.height(px(28.).into()) .height(px(28.).into())
.width(px(28.).into()) .width(px(28.))
.children(toggle_chevron_icon) .children(toggle_chevron_icon)
.tooltip({ .tooltip({
let focus_handle = focus_handle.clone(); let focus_handle = focus_handle.clone();

View file

@ -58,7 +58,7 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement
.tab_index(tab_index) .tab_index(tab_index)
.selected_index(theme_mode as usize) .selected_index(theme_mode as usize)
.style(ui::ToggleButtonGroupStyle::Outlined) .style(ui::ToggleButtonGroupStyle::Outlined)
.button_width(rems_from_px(64.)), .width(rems_from_px(3. * 64.)),
), ),
) )
.child( .child(
@ -305,8 +305,8 @@ fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoE
.when_some(base_keymap, |this, base_keymap| { .when_some(base_keymap, |this, base_keymap| {
this.selected_index(base_keymap) this.selected_index(base_keymap)
}) })
.full_width()
.tab_index(tab_index) .tab_index(tab_index)
.button_width(rems_from_px(216.))
.size(ui::ToggleButtonGroupSize::Medium) .size(ui::ToggleButtonGroupSize::Medium)
.style(ui::ToggleButtonGroupStyle::Outlined), .style(ui::ToggleButtonGroupStyle::Outlined),
); );

View file

@ -706,7 +706,7 @@ fn render_popular_settings_section(
}) })
.tab_index(tab_index) .tab_index(tab_index)
.style(ToggleButtonGroupStyle::Outlined) .style(ToggleButtonGroupStyle::Outlined)
.button_width(ui::rems_from_px(64.)), .width(ui::rems_from_px(3. * 64.)),
), ),
) )
} }

View file

@ -295,7 +295,7 @@ impl NotebookEditor {
_cx: &mut Context<Self>, _cx: &mut Context<Self>,
) -> IconButton { ) -> IconButton {
let id: ElementId = ElementId::Name(id.into()); let id: ElementId = ElementId::Name(id.into());
IconButton::new(id, icon).width(px(CONTROL_SIZE).into()) IconButton::new(id, icon).width(px(CONTROL_SIZE))
} }
fn render_notebook_controls( fn render_notebook_controls(

View file

@ -324,7 +324,7 @@ impl FixedWidth for Button {
/// ``` /// ```
/// ///
/// This sets the button's width to be exactly 100 pixels. /// This sets the button's width to be exactly 100 pixels.
fn width(mut self, width: DefiniteLength) -> Self { fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
self.base = self.base.width(width); self.base = self.base.width(width);
self self
} }

View file

@ -499,8 +499,8 @@ impl Clickable for ButtonLike {
} }
impl FixedWidth for ButtonLike { impl FixedWidth for ButtonLike {
fn width(mut self, width: DefiniteLength) -> Self { fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
self.width = Some(width); self.width = Some(width.into());
self self
} }

View file

@ -133,7 +133,7 @@ impl Clickable for IconButton {
} }
impl FixedWidth for IconButton { impl FixedWidth for IconButton {
fn width(mut self, width: DefiniteLength) -> Self { fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
self.base = self.base.width(width); self.base = self.base.width(width);
self self
} }
@ -194,7 +194,7 @@ impl RenderOnce for IconButton {
.map(|this| match self.shape { .map(|this| match self.shape {
IconButtonShape::Square => { IconButtonShape::Square => {
let size = self.icon_size.square(window, cx); let size = self.icon_size.square(window, cx);
this.width(size.into()).height(size.into()) this.width(size).height(size.into())
} }
IconButtonShape::Wide => this, IconButtonShape::Wide => this,
}) })

View file

@ -1,6 +1,6 @@
use std::rc::Rc; use std::rc::Rc;
use gpui::{AnyView, ClickEvent}; use gpui::{AnyView, ClickEvent, relative};
use crate::{ButtonLike, ButtonLikeRounding, ElevationIndex, TintColor, Tooltip, prelude::*}; use crate::{ButtonLike, ButtonLikeRounding, ElevationIndex, TintColor, Tooltip, prelude::*};
@ -73,8 +73,8 @@ impl SelectableButton for ToggleButton {
} }
impl FixedWidth for ToggleButton { impl FixedWidth for ToggleButton {
fn width(mut self, width: DefiniteLength) -> Self { fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
self.base.width = Some(width); self.base.width = Some(width.into());
self self
} }
@ -429,7 +429,7 @@ where
rows: [[T; COLS]; ROWS], rows: [[T; COLS]; ROWS],
style: ToggleButtonGroupStyle, style: ToggleButtonGroupStyle,
size: ToggleButtonGroupSize, size: ToggleButtonGroupSize,
button_width: Rems, group_width: Option<DefiniteLength>,
selected_index: usize, selected_index: usize,
tab_index: Option<isize>, tab_index: Option<isize>,
} }
@ -441,7 +441,7 @@ impl<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS> {
rows: [buttons], rows: [buttons],
style: ToggleButtonGroupStyle::Transparent, style: ToggleButtonGroupStyle::Transparent,
size: ToggleButtonGroupSize::Default, size: ToggleButtonGroupSize::Default,
button_width: rems_from_px(100.), group_width: None,
selected_index: 0, selected_index: 0,
tab_index: None, tab_index: None,
} }
@ -455,7 +455,7 @@ impl<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS, 2> {
rows: [first_row, second_row], rows: [first_row, second_row],
style: ToggleButtonGroupStyle::Transparent, style: ToggleButtonGroupStyle::Transparent,
size: ToggleButtonGroupSize::Default, size: ToggleButtonGroupSize::Default,
button_width: rems_from_px(100.), group_width: None,
selected_index: 0, selected_index: 0,
tab_index: None, tab_index: None,
} }
@ -473,11 +473,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> ToggleButtonGroup<T
self self
} }
pub fn button_width(mut self, button_width: Rems) -> Self {
self.button_width = button_width;
self
}
pub fn selected_index(mut self, index: usize) -> Self { pub fn selected_index(mut self, index: usize) -> Self {
self.selected_index = index; self.selected_index = index;
self self
@ -491,6 +486,24 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> ToggleButtonGroup<T
*tab_index += (COLS * ROWS) as isize; *tab_index += (COLS * ROWS) as isize;
self self
} }
const fn button_width() -> DefiniteLength {
relative(1. / COLS as f32)
}
}
impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> FixedWidth
for ToggleButtonGroup<T, COLS, ROWS>
{
fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
self.group_width = Some(width.into());
self
}
fn full_width(mut self) -> Self {
self.group_width = Some(relative(1.));
self
}
} }
impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
@ -511,6 +524,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
let entry_index = row_index * COLS + col_index; let entry_index = row_index * COLS + col_index;
ButtonLike::new((self.group_name, entry_index)) ButtonLike::new((self.group_name, entry_index))
.full_width()
.rounding(None) .rounding(None)
.when_some(self.tab_index, |this, tab_index| { .when_some(self.tab_index, |this, tab_index| {
this.tab_index(tab_index + entry_index as isize) this.tab_index(tab_index + entry_index as isize)
@ -527,7 +541,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
}) })
.child( .child(
h_flex() h_flex()
.min_w(self.button_width) .w_full()
.gap_1p5() .gap_1p5()
.px_3() .px_3()
.py_1() .py_1()
@ -561,6 +575,13 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
let is_transparent = self.style == ToggleButtonGroupStyle::Transparent; let is_transparent = self.style == ToggleButtonGroupStyle::Transparent;
v_flex() v_flex()
.map(|this| {
if let Some(width) = self.group_width {
this.w(width)
} else {
this.w_full()
}
})
.rounded_md() .rounded_md()
.overflow_hidden() .overflow_hidden()
.map(|this| { .map(|this| {
@ -583,6 +604,8 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
.when(is_outlined_or_filled && !last_item, |this| { .when(is_outlined_or_filled && !last_item, |this| {
this.border_r_1().border_color(border_color) this.border_r_1().border_color(border_color)
}) })
.w(Self::button_width())
.overflow_hidden()
.child(item) .child(item)
})) }))
})) }))
@ -630,7 +653,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(1) .selected_index(1)
.button_width(rems_from_px(100.))
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
@ -656,7 +678,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(1) .selected_index(1)
.button_width(rems_from_px(100.))
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
@ -675,7 +696,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(3) .selected_index(3)
.button_width(rems_from_px(100.))
.into_any_element(), .into_any_element(),
), ),
single_example( single_example(
@ -718,7 +738,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(3) .selected_index(3)
.button_width(rems_from_px(100.))
.into_any_element(), .into_any_element(),
), ),
], ],
@ -763,7 +782,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(1) .selected_index(1)
.button_width(rems_from_px(100.))
.style(ToggleButtonGroupStyle::Outlined) .style(ToggleButtonGroupStyle::Outlined)
.into_any_element(), .into_any_element(),
), ),
@ -783,7 +801,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(3) .selected_index(3)
.button_width(rems_from_px(100.))
.style(ToggleButtonGroupStyle::Outlined) .style(ToggleButtonGroupStyle::Outlined)
.into_any_element(), .into_any_element(),
), ),
@ -827,7 +844,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(3) .selected_index(3)
.button_width(rems_from_px(100.))
.style(ToggleButtonGroupStyle::Outlined) .style(ToggleButtonGroupStyle::Outlined)
.into_any_element(), .into_any_element(),
), ),
@ -873,7 +889,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(1) .selected_index(1)
.button_width(rems_from_px(100.))
.style(ToggleButtonGroupStyle::Filled) .style(ToggleButtonGroupStyle::Filled)
.into_any_element(), .into_any_element(),
), ),
@ -893,7 +908,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(3) .selected_index(3)
.button_width(rems_from_px(100.)) .width(rems_from_px(100.))
.style(ToggleButtonGroupStyle::Filled) .style(ToggleButtonGroupStyle::Filled)
.into_any_element(), .into_any_element(),
), ),
@ -937,7 +952,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(3) .selected_index(3)
.button_width(rems_from_px(100.)) .width(rems_from_px(100.))
.style(ToggleButtonGroupStyle::Filled) .style(ToggleButtonGroupStyle::Filled)
.into_any_element(), .into_any_element(),
), ),
@ -957,7 +972,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
], ],
) )
.selected_index(1) .selected_index(1)
.button_width(rems_from_px(100.))
.into_any_element(), .into_any_element(),
)]) )])
.into_any_element(), .into_any_element(),

View file

@ -3,7 +3,7 @@ use gpui::DefiniteLength;
/// A trait for elements that can have a fixed with. Enables the use of the `width` and `full_width` methods. /// A trait for elements that can have a fixed with. Enables the use of the `width` and `full_width` methods.
pub trait FixedWidth { pub trait FixedWidth {
/// Sets the width of the element. /// Sets the width of the element.
fn width(self, width: DefiniteLength) -> Self; fn width(self, width: impl Into<DefiniteLength>) -> Self;
/// Sets the element's width to the full width of its container. /// Sets the element's width to the full width of its container.
fn full_width(self) -> Self; fn full_width(self) -> Self;

View file

@ -216,7 +216,7 @@ impl QuickActionBar {
.size(IconSize::XSmall) .size(IconSize::XSmall)
.color(Color::Muted), .color(Color::Muted),
) )
.width(rems(1.).into()) .width(rems(1.))
.disabled(menu_state.popover_disabled), .disabled(menu_state.popover_disabled),
Tooltip::text("REPL Menu"), Tooltip::text("REPL Menu"),
); );