1use documented::Documented;
2use gpui::{
3 AnyElement, AnyView, ClickEvent, CursorStyle, DefiniteLength, FocusHandle, Hsla, MouseButton,
4 MouseClickEvent, MouseDownEvent, MouseUpEvent, Rems, Role, StyleRefinement, 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 /// The default button style, used for most buttons. Has a transparent background,
142 /// but has a background color to indicate states like hover and active.
143 #[default]
144 Subtle,
145
146 /// Used for buttons that only change foreground color on hover and active states.
147 ///
148 /// TODO: Better docs for this.
149 Transparent,
150}
151
152/// Rounding for a button that may have straight edges.
153#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
154pub(crate) struct ButtonLikeRounding {
155 /// Top-left corner rounding
156 pub top_left: bool,
157 /// Top-right corner rounding
158 pub top_right: bool,
159 /// Bottom-right corner rounding
160 pub bottom_right: bool,
161 /// Bottom-left corner rounding
162 pub bottom_left: bool,
163}
164
165impl ButtonLikeRounding {
166 pub const ALL: Self = Self {
167 top_left: true,
168 top_right: true,
169 bottom_right: true,
170 bottom_left: true,
171 };
172 pub const LEFT: Self = Self {
173 top_left: true,
174 top_right: false,
175 bottom_right: false,
176 bottom_left: true,
177 };
178 pub const RIGHT: Self = Self {
179 top_left: false,
180 top_right: true,
181 bottom_right: true,
182 bottom_left: false,
183 };
184}
185
186#[derive(Debug, Clone)]
187pub(crate) struct ButtonLikeStyles {
188 pub background: Hsla,
189 #[allow(unused)]
190 pub border_color: Hsla,
191 #[allow(unused)]
192 pub label_color: Hsla,
193 #[allow(unused)]
194 pub icon_color: Hsla,
195}
196
197fn element_bg_from_elevation(elevation: Option<ElevationIndex>, cx: &mut App) -> Hsla {
198 match elevation {
199 Some(ElevationIndex::Background) => cx.theme().colors().element_background,
200 Some(ElevationIndex::ElevatedSurface) => cx.theme().colors().elevated_surface_background,
201 Some(ElevationIndex::Surface) => cx.theme().colors().surface_background,
202 Some(ElevationIndex::ModalSurface) => cx.theme().colors().background,
203 _ => cx.theme().colors().element_background,
204 }
205}
206
207impl ButtonStyle {
208 pub(crate) fn enabled(
209 self,
210 elevation: Option<ElevationIndex>,
211 cx: &mut App,
212 ) -> ButtonLikeStyles {
213 match self {
214 ButtonStyle::Filled => ButtonLikeStyles {
215 background: element_bg_from_elevation(elevation, cx),
216 border_color: transparent_black(),
217 label_color: Color::Default.color(cx),
218 icon_color: Color::Default.color(cx),
219 },
220 ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
221 ButtonStyle::Outlined => ButtonLikeStyles {
222 background: element_bg_from_elevation(elevation, cx),
223 border_color: cx.theme().colors().border_variant,
224 label_color: Color::Default.color(cx),
225 icon_color: Color::Default.color(cx),
226 },
227 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
228 background: transparent_black(),
229 border_color: cx.theme().colors().border_variant,
230 label_color: Color::Default.color(cx),
231 icon_color: Color::Default.color(cx),
232 },
233 ButtonStyle::Subtle => ButtonLikeStyles {
234 background: cx.theme().colors().ghost_element_background,
235 border_color: transparent_black(),
236 label_color: Color::Default.color(cx),
237 icon_color: Color::Default.color(cx),
238 },
239 ButtonStyle::Transparent => ButtonLikeStyles {
240 background: transparent_black(),
241 border_color: transparent_black(),
242 label_color: Color::Default.color(cx),
243 icon_color: Color::Default.color(cx),
244 },
245 }
246 }
247
248 pub(crate) fn hovered(
249 self,
250 elevation: Option<ElevationIndex>,
251 cx: &mut App,
252 ) -> ButtonLikeStyles {
253 match self {
254 ButtonStyle::Filled => {
255 let mut filled_background = element_bg_from_elevation(elevation, cx);
256 filled_background.fade_out(0.5);
257
258 ButtonLikeStyles {
259 background: filled_background,
260 border_color: transparent_black(),
261 label_color: Color::Default.color(cx),
262 icon_color: Color::Default.color(cx),
263 }
264 }
265 ButtonStyle::Tinted(tint) => {
266 let mut styles = tint.button_like_style(cx);
267 let theme = cx.theme();
268 styles.background = theme.darken(styles.background, 0.05, 0.2);
269 styles
270 }
271 ButtonStyle::Outlined => ButtonLikeStyles {
272 background: cx.theme().colors().ghost_element_hover,
273 border_color: cx.theme().colors().border,
274 label_color: Color::Default.color(cx),
275 icon_color: Color::Default.color(cx),
276 },
277 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
278 background: cx.theme().colors().ghost_element_hover,
279 border_color: cx.theme().colors().border,
280 label_color: Color::Default.color(cx),
281 icon_color: Color::Default.color(cx),
282 },
283 ButtonStyle::Subtle => ButtonLikeStyles {
284 background: cx.theme().colors().ghost_element_hover,
285 border_color: transparent_black(),
286 label_color: Color::Default.color(cx),
287 icon_color: Color::Default.color(cx),
288 },
289 ButtonStyle::Transparent => ButtonLikeStyles {
290 background: transparent_black(),
291 border_color: transparent_black(),
292 // TODO: These are not great
293 label_color: Color::Muted.color(cx),
294 // TODO: These are not great
295 icon_color: Color::Muted.color(cx),
296 },
297 }
298 }
299
300 pub(crate) fn active(self, cx: &mut App) -> ButtonLikeStyles {
301 match self {
302 ButtonStyle::Filled => ButtonLikeStyles {
303 background: cx.theme().colors().element_active,
304 border_color: transparent_black(),
305 label_color: Color::Default.color(cx),
306 icon_color: Color::Default.color(cx),
307 },
308 ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
309 ButtonStyle::Subtle => ButtonLikeStyles {
310 background: cx.theme().colors().ghost_element_active,
311 border_color: transparent_black(),
312 label_color: Color::Default.color(cx),
313 icon_color: Color::Default.color(cx),
314 },
315 ButtonStyle::Outlined => ButtonLikeStyles {
316 background: cx.theme().colors().element_active,
317 border_color: cx.theme().colors().border_variant,
318 label_color: Color::Default.color(cx),
319 icon_color: Color::Default.color(cx),
320 },
321 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
322 background: transparent_black(),
323 border_color: cx.theme().colors().border_variant,
324 label_color: Color::Default.color(cx),
325 icon_color: Color::Default.color(cx),
326 },
327 ButtonStyle::Transparent => ButtonLikeStyles {
328 background: transparent_black(),
329 border_color: transparent_black(),
330 // TODO: These are not great
331 label_color: Color::Muted.color(cx),
332 // TODO: These are not great
333 icon_color: Color::Muted.color(cx),
334 },
335 }
336 }
337
338 #[allow(unused)]
339 pub(crate) fn focused(self, window: &mut Window, cx: &mut App) -> ButtonLikeStyles {
340 match self {
341 ButtonStyle::Filled => ButtonLikeStyles {
342 background: cx.theme().colors().element_background,
343 border_color: cx.theme().colors().border_focused,
344 label_color: Color::Default.color(cx),
345 icon_color: Color::Default.color(cx),
346 },
347 ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
348 ButtonStyle::Subtle => ButtonLikeStyles {
349 background: cx.theme().colors().ghost_element_background,
350 border_color: cx.theme().colors().border_focused,
351 label_color: Color::Default.color(cx),
352 icon_color: Color::Default.color(cx),
353 },
354 ButtonStyle::Outlined => ButtonLikeStyles {
355 background: cx.theme().colors().ghost_element_background,
356 border_color: cx.theme().colors().border,
357 label_color: Color::Default.color(cx),
358 icon_color: Color::Default.color(cx),
359 },
360 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
361 background: transparent_black(),
362 border_color: cx.theme().colors().border,
363 label_color: Color::Default.color(cx),
364 icon_color: Color::Default.color(cx),
365 },
366 ButtonStyle::Transparent => ButtonLikeStyles {
367 background: transparent_black(),
368 border_color: cx.theme().colors().border_focused,
369 label_color: Color::Accent.color(cx),
370 icon_color: Color::Accent.color(cx),
371 },
372 }
373 }
374
375 #[allow(unused)]
376 pub(crate) fn disabled(
377 self,
378 elevation: Option<ElevationIndex>,
379 window: &mut Window,
380 cx: &mut App,
381 ) -> ButtonLikeStyles {
382 match self {
383 ButtonStyle::Filled => ButtonLikeStyles {
384 background: cx.theme().colors().element_disabled,
385 border_color: cx.theme().colors().border_disabled,
386 label_color: Color::Disabled.color(cx),
387 icon_color: Color::Disabled.color(cx),
388 },
389 ButtonStyle::Tinted(tint) => tint.button_like_style(cx),
390 ButtonStyle::Subtle => ButtonLikeStyles {
391 background: cx.theme().colors().ghost_element_disabled,
392 border_color: cx.theme().colors().border_disabled,
393 label_color: Color::Disabled.color(cx),
394 icon_color: Color::Disabled.color(cx),
395 },
396 ButtonStyle::Outlined => ButtonLikeStyles {
397 background: cx.theme().colors().element_disabled,
398 border_color: cx.theme().colors().border_disabled,
399 label_color: Color::Default.color(cx),
400 icon_color: Color::Default.color(cx),
401 },
402 ButtonStyle::OutlinedGhost => ButtonLikeStyles {
403 background: transparent_black(),
404 border_color: cx.theme().colors().border_disabled,
405 label_color: Color::Default.color(cx),
406 icon_color: Color::Default.color(cx),
407 },
408 ButtonStyle::Transparent => ButtonLikeStyles {
409 background: transparent_black(),
410 border_color: transparent_black(),
411 label_color: Color::Disabled.color(cx),
412 icon_color: Color::Disabled.color(cx),
413 },
414 }
415 }
416}
417
418/// The height of a button.
419///
420/// Can also be used to size non-button elements to align with [`Button`]s.
421#[derive(Default, PartialEq, Clone, Copy)]
422pub enum ButtonSize {
423 Large,
424 Medium,
425 #[default]
426 Default,
427 Compact,
428 None,
429}
430
431impl ButtonSize {
432 pub fn rems(self) -> Rems {
433 match self {
434 ButtonSize::Large => rems_from_px(32.),
435 ButtonSize::Medium => rems_from_px(28.),
436 ButtonSize::Default => rems_from_px(22.),
437 ButtonSize::Compact => rems_from_px(18.),
438 ButtonSize::None => rems_from_px(16.),
439 }
440 }
441}
442
443/// A button-like element that can be used to create a custom button when
444/// prebuilt buttons are not sufficient. Use this sparingly, as it is
445/// unconstrained and may make the UI feel less consistent.
446///
447/// This is also used to build the prebuilt buttons.
448#[derive(IntoElement, Documented, RegisterComponent)]
449pub struct ButtonLike {
450 pub(super) base: Div,
451 id: ElementId,
452 pub(super) style: ButtonStyle,
453 pub(super) disabled: bool,
454 pub(super) selected: bool,
455 pub(super) selected_style: Option<ButtonStyle>,
456 pub(super) width: Option<DefiniteLength>,
457 pub(super) height: Option<DefiniteLength>,
458 pub(super) layer: Option<ElevationIndex>,
459 tab_index: Option<isize>,
460 size: ButtonSize,
461 rounding: Option<ButtonLikeRounding>,
462 tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
463 hoverable_tooltip: Option<Box<dyn Fn(&mut Window, &mut App) -> AnyView>>,
464 cursor_style: CursorStyle,
465 on_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
466 on_right_click: Option<Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
467 children: SmallVec<[AnyElement; 2]>,
468 focus_handle: Option<FocusHandle>,
469 override_role: Option<Role>,
470 aria_label: Option<SharedString>,
471 aria_expanded: Option<bool>,
472}
473
474impl ButtonLike {
475 pub fn new(id: impl Into<ElementId>) -> Self {
476 Self {
477 base: div(),
478 id: id.into(),
479 style: ButtonStyle::default(),
480 disabled: false,
481 selected: false,
482 selected_style: None,
483 width: None,
484 height: None,
485 size: ButtonSize::Default,
486 rounding: Some(ButtonLikeRounding::ALL),
487 tooltip: None,
488 hoverable_tooltip: None,
489 children: SmallVec::new(),
490 cursor_style: CursorStyle::PointingHand,
491 on_click: None,
492 on_right_click: None,
493 layer: None,
494 tab_index: None,
495 focus_handle: None,
496 override_role: None,
497 aria_label: None,
498 aria_expanded: None,
499 }
500 }
501
502 pub fn new_rounded_left(id: impl Into<ElementId>) -> Self {
503 Self::new(id).rounding(ButtonLikeRounding::LEFT)
504 }
505
506 pub fn new_rounded_right(id: impl Into<ElementId>) -> Self {
507 Self::new(id).rounding(ButtonLikeRounding::RIGHT)
508 }
509
510 pub fn new_rounded_all(id: impl Into<ElementId>) -> Self {
511 Self::new(id).rounding(ButtonLikeRounding::ALL)
512 }
513
514 /// Override the default accessible role for this element.
515 ///
516 /// Defaults to [`Role::Button`].
517 ///
518 /// Panics if called on a node without an id. See
519 /// [`InteractiveElement::role`] for more information.
520 pub fn role(mut self, role: Role) -> Self {
521 self.override_role = Some(role);
522 self
523 }
524
525 /// Set the accessible label for this element.
526 ///
527 /// See [`InteractiveElement::aria_label`]
528 pub fn aria_label(mut self, label: impl Into<SharedString>) -> Self {
529 self.aria_label = Some(label.into());
530 self
531 }
532
533 /// Set the accessible expanded state for this element.
534 ///
535 /// See [`InteractiveElement::aria_expanded`]
536 pub fn aria_expanded(mut self, expanded: bool) -> Self {
537 self.aria_expanded = Some(expanded);
538 self
539 }
540
541 pub fn opacity(mut self, opacity: f32) -> Self {
542 self.base = self.base.opacity(opacity);
543 self
544 }
545
546 pub fn height(mut self, height: DefiniteLength) -> Self {
547 self.height = Some(height);
548 self
549 }
550
551 pub(crate) fn rounding(mut self, rounding: impl Into<Option<ButtonLikeRounding>>) -> Self {
552 self.rounding = rounding.into();
553 self
554 }
555
556 pub fn on_right_click(
557 mut self,
558 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
559 ) -> Self {
560 self.on_right_click = Some(Box::new(handler));
561 self
562 }
563
564 pub fn hoverable_tooltip(
565 mut self,
566 tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
567 ) -> Self {
568 self.hoverable_tooltip = Some(Box::new(tooltip));
569 self
570 }
571}
572
573impl Disableable for ButtonLike {
574 fn disabled(mut self, disabled: bool) -> Self {
575 self.disabled = disabled;
576 self
577 }
578}
579
580impl Toggleable for ButtonLike {
581 fn toggle_state(mut self, selected: bool) -> Self {
582 self.selected = selected;
583 self
584 }
585}
586
587impl SelectableButton for ButtonLike {
588 fn selected_style(mut self, style: ButtonStyle) -> Self {
589 self.selected_style = Some(style);
590 self
591 }
592}
593
594impl Clickable for ButtonLike {
595 fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
596 self.on_click = Some(Box::new(handler));
597 self
598 }
599
600 fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
601 self.cursor_style = cursor_style;
602 self
603 }
604}
605
606impl FixedWidth for ButtonLike {
607 fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
608 self.width = Some(width.into());
609 self
610 }
611
612 fn full_width(mut self) -> Self {
613 self.width = Some(relative(1.));
614 self
615 }
616}
617
618impl ButtonCommon for ButtonLike {
619 fn id(&self) -> &ElementId {
620 &self.id
621 }
622
623 fn style(mut self, style: ButtonStyle) -> Self {
624 self.style = style;
625 self
626 }
627
628 fn size(mut self, size: ButtonSize) -> Self {
629 self.size = size;
630 self
631 }
632
633 fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
634 self.tooltip = Some(Box::new(tooltip));
635 self
636 }
637
638 fn tab_index(mut self, tab_index: impl Into<isize>) -> Self {
639 self.tab_index = Some(tab_index.into());
640 self
641 }
642
643 fn layer(mut self, elevation: ElevationIndex) -> Self {
644 self.layer = Some(elevation);
645 self
646 }
647
648 fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
649 self.focus_handle = Some(focus_handle.clone());
650 self
651 }
652}
653
654impl VisibleOnHover for ButtonLike {
655 fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
656 self.base = self.base.visible_on_hover(group_name);
657 self
658 }
659}
660
661impl ParentElement for ButtonLike {
662 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
663 self.children.extend(elements)
664 }
665}
666
667impl RenderOnce for ButtonLike {
668 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
669 let style = self
670 .selected_style
671 .filter(|_| self.selected)
672 .unwrap_or(self.style);
673
674 let is_outlined = matches!(
675 self.style,
676 ButtonStyle::Outlined | ButtonStyle::OutlinedGhost
677 );
678
679 self.base
680 .h_flex()
681 .id(self.id.clone())
682 .role(self.override_role.unwrap_or(Role::Button))
683 .when_some(self.aria_label, |this, label| this.aria_label(label))
684 .when_some(self.aria_expanded, |this, expanded| {
685 this.aria_expanded(expanded)
686 })
687 .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
688 .when_some(self.focus_handle, |this, focus_handle| {
689 this.track_focus(&focus_handle)
690 })
691 .font_ui(cx)
692 .group("")
693 .flex_none()
694 .h(self.height.unwrap_or(self.size.rems().into()))
695 .when_some(self.width, |this, width| {
696 this.w(width).justify_center().text_center()
697 })
698 .when(is_outlined, |this| this.border_1())
699 .when_some(self.rounding, |this, rounding| {
700 this.when(rounding.top_left, |this| this.rounded_tl_sm())
701 .when(rounding.top_right, |this| this.rounded_tr_sm())
702 .when(rounding.bottom_right, |this| this.rounded_br_sm())
703 .when(rounding.bottom_left, |this| this.rounded_bl_sm())
704 })
705 .gap(DynamicSpacing::Base04.rems(cx))
706 .map(|this| match self.size {
707 ButtonSize::Large | ButtonSize::Medium => this.px(DynamicSpacing::Base08.rems(cx)),
708 ButtonSize::Default | ButtonSize::Compact => {
709 this.px(DynamicSpacing::Base04.rems(cx))
710 }
711 ButtonSize::None => this.px_px(),
712 })
713 .border_color(style.enabled(self.layer, cx).border_color)
714 .bg(style.enabled(self.layer, cx).background)
715 .when(self.disabled, |this| {
716 if self.cursor_style == CursorStyle::PointingHand {
717 this.cursor_not_allowed()
718 } else {
719 this.cursor(self.cursor_style)
720 }
721 })
722 .when(!self.disabled, |this| {
723 let hovered_style = style.hovered(self.layer, cx);
724 let focus_color =
725 |refinement: StyleRefinement| refinement.bg(hovered_style.background);
726
727 this.cursor(self.cursor_style)
728 .hover(focus_color)
729 .map(|this| {
730 if is_outlined {
731 this.focus_visible(|s| {
732 s.border_color(cx.theme().colors().border_focused)
733 })
734 } else {
735 this.focus_visible(focus_color)
736 }
737 })
738 .active(|active| active.bg(style.active(cx).background))
739 })
740 .when_some(
741 self.on_right_click.filter(|_| !self.disabled),
742 |this, on_right_click| {
743 this.on_mouse_down(MouseButton::Right, |_event, window, cx| {
744 window.prevent_default();
745 cx.stop_propagation();
746 })
747 .on_mouse_up(
748 MouseButton::Right,
749 move |event, window, cx| {
750 cx.stop_propagation();
751 let click_event = ClickEvent::Mouse(MouseClickEvent {
752 down: MouseDownEvent {
753 button: MouseButton::Right,
754 position: event.position,
755 modifiers: event.modifiers,
756 click_count: 1,
757 first_mouse: false,
758 },
759 up: MouseUpEvent {
760 button: MouseButton::Right,
761 position: event.position,
762 modifiers: event.modifiers,
763 click_count: 1,
764 },
765 });
766 (on_right_click)(&click_event, window, cx)
767 },
768 )
769 },
770 )
771 .when_some(
772 self.on_click.filter(|_| !self.disabled),
773 |this, on_click| {
774 this.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
775 .on_click(move |event, window, cx| {
776 cx.stop_propagation();
777 (on_click)(event, window, cx)
778 })
779 },
780 )
781 .when_some(self.tooltip, |this, tooltip| {
782 this.tooltip(move |window, cx| tooltip(window, cx))
783 })
784 .when_some(self.hoverable_tooltip, |this, tooltip| {
785 this.hoverable_tooltip(move |window, cx| tooltip(window, cx))
786 })
787 .children(self.children)
788 }
789}
790
791impl Component for ButtonLike {
792 fn scope() -> ComponentScope {
793 ComponentScope::Input
794 }
795
796 fn sort_name() -> &'static str {
797 // ButtonLike should be at the bottom of the button list
798 "ButtonZ"
799 }
800
801 fn description() -> Option<&'static str> {
802 Some(ButtonLike::DOCS)
803 }
804
805 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
806 Some(
807 v_flex()
808 .gap_6()
809 .children(vec![
810 example_group(vec![
811 single_example(
812 "Default",
813 ButtonLike::new("default")
814 .child(Label::new("Default"))
815 .into_any_element(),
816 ),
817 single_example(
818 "Filled",
819 ButtonLike::new("filled")
820 .style(ButtonStyle::Filled)
821 .child(Label::new("Filled"))
822 .into_any_element(),
823 ),
824 single_example(
825 "Subtle",
826 ButtonLike::new("outline")
827 .style(ButtonStyle::Subtle)
828 .child(Label::new("Subtle"))
829 .into_any_element(),
830 ),
831 single_example(
832 "Tinted",
833 ButtonLike::new("tinted_accent_style")
834 .style(ButtonStyle::Tinted(TintColor::Accent))
835 .child(Label::new("Accent"))
836 .into_any_element(),
837 ),
838 single_example(
839 "Transparent",
840 ButtonLike::new("transparent")
841 .style(ButtonStyle::Transparent)
842 .child(Label::new("Transparent"))
843 .into_any_element(),
844 ),
845 ]),
846 example_group_with_title(
847 "Button Group Constructors",
848 vec![
849 single_example(
850 "Left Rounded",
851 ButtonLike::new_rounded_left("left_rounded")
852 .child(Label::new("Left Rounded"))
853 .style(ButtonStyle::Filled)
854 .into_any_element(),
855 ),
856 single_example(
857 "Right Rounded",
858 ButtonLike::new_rounded_right("right_rounded")
859 .child(Label::new("Right Rounded"))
860 .style(ButtonStyle::Filled)
861 .into_any_element(),
862 ),
863 single_example(
864 "Button Group",
865 h_flex()
866 .gap_px()
867 .child(
868 ButtonLike::new_rounded_left("bg_left")
869 .child(Label::new("Left"))
870 .style(ButtonStyle::Filled),
871 )
872 .child(
873 ButtonLike::new_rounded_right("bg_right")
874 .child(Label::new("Right"))
875 .style(ButtonStyle::Filled),
876 )
877 .into_any_element(),
878 ),
879 ],
880 ),
881 ])
882 .into_any_element(),
883 )
884 }
885}