1use documented::Documented;
2use gpui::{
3 AnyElement, AnyView, ClickEvent, CursorStyle, DefiniteLength, FocusHandle, Hsla, MouseButton,
4 MouseClickEvent, MouseDownEvent, MouseUpEvent, Rems, 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}
470
471impl ButtonLike {
472 pub fn new(id: impl Into<ElementId>) -> Self {
473 Self {
474 base: div(),
475 id: id.into(),
476 style: ButtonStyle::default(),
477 disabled: false,
478 selected: false,
479 selected_style: None,
480 width: None,
481 height: None,
482 size: ButtonSize::Default,
483 rounding: Some(ButtonLikeRounding::ALL),
484 tooltip: None,
485 hoverable_tooltip: None,
486 children: SmallVec::new(),
487 cursor_style: CursorStyle::PointingHand,
488 on_click: None,
489 on_right_click: None,
490 layer: None,
491 tab_index: None,
492 focus_handle: None,
493 }
494 }
495
496 pub fn new_rounded_left(id: impl Into<ElementId>) -> Self {
497 Self::new(id).rounding(ButtonLikeRounding::LEFT)
498 }
499
500 pub fn new_rounded_right(id: impl Into<ElementId>) -> Self {
501 Self::new(id).rounding(ButtonLikeRounding::RIGHT)
502 }
503
504 pub fn new_rounded_all(id: impl Into<ElementId>) -> Self {
505 Self::new(id).rounding(ButtonLikeRounding::ALL)
506 }
507
508 pub fn opacity(mut self, opacity: f32) -> Self {
509 self.base = self.base.opacity(opacity);
510 self
511 }
512
513 pub fn height(mut self, height: DefiniteLength) -> Self {
514 self.height = Some(height);
515 self
516 }
517
518 pub(crate) fn rounding(mut self, rounding: impl Into<Option<ButtonLikeRounding>>) -> Self {
519 self.rounding = rounding.into();
520 self
521 }
522
523 pub fn on_right_click(
524 mut self,
525 handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
526 ) -> Self {
527 self.on_right_click = Some(Box::new(handler));
528 self
529 }
530
531 pub fn hoverable_tooltip(
532 mut self,
533 tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static,
534 ) -> Self {
535 self.hoverable_tooltip = Some(Box::new(tooltip));
536 self
537 }
538}
539
540impl Disableable for ButtonLike {
541 fn disabled(mut self, disabled: bool) -> Self {
542 self.disabled = disabled;
543 self
544 }
545}
546
547impl Toggleable for ButtonLike {
548 fn toggle_state(mut self, selected: bool) -> Self {
549 self.selected = selected;
550 self
551 }
552}
553
554impl SelectableButton for ButtonLike {
555 fn selected_style(mut self, style: ButtonStyle) -> Self {
556 self.selected_style = Some(style);
557 self
558 }
559}
560
561impl Clickable for ButtonLike {
562 fn on_click(mut self, handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
563 self.on_click = Some(Box::new(handler));
564 self
565 }
566
567 fn cursor_style(mut self, cursor_style: CursorStyle) -> Self {
568 self.cursor_style = cursor_style;
569 self
570 }
571}
572
573impl FixedWidth for ButtonLike {
574 fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
575 self.width = Some(width.into());
576 self
577 }
578
579 fn full_width(mut self) -> Self {
580 self.width = Some(relative(1.));
581 self
582 }
583}
584
585impl ButtonCommon for ButtonLike {
586 fn id(&self) -> &ElementId {
587 &self.id
588 }
589
590 fn style(mut self, style: ButtonStyle) -> Self {
591 self.style = style;
592 self
593 }
594
595 fn size(mut self, size: ButtonSize) -> Self {
596 self.size = size;
597 self
598 }
599
600 fn tooltip(mut self, tooltip: impl Fn(&mut Window, &mut App) -> AnyView + 'static) -> Self {
601 self.tooltip = Some(Box::new(tooltip));
602 self
603 }
604
605 fn tab_index(mut self, tab_index: impl Into<isize>) -> Self {
606 self.tab_index = Some(tab_index.into());
607 self
608 }
609
610 fn layer(mut self, elevation: ElevationIndex) -> Self {
611 self.layer = Some(elevation);
612 self
613 }
614
615 fn track_focus(mut self, focus_handle: &gpui::FocusHandle) -> Self {
616 self.focus_handle = Some(focus_handle.clone());
617 self
618 }
619}
620
621impl VisibleOnHover for ButtonLike {
622 fn visible_on_hover(mut self, group_name: impl Into<SharedString>) -> Self {
623 self.base = self.base.visible_on_hover(group_name);
624 self
625 }
626}
627
628impl ParentElement for ButtonLike {
629 fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
630 self.children.extend(elements)
631 }
632}
633
634impl RenderOnce for ButtonLike {
635 fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
636 let style = self
637 .selected_style
638 .filter(|_| self.selected)
639 .unwrap_or(self.style);
640
641 let is_outlined = matches!(
642 self.style,
643 ButtonStyle::Outlined | ButtonStyle::OutlinedGhost
644 );
645
646 self.base
647 .h_flex()
648 .id(self.id.clone())
649 .when_some(self.tab_index, |this, tab_index| this.tab_index(tab_index))
650 .when_some(self.focus_handle, |this, focus_handle| {
651 this.track_focus(&focus_handle)
652 })
653 .font_ui(cx)
654 .group("")
655 .flex_none()
656 .h(self.height.unwrap_or(self.size.rems().into()))
657 .when_some(self.width, |this, width| {
658 this.w(width).justify_center().text_center()
659 })
660 .when(is_outlined, |this| this.border_1())
661 .when_some(self.rounding, |this, rounding| {
662 this.when(rounding.top_left, |this| this.rounded_tl_sm())
663 .when(rounding.top_right, |this| this.rounded_tr_sm())
664 .when(rounding.bottom_right, |this| this.rounded_br_sm())
665 .when(rounding.bottom_left, |this| this.rounded_bl_sm())
666 })
667 .gap(DynamicSpacing::Base04.rems(cx))
668 .map(|this| match self.size {
669 ButtonSize::Large | ButtonSize::Medium => this.px(DynamicSpacing::Base08.rems(cx)),
670 ButtonSize::Default | ButtonSize::Compact => {
671 this.px(DynamicSpacing::Base04.rems(cx))
672 }
673 ButtonSize::None => this.px_px(),
674 })
675 .border_color(style.enabled(self.layer, cx).border_color)
676 .bg(style.enabled(self.layer, cx).background)
677 .when(self.disabled, |this| {
678 if self.cursor_style == CursorStyle::PointingHand {
679 this.cursor_not_allowed()
680 } else {
681 this.cursor(self.cursor_style)
682 }
683 })
684 .when(!self.disabled, |this| {
685 let hovered_style = style.hovered(self.layer, cx);
686 let focus_color =
687 |refinement: StyleRefinement| refinement.bg(hovered_style.background);
688
689 this.cursor(self.cursor_style)
690 .hover(focus_color)
691 .map(|this| {
692 if is_outlined {
693 this.focus_visible(|s| {
694 s.border_color(cx.theme().colors().border_focused)
695 })
696 } else {
697 this.focus_visible(focus_color)
698 }
699 })
700 .active(|active| active.bg(style.active(cx).background))
701 })
702 .when_some(
703 self.on_right_click.filter(|_| !self.disabled),
704 |this, on_right_click| {
705 this.on_mouse_down(MouseButton::Right, |_event, window, cx| {
706 window.prevent_default();
707 cx.stop_propagation();
708 })
709 .on_mouse_up(
710 MouseButton::Right,
711 move |event, window, cx| {
712 cx.stop_propagation();
713 let click_event = ClickEvent::Mouse(MouseClickEvent {
714 down: MouseDownEvent {
715 button: MouseButton::Right,
716 position: event.position,
717 modifiers: event.modifiers,
718 click_count: 1,
719 first_mouse: false,
720 },
721 up: MouseUpEvent {
722 button: MouseButton::Right,
723 position: event.position,
724 modifiers: event.modifiers,
725 click_count: 1,
726 },
727 });
728 (on_right_click)(&click_event, window, cx)
729 },
730 )
731 },
732 )
733 .when_some(
734 self.on_click.filter(|_| !self.disabled),
735 |this, on_click| {
736 this.on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
737 .on_click(move |event, window, cx| {
738 cx.stop_propagation();
739 (on_click)(event, window, cx)
740 })
741 },
742 )
743 .when_some(self.tooltip, |this, tooltip| {
744 this.tooltip(move |window, cx| tooltip(window, cx))
745 })
746 .when_some(self.hoverable_tooltip, |this, tooltip| {
747 this.hoverable_tooltip(move |window, cx| tooltip(window, cx))
748 })
749 .children(self.children)
750 }
751}
752
753impl Component for ButtonLike {
754 fn scope() -> ComponentScope {
755 ComponentScope::Input
756 }
757
758 fn sort_name() -> &'static str {
759 // ButtonLike should be at the bottom of the button list
760 "ButtonZ"
761 }
762
763 fn description() -> Option<&'static str> {
764 Some(ButtonLike::DOCS)
765 }
766
767 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
768 Some(
769 v_flex()
770 .gap_6()
771 .children(vec![
772 example_group(vec![
773 single_example(
774 "Default",
775 ButtonLike::new("default")
776 .child(Label::new("Default"))
777 .into_any_element(),
778 ),
779 single_example(
780 "Filled",
781 ButtonLike::new("filled")
782 .style(ButtonStyle::Filled)
783 .child(Label::new("Filled"))
784 .into_any_element(),
785 ),
786 single_example(
787 "Subtle",
788 ButtonLike::new("outline")
789 .style(ButtonStyle::Subtle)
790 .child(Label::new("Subtle"))
791 .into_any_element(),
792 ),
793 single_example(
794 "Tinted",
795 ButtonLike::new("tinted_accent_style")
796 .style(ButtonStyle::Tinted(TintColor::Accent))
797 .child(Label::new("Accent"))
798 .into_any_element(),
799 ),
800 single_example(
801 "Transparent",
802 ButtonLike::new("transparent")
803 .style(ButtonStyle::Transparent)
804 .child(Label::new("Transparent"))
805 .into_any_element(),
806 ),
807 ]),
808 example_group_with_title(
809 "Button Group Constructors",
810 vec![
811 single_example(
812 "Left Rounded",
813 ButtonLike::new_rounded_left("left_rounded")
814 .child(Label::new("Left Rounded"))
815 .style(ButtonStyle::Filled)
816 .into_any_element(),
817 ),
818 single_example(
819 "Right Rounded",
820 ButtonLike::new_rounded_right("right_rounded")
821 .child(Label::new("Right Rounded"))
822 .style(ButtonStyle::Filled)
823 .into_any_element(),
824 ),
825 single_example(
826 "Button Group",
827 h_flex()
828 .gap_px()
829 .child(
830 ButtonLike::new_rounded_left("bg_left")
831 .child(Label::new("Left"))
832 .style(ButtonStyle::Filled),
833 )
834 .child(
835 ButtonLike::new_rounded_right("bg_right")
836 .child(Label::new("Right"))
837 .style(ButtonStyle::Filled),
838 )
839 .into_any_element(),
840 ),
841 ],
842 ),
843 ])
844 .into_any_element(),
845 )
846 }
847}