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