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