Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T04:33:13.678Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

button_like.rs

982 lines · 36.8 KB · rust
1use documented::Documented;
2use gpui::{
3    AnyElement, AnyView, ClickEvent, CursorStyle, DefiniteLength, FocusHandle, Hsla, MouseButton,
4    MouseClickEvent, MouseDownEvent, MouseUpEvent, Rems, Role, StyleRefinement, Toggled, relative,
5    transparent_black,
6};
7use smallvec::SmallVec;
8
9use crate::{DynamicSpacing, ElevationIndex, prelude::*};
10
11/// A trait for buttons that can be Selected. Enables setting the [`ButtonStyle`] of a button when it is selected.
12pub trait SelectableButton: Toggleable {
13    fn selected_style(self, style: ButtonStyle) -> Self;
14}
15
16/// A common set of traits all buttons must implement.
17pub trait ButtonCommon: Clickable + Disableable {
18    /// A unique element ID to identify the button.
19    fn id(&self) -> &ElementId;
20
21    /// The visual style of the button.
22    ///
23    /// Most commonly will be [`ButtonStyle::Subtle`], or [`ButtonStyle::Filled`]
24    /// for an emphasized button.
25    fn style(self, style: ButtonStyle) -> Self;
26
27    /// The size of the button.
28    ///
29    /// Most buttons will use the default size.
30    ///
31    /// [`ButtonSize`] can also be used to help build non-button elements
32    /// that are consistently sized with buttons.
33    fn size(self, size: ButtonSize) -> Self;
34
35    /// The tooltip that shows when a user hovers over the button.
36    ///
37    /// Nearly all interactable elements should have a tooltip. Some example
38    /// exceptions might a scroll bar, or a slider.
39    fn tooltip(self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self;
40
41    fn tab_index(self, tab_index: impl Into<isize>) -> Self;
42
43    fn layer(self, elevation: ElevationIndex) -> Self;
44
45    fn track_focus(self, focus_handle: &FocusHandle) -> Self;
46}
47
48#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
49pub enum IconPosition {
50    #[default]
51    Start,
52    End,
53}
54
55#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
56pub enum KeybindingPosition {
57    Start,
58    #[default]
59    End,
60}
61
62#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
63pub enum TintColor {
64    #[default]
65    Accent,
66    Error,
67    Warning,
68    Success,
69}
70
71impl TintColor {
72    fn button_like_style(self, cx: &mut App) -> ButtonLikeStyles {
73        match self {
74            TintColor::Accent => ButtonLikeStyles {
75                background: cx.theme().status().info_background,
76                border_color: cx.theme().status().info_border,
77                label_color: cx.theme().colors().text,
78                icon_color: cx.theme().colors().text,
79            },
80            TintColor::Error => ButtonLikeStyles {
81                background: cx.theme().status().error_background,
82                border_color: cx.theme().status().error_border,
83                label_color: cx.theme().colors().text,
84                icon_color: cx.theme().colors().text,
85            },
86            TintColor::Warning => ButtonLikeStyles {
87                background: cx.theme().status().warning_background,
88                border_color: cx.theme().status().warning_border,
89                label_color: cx.theme().colors().text,
90                icon_color: cx.theme().colors().text,
91            },
92            TintColor::Success => ButtonLikeStyles {
93                background: cx.theme().status().success_background,
94                border_color: cx.theme().status().success_border,
95                label_color: cx.theme().colors().text,
96                icon_color: cx.theme().colors().text,
97            },
98        }
99    }
100}
101
102impl From<TintColor> for Color {
103    fn from(tint: TintColor) -> Self {
104        match tint {
105            TintColor::Accent => Color::Accent,
106            TintColor::Error => Color::Error,
107            TintColor::Warning => Color::Warning,
108            TintColor::Success => Color::Success,
109        }
110    }
111}
112
113// Used to go from ButtonStyle -> Color through tint colors.
114impl From<ButtonStyle> for Color {
115    fn from(style: ButtonStyle) -> Self {
116        match style {
117            ButtonStyle::Tinted(tint) => tint.into(),
118            _ => Color::Default,
119        }
120    }
121}
122
123/// The visual appearance of a button.
124#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Default)]
125pub enum ButtonStyle {
126    /// A filled button with a solid background color. Provides emphasis versus
127    /// the more common subtle button.
128    Filled,
129
130    /// Used to emphasize a button in some way, like a selected state, or a semantic
131    /// coloring like an error or success button.
132    Tinted(TintColor),
133
134    /// Usually used as a secondary action that should have more emphasis than
135    /// a fully transparent button.
136    Outlined,
137
138    /// A more de-emphasized version of the outlined button.
139    OutlinedGhost,
140
141    /// Like [`ButtonStyle::Outlined`], but with a caller-provided border color.
142    OutlinedCustom(Hsla),
143
144    /// The default button style, used for most buttons. Has a transparent background,
145    /// but has a background color to indicate states like hover and active.
146    #[default]
147    Subtle,
148
149    /// Used for buttons that only change foreground color on hover and active states.
150    ///
151    /// TODO: Better docs for this.
152    Transparent,
153}
154
155/// Rounding for a button that may have straight edges.
156#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
157pub(crate) struct ButtonLikeRounding {
158    /// Top-left corner rounding
159    pub top_left: bool,
160    /// Top-right corner rounding
161    pub top_right: bool,
162    /// Bottom-right corner rounding
163    pub bottom_right: bool,
164    /// Bottom-left corner rounding
165    pub bottom_left: bool,
166}
167
168impl ButtonLikeRounding {
169    pub const ALL: Self = Self {
170        top_left: true,
171        top_right: true,
172        bottom_right: true,
173        bottom_left: true,
174    };
175    pub const LEFT: Self = Self {
176        top_left: true,
177        top_right: false,
178        bottom_right: false,
179        bottom_left: true,
180    };
181    pub const RIGHT: Self = Self {
182        top_left: false,
183        top_right: true,
184        bottom_right: true,
185        bottom_left: false,
186    };
187}
188
189#[derive(Debug, Clone)]
190pub(crate) struct ButtonLikeStyles {
191    pub background: Hsla,
192    #[allow(unused)]
193    pub border_color: Hsla,
194    #[allow(unused)]
195    pub label_color: Hsla,
196    #[allow(unused)]
197    pub icon_color: Hsla,
198}
199
200fn element_bg_from_elevation(elevation: Option<ElevationIndex>, cx: &mut App) -> Hsla {
201    match elevation {
202        Some(ElevationIndex::Background) => cx.theme().colors().element_background,
203        Some(ElevationIndex::ElevatedSurface) => cx.theme().colors().elevated_surface_background,
204        Some(ElevationIndex::Surface) => cx.theme().colors().surface_background,
205        Some(ElevationIndex::ModalSurface) => cx.theme().colors().background,
206        _ => cx.theme().colors().element_background,
207    }
208}
209
210impl ButtonStyle {
211    pub(crate) fn enabled(
212        self,
213        elevation: Option<ElevationIndex>,
214        cx: &mut App,
215    ) -> ButtonLikeStyles {
216        match self {
217            ButtonStyle::Filled => ButtonLikeStyles {
218                background: element_bg_from_elevation(elevation, cx),
219                border_color: transparent_black(),
220                label_color: Color::Default.color(cx),
221                icon_color: Color::Default.color(cx),
222            },
223            ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
224            ButtonStyle::Outlined => ButtonLikeStyles {
225                background: element_bg_from_elevation(elevation, cx),
226                border_color: cx.theme().colors().border_variant,
227                label_color: Color::Default.color(cx),
228                icon_color: Color::Default.color(cx),
229            },
230            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
231                background: transparent_black(),
232                border_color: cx.theme().colors().border_variant,
233                label_color: Color::Default.color(cx),
234                icon_color: Color::Default.color(cx),
235            },
236            ButtonStyle::OutlinedCustom(border_color) => ButtonLikeStyles {
237                background: transparent_black(),
238                border_color,
239                label_color: Color::Default.color(cx),
240                icon_color: Color::Default.color(cx),
241            },
242            ButtonStyle::Subtle => ButtonLikeStyles {
243                background: cx.theme().colors().ghost_element_background,
244                border_color: transparent_black(),
245                label_color: Color::Default.color(cx),
246                icon_color: Color::Default.color(cx),
247            },
248            ButtonStyle::Transparent => ButtonLikeStyles {
249                background: transparent_black(),
250                border_color: transparent_black(),
251                label_color: Color::Default.color(cx),
252                icon_color: Color::Default.color(cx),
253            },
254        }
255    }
256
257    pub(crate) fn hovered(
258        self,
259        elevation: Option<ElevationIndex>,
260        cx: &mut App,
261    ) -> ButtonLikeStyles {
262        match self {
263            ButtonStyle::Filled => {
264                let mut filled_background = element_bg_from_elevation(elevation, cx);
265                filled_background.fade_out(0.5);
266
267                ButtonLikeStyles {
268                    background: filled_background,
269                    border_color: transparent_black(),
270                    label_color: Color::Default.color(cx),
271                    icon_color: Color::Default.color(cx),
272                }
273            }
274            ButtonStyle::Tinted(tint) => {
275                let mut styles = tint.button_like_style(cx);
276                let theme = cx.theme();
277                styles.background = theme.darken(styles.background, 0.05, 0.2);
278                styles
279            }
280            ButtonStyle::Outlined => ButtonLikeStyles {
281                background: cx.theme().colors().ghost_element_hover,
282                border_color: cx.theme().colors().border,
283                label_color: Color::Default.color(cx),
284                icon_color: Color::Default.color(cx),
285            },
286            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
287                background: cx.theme().colors().ghost_element_hover,
288                border_color: cx.theme().colors().border,
289                label_color: Color::Default.color(cx),
290                icon_color: Color::Default.color(cx),
291            },
292            ButtonStyle::OutlinedCustom(border_color) => ButtonLikeStyles {
293                background: cx.theme().colors().ghost_element_hover,
294                border_color,
295                label_color: Color::Default.color(cx),
296                icon_color: Color::Default.color(cx),
297            },
298            ButtonStyle::Subtle => ButtonLikeStyles {
299                background: cx.theme().colors().ghost_element_hover,
300                border_color: transparent_black(),
301                label_color: Color::Default.color(cx),
302                icon_color: Color::Default.color(cx),
303            },
304            ButtonStyle::Transparent => ButtonLikeStyles {
305                background: transparent_black(),
306                border_color: transparent_black(),
307                // TODO: These are not great
308                label_color: Color::Muted.color(cx),
309                // TODO: These are not great
310                icon_color: Color::Muted.color(cx),
311            },
312        }
313    }
314
315    pub(crate) fn active(self, cx: &mut App) -> ButtonLikeStyles {
316        match self {
317            ButtonStyle::Filled => ButtonLikeStyles {
318                background: cx.theme().colors().element_active,
319                border_color: transparent_black(),
320                label_color: Color::Default.color(cx),
321                icon_color: Color::Default.color(cx),
322            },
323            ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
324            ButtonStyle::Subtle => ButtonLikeStyles {
325                background: cx.theme().colors().ghost_element_active,
326                border_color: transparent_black(),
327                label_color: Color::Default.color(cx),
328                icon_color: Color::Default.color(cx),
329            },
330            ButtonStyle::Outlined => ButtonLikeStyles {
331                background: cx.theme().colors().element_active,
332                border_color: cx.theme().colors().border_variant,
333                label_color: Color::Default.color(cx),
334                icon_color: Color::Default.color(cx),
335            },
336            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
337                background: transparent_black(),
338                border_color: cx.theme().colors().border_variant,
339                label_color: Color::Default.color(cx),
340                icon_color: Color::Default.color(cx),
341            },
342            ButtonStyle::OutlinedCustom(border_color) => ButtonLikeStyles {
343                background: cx.theme().colors().element_active,
344                border_color,
345                label_color: Color::Default.color(cx),
346                icon_color: Color::Default.color(cx),
347            },
348            ButtonStyle::Transparent => ButtonLikeStyles {
349                background: transparent_black(),
350                border_color: transparent_black(),
351                // TODO: These are not great
352                label_color: Color::Muted.color(cx),
353                // TODO: These are not great
354                icon_color: Color::Muted.color(cx),
355            },
356        }
357    }
358
359    #[allow(unused)]
360    pub(crate) fn focused(self, window: &mut Window, cx: &mut App) -> ButtonLikeStyles {
361        match self {
362            ButtonStyle::Filled => ButtonLikeStyles {
363                background: cx.theme().colors().element_background,
364                border_color: cx.theme().colors().border_focused,
365                label_color: Color::Default.color(cx),
366                icon_color: Color::Default.color(cx),
367            },
368            ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
369            ButtonStyle::Subtle => ButtonLikeStyles {
370                background: cx.theme().colors().ghost_element_background,
371                border_color: cx.theme().colors().border_focused,
372                label_color: Color::Default.color(cx),
373                icon_color: Color::Default.color(cx),
374            },
375            ButtonStyle::Outlined => ButtonLikeStyles {
376                background: cx.theme().colors().ghost_element_background,
377                border_color: cx.theme().colors().border,
378                label_color: Color::Default.color(cx),
379                icon_color: Color::Default.color(cx),
380            },
381            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
382                background: transparent_black(),
383                border_color: cx.theme().colors().border,
384                label_color: Color::Default.color(cx),
385                icon_color: Color::Default.color(cx),
386            },
387            ButtonStyle::OutlinedCustom(border_color) => ButtonLikeStyles {
388                background: cx.theme().colors().ghost_element_background,
389                border_color,
390                label_color: Color::Default.color(cx),
391                icon_color: Color::Default.color(cx),
392            },
393            ButtonStyle::Transparent => ButtonLikeStyles {
394                background: transparent_black(),
395                border_color: cx.theme().colors().border_focused,
396                label_color: Color::Accent.color(cx),
397                icon_color: Color::Accent.color(cx),
398            },
399        }
400    }
401
402    #[allow(unused)]
403    pub(crate) fn disabled(
404        self,
405        elevation: Option<ElevationIndex>,
406        window: &mut Window,
407        cx: &mut App,
408    ) -> ButtonLikeStyles {
409        match self {
410            ButtonStyle::Filled => ButtonLikeStyles {
411                background: cx.theme().colors().element_disabled,
412                border_color: cx.theme().colors().border_disabled,
413                label_color: Color::Disabled.color(cx),
414                icon_color: Color::Disabled.color(cx),
415            },
416            ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
417            ButtonStyle::Subtle => ButtonLikeStyles {
418                background: cx.theme().colors().ghost_element_disabled,
419                border_color: cx.theme().colors().border_disabled,
420                label_color: Color::Disabled.color(cx),
421                icon_color: Color::Disabled.color(cx),
422            },
423            ButtonStyle::Outlined => ButtonLikeStyles {
424                background: cx.theme().colors().element_disabled,
425                border_color: cx.theme().colors().border_disabled,
426                label_color: Color::Default.color(cx),
427                icon_color: Color::Default.color(cx),
428            },
429            ButtonStyle::OutlinedGhost => ButtonLikeStyles {
430                background: transparent_black(),
431                border_color: cx.theme().colors().border_disabled,
432                label_color: Color::Default.color(cx),
433                icon_color: Color::Default.color(cx),
434            },
435            ButtonStyle::OutlinedCustom(_) => ButtonLikeStyles {
436                background: cx.theme().colors().element_disabled,
437                border_color: cx.theme().colors().border_disabled,
438                label_color: Color::Default.color(cx),
439                icon_color: Color::Default.color(cx),
440            },
441            ButtonStyle::Transparent => ButtonLikeStyles {
442                background: transparent_black(),
443                border_color: transparent_black(),
444                label_color: Color::Disabled.color(cx),
445                icon_color: Color::Disabled.color(cx),
446            },
447        }
448    }
449}
450
451/// The height of a button.
452///
453/// Can also be used to size non-button elements to align with [`Button`]s.
454#[derive(Default, PartialEq, Clone, Copy)]
455pub enum ButtonSize {
456    Large,
457    Medium,
458    #[default]
459    Default,
460    Compact,
461    None,
462}
463
464impl ButtonSize {
465    pub fn rems(self) -> Rems {
466        match self {
467            ButtonSize::Large => rems_from_px(32.),
468            ButtonSize::Medium => rems_from_px(28.),
469            ButtonSize::Default => rems_from_px(22.),
470            ButtonSize::Compact => rems_from_px(18.),
471            ButtonSize::None => rems_from_px(16.),
472        }
473    }
474}
475
476/// A button-like element that can be used to create a custom button when
477/// prebuilt buttons are not sufficient. Use this sparingly, as it is
478/// unconstrained and may make the UI feel less consistent.
479///
480/// This is also used to build the prebuilt buttons.
481#[derive(IntoElement, Documented, RegisterComponent)]
482pub struct ButtonLike {
483    pub(super) base: Div,
484    id: ElementId,
485    pub(super) style: ButtonStyle,
486    pub(super) disabled: bool,
487    pub(super) selected: bool,
488    pub(super) selected_style: Option<ButtonStyle>,
489    pub(super) width: Option<DefiniteLength>,
490    pub(super) height: Option<DefiniteLength>,
491    pub(super) layer: Option<ElevationIndex>,
492    tab_index: Option<isize>,
493    size: ButtonSize,
494    rounding: Option<ButtonLikeRounding>,
495    pub(super) aria_label: Option<SharedString>,
496    aria_description: Option<SharedString>,
497    pub(super) aria_value: Option<SharedString>,
498    pub(super) aria_keyshortcuts: Option<SharedString>,
499    pub(super) aria_role: Option<Role>,
500    aria_expanded: Option<bool>,
501    toggled: Option<bool>,
502    tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
503    hoverable_tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
504    cursor_style: CursorStyle,
505    on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
506    on_right_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
507    a11y_actions: Vec<(
508        gpui::accesskit::Action,
509        Box<dyn FnMut(Option<&gpui::accesskit::ActionData>, &mut Window, &mut App) + 'static>,
510    )>,
511    children: SmallVec<[AnyElement; 2]>,
512    focus_handle: Option<FocusHandle>,
513}
514
515impl ButtonLike {
516    pub fn new(id: impl Into<ElementId>) -> Self {
517        Self {
518            base: div(),
519            id: id.into(),
520            style: ButtonStyle::default(),
521            disabled: false,
522            selected: false,
523            selected_style: None,
524            width: None,
525            height: None,
526            size: ButtonSize::Default,
527            rounding: Some(ButtonLikeRounding::ALL),
528            aria_label: None,
529            aria_description: None,
530            aria_value: None,
531            aria_keyshortcuts: None,
532            aria_role: None,
533            aria_expanded: None,
534            toggled: None,
535            tooltip: None,
536            hoverable_tooltip: None,
537            children: SmallVec::new(),
538            cursor_style: CursorStyle::PointingHand,
539            on_click: None,
540            on_right_click: None,
541            a11y_actions: Vec::new(),
542            layer: None,
543            tab_index: None,
544            focus_handle: None,
545        }
546    }
547
548    pub fn new_rounded_left(id: impl Into<ElementId>) -> Self {
549        Self::new(id).rounding(ButtonLikeRounding::LEFT)
550    }
551
552    pub fn new_rounded_right(id: impl Into<ElementId>) -> Self {
553        Self::new(id).rounding(ButtonLikeRounding::RIGHT)
554    }
555
556    pub fn new_rounded_all(id: impl Into<ElementId>) -> Self {
557        Self::new(id).rounding(ButtonLikeRounding::ALL)
558    }
559
560    pub fn opacity(mut self, opacity: f32) -> Self {
561        self.base = self.base.opacity(opacity);
562        self
563    }
564
565    pub fn height(mut self, height: DefiniteLength) -> Self {
566        self.height = Some(height);
567        self
568    }
569
570    pub(crate) fn rounding(mut self, rounding: impl Into<Option<ButtonLikeRounding>>) -> Self {
571        self.rounding = rounding.into();
572        self
573    }
574
575    pub fn on_right_click(
576        mut self,
577        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
578    ) -> Self {
579        self.on_right_click = Some(Box::new(handler));
580        self
581    }
582
583    pub fn hoverable_tooltip(
584        mut self,
585        tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
586    ) -> Self {
587        self.hoverable_tooltip = Some(Box::new(tooltip));
588        self
589    }
590
591    /// Sets the label announced by assistive technology for this button.
592    pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
593        self.aria_label = Some(label.into());
594        self
595    }
596
597    /// Sets the keyboard shortcut announced by assistive technology for this
598    /// button. Use a human-friendly display string (the accelerator shown to
599    /// sighted users), e.g. `"Ctrl-S"` - see
600    /// [`KeyBinding::keyboard_shortcut_text`](crate::KeyBinding::keyboard_shortcut_text).
601    /// [`Button`](crate::Button) sets this automatically from its displayed
602    /// keybinding.
603    pub fn aria_keyshortcuts(mut self, keyshortcuts: impl Into<SharedString>) -> Self {
604        self.aria_keyshortcuts = Some(keyshortcuts.into());
605        self
606    }
607
608    /// Sets the supplementary description announced by assistive technology
609    /// after the button's name, role, and value.
610    pub fn aria_description(mut self, description: impl Into<SharedString>) -> Self {
611        self.aria_description = Some(description.into());
612        self
613    }
614
615    /// Sets the current value reported to assistive technology. Use this when
616    /// the button represents a control with a value, such as a combobox
617    /// trigger whose value is the current selection.
618    pub fn aria_value(mut self, value: impl Into<SharedString>) -> Self {
619        self.aria_value = Some(value.into());
620        self
621    }
622
623    /// Overrides the role reported to assistive technology.
624    /// Defaults to [`Role::Button`].
625    pub fn aria_role(mut self, role: Role) -> Self {
626        self.aria_role = Some(role);
627        self
628    }
629
630    /// Sets the expanded state reported to assistive technology, for buttons
631    /// that control a popup (e.g. dropdown or disclosure triggers).
632    pub fn aria_expanded(mut self, expanded: bool) -> Self {
633        self.aria_expanded = Some(expanded);
634        self
635    }
636
637    /// Registers a handler for an accessibility action (e.g.
638    /// [`accesskit::Action::Expand`]) dispatched by assistive technology to
639    /// this button. Also advertises the action to assistive technology.
640    pub fn on_a11y_action(
641        mut self,
642        action: gpui::accesskit::Action,
643        listener: impl FnMut(Option<&gpui::accesskit::ActionData>, &mut Window, &mut App) + 'static,
644    ) -> Self {
645        self.a11y_actions.push((action, Box::new(listener)));
646        self
647    }
648}
649
650impl Disableable for ButtonLike {
651    fn disabled(mut self, disabled: bool) -> Self {
652        self.disabled = disabled;
653        self
654    }
655}
656
657impl Toggleable for ButtonLike {
658    fn toggle_state(mut self, selected: bool) -> Self {
659        self.selected = selected;
660        self.toggled = Some(selected);
661        self
662    }
663}
664
665impl SelectableButton for ButtonLike {
666    fn selected_style(mut self, style: ButtonStyle) -> Self {
667        self.selected_style = Some(style);
668        self
669    }
670}
671
672impl Clickable for ButtonLike {
673    fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
674        self.on_click = Some(Box::new(handler));
675        self
676    }
677
678    fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
679        self.cursor_style = cursor_style;
680        self
681    }
682}
683
684impl FixedWidth for ButtonLike {
685    fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
686        self.width = Some(width.into());
687        self
688    }
689
690    fn full_width(mut self) -> Self {
691        self.width = Some(relative(1.));
692        self
693    }
694}
695
696impl ButtonCommon for ButtonLike {
697    fn id(&self) -> &ElementId {
698        &self.id
699    }
700
701    fn style(mut self, style: ButtonStyle) -> Self {
702        self.style = style;
703        self
704    }
705
706    fn size(mut self, size: ButtonSize) -> Self {
707        self.size = size;
708        self
709    }
710
711    fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
712        self.tooltip = Some(Box::new(tooltip));
713        self
714    }
715
716    fn tab_index(mut self, tab_index: impl Into<isize>) -> Self {
717        self.tab_index = Some(tab_index.into());
718        self
719    }
720
721    fn layer(mut self, elevation: ElevationIndex) -> Self {
722        self.layer = Some(elevation);
723        self
724    }
725
726    fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
727        self.focus_handle = Some(focus_handle.clone());
728        self
729    }
730}
731
732impl VisibleOnHover for ButtonLike {
733    fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
734        self.base = self.base.visible_on_hover(group_name);
735        self
736    }
737}
738
739impl ParentElement for ButtonLike {
740    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
741        self.children.extend(elements)
742    }
743}
744
745impl RenderOnce for ButtonLike {
746    fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
747        let style = self
748            .selected_style
749            .filter(|_| self.selected)
750            .unwrap_or(self.style);
751
752        let is_outlined = matches!(
753            self.style,
754            ButtonStyle::Outlined | ButtonStyle::OutlinedGhost | ButtonStyle::OutlinedCustom(_)
755        );
756
757        self.base
758            .h_flex()
759            .id(self.id.clone())
760            .role(self.aria_role.unwrap_or(Role::Button))
761            .when_some(self.aria_label, |this, label| this.aria_label(label))
762            .when_some(self.aria_keyshortcuts, |this, keyshortcuts| {
763                this.aria_keyshortcuts(keyshortcuts)
764            })
765            .when_some(self.aria_description, |this, description| {
766                this.aria_description(description)
767            })
768            .when_some(self.aria_value, |this, value| this.aria_value(value))
769            .when_some(self.aria_expanded, |this, expanded| {
770                this.aria_expanded(expanded)
771            })
772            .when_some(self.toggled, |this, toggled| {
773                this.aria_toggled(if toggled {
774                    Toggled::True
775                } else {
776                    Toggled::False
777                })
778            })
779            .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
780            .when_some(self.focus_handle, |this, focus_handle| {
781                this.track_focus(&focus_handle)
782            })
783            .font_ui(cx)
784            .group("")
785            .flex_none()
786            .h(self.height.unwrap_or(self.size.rems().into()))
787            .when_some(self.width, |this, width| {
788                this.w(width).justify_center().text_center()
789            })
790            .when(is_outlined, |this| this.border_1())
791            .when_some(self.rounding, |this, rounding| {
792                this.when(rounding.top_left, |this| this.rounded_tl_sm())
793                    .when(rounding.top_right, |this| this.rounded_tr_sm())
794                    .when(rounding.bottom_right, |this| this.rounded_br_sm())
795                    .when(rounding.bottom_left, |this| this.rounded_bl_sm())
796            })
797            .gap(DynamicSpacing::Base04.rems(cx))
798            .map(|this| match self.size {
799                ButtonSize::Large | ButtonSize::Medium => this.px(DynamicSpacing::Base08.rems(cx)),
800                ButtonSize::Default | ButtonSize::Compact => {
801                    this.px(DynamicSpacing::Base04.rems(cx))
802                }
803                ButtonSize::None => this.px_px(),
804            })
805            .border_color(style.enabled(self.layer, cx).border_color)
806            .bg(style.enabled(self.layer, cx).background)
807            .when(self.disabled, |this| {
808                if self.cursor_style == CursorStyle::PointingHand {
809                    this.cursor_not_allowed()
810                } else {
811                    this.cursor(self.cursor_style)
812                }
813            })
814            .when(!self.disabled, |this| {
815                let hovered_style = style.hovered(self.layer, cx);
816                let focus_color =
817                    |refinement: StyleRefinement| refinement.bg(hovered_style.background);
818
819                this.cursor(self.cursor_style)
820                    .hover(focus_color)
821                    .map(|this| {
822                        if is_outlined {
823                            this.focus_visible(|s| {
824                                s.border_color(cx.theme().colors().border_focused)
825                            })
826                        } else {
827                            this.focus_visible(focus_color)
828                        }
829                    })
830                    .active(|active| active.bg(style.active(cx).background))
831            })
832            .when_some(
833                self.on_right_click.filter(|_| !self.disabled),
834                |this, on_right_click| {
835                    this.on_mouse_down(MouseButton::Right, |_event, window, cx| {
836                        window.prevent_default();
837                        cx.stop_propagation();
838                    })
839                    .on_mouse_up(
840                        MouseButton::Right,
841                        move |event, window, cx| {
842                            cx.stop_propagation();
843                            let click_event = ClickEvent::Mouse(MouseClickEvent {
844                                down: MouseDownEvent {
845                                    button: MouseButton::Right,
846                                    position: event.position,
847                                    modifiers: event.modifiers,
848                                    click_count: 1,
849                                    first_mouse: false,
850                                },
851                                up: MouseUpEvent {
852                                    button: MouseButton::Right,
853                                    position: event.position,
854                                    modifiers: event.modifiers,
855                                    click_count: 1,
856                                },
857                            });
858                            (on_right_click)(&click_event, window, cx)
859                        },
860                    )
861                },
862            )
863            .when_some(
864                self.on_click.filter(|_| !self.disabled),
865                |this, on_click| {
866                    this.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
867                        .on_click(move |event, window, cx| {
868                            cx.stop_propagation();
869                            (on_click)(event, window, cx)
870                        })
871                },
872            )
873            .when_some(self.tooltip, |this, tooltip| {
874                this.tooltip(move |window, cx| tooltip(window, cx))
875            })
876            .when_some(self.hoverable_tooltip, |this, tooltip| {
877                this.hoverable_tooltip(move |window, cx| tooltip(window, cx))
878            })
879            .map(|mut this| {
880                for (action, listener) in self.a11y_actions {
881                    this = this.on_a11y_action(action, listener);
882                }
883                this
884            })
885            .children(self.children)
886    }
887}
888
889impl Component for ButtonLike {
890    fn scope() -> ComponentScope {
891        ComponentScope::Input
892    }
893
894    fn sort_name() -> &'static str {
895        // ButtonLike should be at the bottom of the button list
896        "ButtonZ"
897    }
898
899    fn description() -> &'static str {
900        ButtonLike::DOCS
901    }
902
903    fn preview(_window: &mut Window, _cx: &mut App) -> AnyElement {
904        v_flex()
905            .gap_6()
906            .children(vec![
907                example_group(vec![
908                    single_example(
909                        "Default",
910                        ButtonLike::new("default")
911                            .child(Label::new("Default"))
912                            .into_any_element(),
913                    ),
914                    single_example(
915                        "Filled",
916                        ButtonLike::new("filled")
917                            .style(ButtonStyle::Filled)
918                            .child(Label::new("Filled"))
919                            .into_any_element(),
920                    ),
921                    single_example(
922                        "Subtle",
923                        ButtonLike::new("outline")
924                            .style(ButtonStyle::Subtle)
925                            .child(Label::new("Subtle"))
926                            .into_any_element(),
927                    ),
928                    single_example(
929                        "Tinted",
930                        ButtonLike::new("tinted_accent_style")
931                            .style(ButtonStyle::Tinted(TintColor::Accent))
932                            .child(Label::new("Accent"))
933                            .into_any_element(),
934                    ),
935                    single_example(
936                        "Transparent",
937                        ButtonLike::new("transparent")
938                            .style(ButtonStyle::Transparent)
939                            .child(Label::new("Transparent"))
940                            .into_any_element(),
941                    ),
942                ]),
943                example_group_with_title(
944                    "Button Group Constructors",
945                    vec![
946                        single_example(
947                            "Left Rounded",
948                            ButtonLike::new_rounded_left("left_rounded")
949                                .child(Label::new("Left Rounded"))
950                                .style(ButtonStyle::Filled)
951                                .into_any_element(),
952                        ),
953                        single_example(
954                            "Right Rounded",
955                            ButtonLike::new_rounded_right("right_rounded")
956                                .child(Label::new("Right Rounded"))
957                                .style(ButtonStyle::Filled)
958                                .into_any_element(),
959                        ),
960                        single_example(
961                            "Button Group",
962                            h_flex()
963                                .gap_px()
964                                .child(
965                                    ButtonLike::new_rounded_left("bg_left")
966                                        .child(Label::new("Left"))
967                                        .style(ButtonStyle::Filled),
968                                )
969                                .child(
970                                    ButtonLike::new_rounded_right("bg_right")
971                                        .child(Label::new("Right"))
972                                        .style(ButtonStyle::Filled),
973                                )
974                                .into_any_element(),
975                        ),
976                    ],
977                ),
978            ])
979            .into_any_element()
980    }
981}
982
Served at tenant.openagents/omega Member data and write actions are omitted.