Skip to main content

button/
button.rs

1use std::{rc::Rc, time::Duration};
2
3use gpui::{prelude::FluentBuilder, *};
4use gpui_animation::{
5    animation::TransitionExt,
6    transition::general::{self, Linear},
7};
8
9#[derive(IntoElement)]
10struct Button {
11    id: ElementId,
12    style: StyleRefinement,
13    children: Vec<AnyElement>,
14    on_hover: Option<Rc<dyn Fn(&bool, &mut Window, &mut App) + 'static>>,
15    on_click: Option<Rc<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>>,
16    selected: Option<bool>,
17    disabled: Option<bool>,
18}
19
20impl Styled for Button {
21    fn style(&mut self) -> &mut StyleRefinement {
22        &mut self.style
23    }
24}
25
26impl ParentElement for Button {
27    fn extend(&mut self, elements: impl IntoIterator<Item = AnyElement>) {
28        self.children.extend(elements);
29    }
30}
31
32impl Button {
33    pub fn new(id: impl Into<ElementId>) -> Self {
34        Self {
35            id: id.into(),
36            style: Default::default(),
37            children: Default::default(),
38            on_hover: None,
39            on_click: None,
40            selected: None,
41            disabled: None,
42        }
43    }
44
45    #[allow(dead_code)]
46    pub fn on_hover(mut self, f: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
47        self.on_hover = Some(Rc::new(f));
48
49        self
50    }
51
52    #[allow(dead_code)]
53    pub fn on_click(mut self, f: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static) -> Self {
54        self.on_click = Some(Rc::new(f));
55
56        self
57    }
58
59    pub fn selected(mut self, selected: bool) -> Self {
60        self.selected = Some(selected);
61
62        self
63    }
64
65    pub fn disabled(mut self, disabled: bool) -> Self {
66        self.disabled = Some(disabled);
67
68        self
69    }
70}
71
72impl RenderOnce for Button {
73    fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
74        let mut root = div()
75            .id(self.id.clone())
76            .flex()
77            .items_center()
78            .justify_center()
79            .w_full()
80            .border_1()
81            .border_color(rgb(0x262626))
82            .rounded_md()
83            .h_8()
84            .bg(rgb(0x0a0a0a));
85
86        root.style().refine(&self.style);
87
88        root.children(self.children)
89            .with_transition(self.id)
90            .when_else(
91                self.disabled.unwrap_or_default(),
92                |this| this.bg(rgb(0x333333)).cursor_not_allowed(),
93                |this| {
94                    this.when_some(self.on_hover, |this, on_hover| {
95                        this.on_hover(move |h, w, a| {
96                            (on_hover)(h, w, a);
97                        })
98                    })
99                    .when_some(self.on_click, |this, on_click| {
100                        this.on_click(move |e, w, a| {
101                            (on_click)(e, w, a);
102                        })
103                    })
104                    .transition_when_else(
105                        self.selected.unwrap_or_default(),
106                        Duration::from_millis(250),
107                        Linear,
108                        |this| this.bg(rgb(0x1a1a1a)),
109                        |this| this.bg(rgb(0x0a0a0a)),
110                    )
111                    .transition_on_hover(Duration::from_millis(250), Linear, |hovered, this| {
112                        if *hovered {
113                            this.bg(rgb(0x1a1a1a))
114                        } else {
115                            this
116                        }
117                    })
118                    .when(!self.selected.unwrap_or_default(), |this| {
119                        this.transition_on_click(
120                            Duration::from_millis(150),
121                            general::EaseInExpo,
122                            |_, this| this.bg(rgb(0x262626)),
123                        )
124                    })
125                },
126            )
127    }
128}
129
130struct BasicBackground;
131
132impl Render for BasicBackground {
133    fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
134        let text_color = rgb(0xffffff);
135
136        div()
137            .flex()
138            .flex_col()
139            .gap_3()
140            .size(px(500.0))
141            .justify_center()
142            .items_center()
143            .bg(rgb(0x0a0a0a))
144            .child(
145                Button::new("Button1")
146                    .child("Button1")
147                    .text_color(text_color),
148            )
149            .child(
150                Button::new("Button2")
151                    .child("Button2")
152                    .selected(true)
153                    .text_color(text_color),
154            )
155            .child(
156                Button::new("Button3")
157                    .child("Button3")
158                    .disabled(true)
159                    .text_color(text_color),
160            )
161    }
162}
163
164fn main() {
165    Application::new().run(|cx: &mut App| {
166        let bounds = Bounds::centered(None, size(px(500.), px(500.0)), cx);
167        cx.open_window(
168            WindowOptions {
169                window_bounds: Some(WindowBounds::Windowed(bounds)),
170                ..Default::default()
171            },
172            |_, cx| cx.new(|_| BasicBackground),
173        )
174        .unwrap();
175    });
176}