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}