1use button_component::Button;
2
3use gpui::{
4 color::Color,
5 elements::{ContainerStyle, Flex, Label, ParentElement, StatefulComponent},
6 fonts::{self, TextStyle},
7 platform::WindowOptions,
8 AnyElement, App, Element, Entity, View, ViewContext,
9};
10use log::LevelFilter;
11use pathfinder_geometry::vector::vec2f;
12use simplelog::SimpleLogger;
13use theme::Toggleable;
14use toggleable_button::ToggleableButton;
15
16// cargo run -p gpui --example components
17
18fn main() {
19 SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
20
21 App::new(()).unwrap().run(|cx| {
22 cx.platform().activate(true);
23 cx.add_window(WindowOptions::with_bounds(vec2f(300., 200.)), |_| {
24 TestView {
25 count: 0,
26 is_doubling: false,
27 }
28 });
29 });
30}
31
32pub struct TestView {
33 count: usize,
34 is_doubling: bool,
35}
36
37impl TestView {
38 fn increase_count(&mut self) {
39 if self.is_doubling {
40 self.count *= 2;
41 } else {
42 self.count += 1;
43 }
44 }
45}
46
47impl Entity for TestView {
48 type Event = ();
49}
50
51type ButtonStyle = ContainerStyle;
52
53impl View for TestView {
54 fn ui_name() -> &'static str {
55 "TestView"
56 }
57
58 fn render(&mut self, cx: &mut ViewContext<'_, '_, Self>) -> AnyElement<Self> {
59 fonts::with_font_cache(cx.font_cache.to_owned(), || {
60 Flex::column()
61 .with_child(Label::new(
62 format!("Count: {}", self.count),
63 TextStyle::for_color(Color::red()),
64 ))
65 .with_child(
66 Button::new(move |_, v: &mut Self, cx| {
67 v.increase_count();
68 cx.notify();
69 })
70 .with_text(
71 "Hello from a counting BUTTON",
72 TextStyle::for_color(Color::blue()),
73 )
74 .with_style(ButtonStyle::fill(Color::yellow()))
75 .element(),
76 )
77 .with_child(
78 ToggleableButton::new(self.is_doubling, move |_, v: &mut Self, cx| {
79 v.is_doubling = !v.is_doubling;
80 cx.notify();
81 })
82 .with_text("Double the count?", TextStyle::for_color(Color::black()))
83 .with_style(Toggleable {
84 inactive: ButtonStyle::fill(Color::red()),
85 active: ButtonStyle::fill(Color::green()),
86 })
87 .element(),
88 )
89 .expanded()
90 .contained()
91 .with_background_color(Color::white())
92 .into_any()
93 })
94 }
95}
96
97mod theme {
98 pub struct Toggleable<T> {
99 pub inactive: T,
100 pub active: T,
101 }
102
103 impl<T> Toggleable<T> {
104 pub fn style_for(&self, active: bool) -> &T {
105 if active {
106 &self.active
107 } else {
108 &self.inactive
109 }
110 }
111 }
112}
113
114// Component creation:
115mod toggleable_button {
116 use gpui::{
117 elements::{ContainerStyle, LabelStyle, StatefulComponent},
118 scene::MouseClick,
119 EventContext, View,
120 };
121
122 use crate::{button_component::Button, theme::Toggleable};
123
124 pub struct ToggleableButton<V: View> {
125 active: bool,
126 style: Option<Toggleable<ContainerStyle>>,
127 button: Button<V>,
128 }
129
130 impl<V: View> ToggleableButton<V> {
131 pub fn new<F>(active: bool, on_click: F) -> Self
132 where
133 F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
134 {
135 Self {
136 active,
137 button: Button::new(on_click),
138 style: None,
139 }
140 }
141
142 pub fn with_text(self, text: &str, style: impl Into<LabelStyle>) -> ToggleableButton<V> {
143 ToggleableButton {
144 active: self.active,
145 style: self.style,
146 button: self.button.with_text(text, style),
147 }
148 }
149
150 pub fn with_style(self, style: Toggleable<ContainerStyle>) -> ToggleableButton<V> {
151 ToggleableButton {
152 active: self.active,
153 style: Some(style),
154 button: self.button,
155 }
156 }
157 }
158
159 impl<V: View> StatefulComponent<V> for ToggleableButton<V> {
160 fn render(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
161 let button = if let Some(style) = self.style {
162 self.button.with_style(*style.style_for(self.active))
163 } else {
164 self.button
165 };
166 button.render(v, cx)
167 }
168 }
169}
170
171mod button_component {
172
173 use gpui::{
174 elements::{ContainerStyle, Label, LabelStyle, MouseEventHandler, StatefulComponent},
175 platform::MouseButton,
176 scene::MouseClick,
177 AnyElement, Element, EventContext, TypeTag, View, ViewContext,
178 };
179
180 type ClickHandler<V> = Box<dyn Fn(MouseClick, &mut V, &mut EventContext<V>)>;
181
182 pub struct Button<V: View> {
183 click_handler: ClickHandler<V>,
184 tag: TypeTag,
185 contents: Option<AnyElement<V>>,
186 style: Option<ContainerStyle>,
187 }
188
189 impl<V: View> Button<V> {
190 pub fn new<F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static>(handler: F) -> Self {
191 Self {
192 click_handler: Box::new(handler),
193 tag: TypeTag::new::<F>(),
194 style: None,
195 contents: None,
196 }
197 }
198
199 pub fn with_text(mut self, text: &str, style: impl Into<LabelStyle>) -> Self {
200 self.contents = Some(Label::new(text.to_string(), style).into_any());
201 self
202 }
203
204 pub fn _with_contents<E: Element<V>>(mut self, contents: E) -> Self {
205 self.contents = Some(contents.into_any());
206 self
207 }
208
209 pub fn with_style(mut self, style: ContainerStyle) -> Self {
210 self.style = Some(style);
211 self
212 }
213 }
214
215 impl<V: View> StatefulComponent<V> for Button<V> {
216 fn render(self, _: &mut V, cx: &mut ViewContext<V>) -> AnyElement<V> {
217 let click_handler = self.click_handler;
218
219 let result = MouseEventHandler::new_dynamic(self.tag, 0, cx, |_, _| {
220 self.contents
221 .unwrap_or_else(|| gpui::elements::Empty::new().into_any())
222 })
223 .on_click(MouseButton::Left, move |click, v, cx| {
224 click_handler(click, v, cx);
225 })
226 .contained();
227
228 let result = if let Some(style) = self.style {
229 result.with_style(style)
230 } else {
231 result
232 };
233
234 result.into_any()
235 }
236 }
237}