Buttons · ToggleButton disabled: shown, not pressable, no tab stop, says it is unavailable; its tooltip can give the reason

This commit is contained in:
ZacharyZhang-NY committed 2026-10-04 07:13:14 -04:00
1 parent b6cb94cad4
commit c0f17eeb48
2 files changed
+63 -16

No files matched your search

+25 -13
View File
@@ -10,6 +10,7 @@ use super::{
button::{Slot, label_size}, button::{Slot, label_size},
}; };
use crate::{ use crate::{
i18n,
primitives::{FocusRing, Icon, IconName, Tooltip}, primitives::{FocusRing, Icon, IconName, Tooltip},
theme::{ActiveTheme, ControlSize, Radius}, theme::{ActiveTheme, ControlSize, Radius},
}; };
@@ -93,7 +94,7 @@ fn toggle_face(
item: &ToggleItem, item: &ToggleItem,
on: bool, on: bool,
size: ControlSize, size: ControlSize,
press: OnPress, press: Option<OnPress>,
cx: &App, cx: &App,
) -> impl IntoElement + use<> { ) -> impl IntoElement + use<> {
assert!( assert!(
@@ -125,18 +126,20 @@ fn toggle_face(
.text_size(theme.text_size(text)) .text_size(theme.text_size(text))
.font_weight(FontWeight::MEDIUM) .font_weight(FontWeight::MEDIUM)
.text_color(fg) .text_color(fg)
.map(|face| { .when(on, |face| face.bg(colors.active))
if on { .map(|face| match press {
face.bg(colors.active) None => face
} else { .aria_description(i18n::text(cx, "state.unavailable", &[]))
face.hover(|style| style.bg(hover)) .opacity(0.45)
} .cursor_not_allowed(),
Some(press) => face
.when(!on, |face| face.hover(|style| style.bg(hover)))
.cursor_pointer()
.tab_index(0)
.focus_ring(cx)
.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
.on_click(move |_, window, cx| press(window, cx)),
}) })
.cursor_pointer()
.tab_index(0)
.focus_ring(cx)
.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
.on_click(move |_, window, cx| press(window, cx))
.when_some(item.icon, |face, icon| { .when_some(item.icon, |face, icon| {
face.child(Icon::new(icon).size(icon_size).color(fg)) face.child(Icon::new(icon).size(icon_size).color(fg))
}) })
@@ -154,6 +157,7 @@ pub struct ToggleButton {
id: ElementId, id: ElementId,
item: ToggleItem, item: ToggleItem,
on: bool, on: bool,
disabled: bool,
size: ControlSize, size: ControlSize,
on_toggle: Option<OnToggle>, on_toggle: Option<OnToggle>,
} }
@@ -164,6 +168,7 @@ impl ToggleButton {
id: id.into(), id: id.into(),
item, item,
on, on,
disabled: false,
size: ControlSize::default(), size: ControlSize::default(),
on_toggle: None, on_toggle: None,
} }
@@ -174,6 +179,12 @@ impl ToggleButton {
self self
} }
/// Shown, not pressable; the item's tooltip can say why.
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn on_toggle(mut self, handler: impl Fn(bool, &mut Window, &mut App) + 'static) -> Self { pub fn on_toggle(mut self, handler: impl Fn(bool, &mut Window, &mut App) + 'static) -> Self {
self.on_toggle = Some(Rc::new(handler)); self.on_toggle = Some(Rc::new(handler));
self self
@@ -189,6 +200,7 @@ impl RenderOnce for ToggleButton {
toggle(!on, window, cx); toggle(!on, window, cx);
} }
}); });
let press = (!self.disabled).then_some(press);
toggle_face(self.id, &self.item, on, self.size, press, cx) toggle_face(self.id, &self.item, on, self.size, press, cx)
} }
} }
@@ -280,7 +292,7 @@ impl RenderOnce for ToggleGroup {
item, item,
on, on,
self.size, self.size,
press, Some(press),
cx, cx,
) )
}) })
+38 -3
View File
@@ -1,9 +1,9 @@
use gpui::{ use gpui::{
Context, FocusHandle, IntoElement, KeyBinding, KeyUpEvent, Keystroke, ParentElement, Render, Context, FocusHandle, IntoElement, KeyBinding, KeyUpEvent, Keystroke, Modifiers, ParentElement,
Styled, TestAppContext, VisualTestContext, Window, Render, Styled, TestAppContext, VisualTestContext, Window, div, point, px,
}; };
use super::Button; use super::{Button, ToggleButton, ToggleItem};
use crate::{ use crate::{
primitives::{FocusNext, FocusScope}, primitives::{FocusNext, FocusScope},
theme::Theme, theme::Theme,
@@ -63,3 +63,38 @@ fn a_loading_button_ignores_presses_and_keeps_its_tab_stop(cx: &mut TestAppConte
"focus stayed on it" "focus stayed on it"
); );
} }
/// One toggle that counts its presses.
struct Toggles {
disabled: bool,
presses: usize,
}
impl Render for Toggles {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
div().size_full().child(
ToggleButton::new("bold", ToggleItem::new("bold").label("B"), false)
.disabled(self.disabled)
.on_toggle(move |_, _, cx| view.update(cx, |toggles, _| toggles.presses += 1)),
)
}
}
#[gpui::test]
fn a_disabled_toggle_ignores_clicks(cx: &mut TestAppContext) {
cx.update(Theme::init);
let (view, cx) = cx.add_window_view(|_, _| Toggles {
disabled: true,
presses: 0,
});
cx.simulate_click(point(px(4.0), px(4.0)), Modifiers::none());
assert_eq!(view.read_with(cx, |toggles, _| toggles.presses), 0);
view.update(cx, |toggles, cx| {
toggles.disabled = false;
cx.notify();
});
cx.run_until_parked();
cx.simulate_click(point(px(4.0), px(4.0)), Modifiers::none());
assert_eq!(view.read_with(cx, |toggles, _| toggles.presses), 1);
}