1use std::borrow::Cow;
2
3use gpui::{
4 color::Color,
5 elements::{
6 ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label,
7 MouseEventHandler, ParentElement, Svg,
8 },
9 geometry::vector::{vec2f, Vector2F},
10 scene::MouseClick,
11 Action, Element, ElementBox, EventContext, MouseButton, MouseState, RenderContext, View,
12};
13use serde::Deserialize;
14
15use crate::{ContainedText, Interactive};
16
17#[derive(Clone, Deserialize, Default)]
18pub struct CheckboxStyle {
19 pub icon: SvgStyle,
20 pub label: ContainedText,
21 pub default: ContainerStyle,
22 pub checked: ContainerStyle,
23 pub hovered: ContainerStyle,
24 pub hovered_and_checked: ContainerStyle,
25}
26
27pub fn checkbox<T: 'static, V: View>(
28 label: &'static str,
29 style: &CheckboxStyle,
30 checked: bool,
31 cx: &mut RenderContext<V>,
32 change: fn(checked: bool, cx: &mut EventContext) -> (),
33) -> MouseEventHandler<T> {
34 let label = Label::new(label, style.label.text.clone())
35 .contained()
36 .with_style(style.label.container)
37 .boxed();
38
39 checkbox_with_label(label, style, checked, cx, change)
40}
41
42pub fn checkbox_with_label<T: 'static, V: View>(
43 label: ElementBox,
44 style: &CheckboxStyle,
45 checked: bool,
46 cx: &mut RenderContext<V>,
47 change: fn(checked: bool, cx: &mut EventContext) -> (),
48) -> MouseEventHandler<T> {
49 MouseEventHandler::<T>::new(0, cx, |state, _| {
50 let indicator = if checked {
51 svg(&style.icon)
52 } else {
53 Empty::new()
54 .constrained()
55 .with_width(style.icon.dimensions.width)
56 .with_height(style.icon.dimensions.height)
57 };
58
59 Flex::row()
60 .with_children([
61 indicator
62 .contained()
63 .with_style(if checked {
64 if state.hovered() {
65 style.hovered_and_checked
66 } else {
67 style.checked
68 }
69 } else {
70 if state.hovered() {
71 style.hovered
72 } else {
73 style.default
74 }
75 })
76 .boxed(),
77 label,
78 ])
79 .align_children_center()
80 .boxed()
81 })
82 .on_click(gpui::MouseButton::Left, move |_, cx| change(!checked, cx))
83 .with_cursor_style(gpui::CursorStyle::PointingHand)
84}
85
86#[derive(Clone, Deserialize, Default)]
87pub struct SvgStyle {
88 pub color: Color,
89 pub asset: String,
90 pub dimensions: Dimensions,
91}
92
93#[derive(Clone, Deserialize, Default)]
94pub struct Dimensions {
95 pub width: f32,
96 pub height: f32,
97}
98
99impl Dimensions {
100 pub fn to_vec(&self) -> Vector2F {
101 vec2f(self.width, self.height)
102 }
103}
104
105pub fn svg(style: &SvgStyle) -> ConstrainedBox {
106 Svg::new(style.asset.clone())
107 .with_color(style.color)
108 .constrained()
109 .with_width(style.dimensions.width)
110 .with_height(style.dimensions.height)
111}
112
113#[derive(Clone, Deserialize, Default)]
114pub struct IconStyle {
115 icon: SvgStyle,
116 container: ContainerStyle,
117}
118
119pub fn icon(style: &IconStyle) -> Container {
120 svg(&style.icon).contained().with_style(style.container)
121}
122
123pub fn keystroke_label<V: View>(
124 label_text: &'static str,
125 label_style: &ContainedText,
126 keystroke_style: &ContainedText,
127 action: Box<dyn Action>,
128 cx: &mut RenderContext<V>,
129) -> Container {
130 // FIXME: Put the theme in it's own global so we can
131 // query the keystroke style on our own
132 keystroke_label_for(
133 cx.window_id(),
134 cx.handle().id(),
135 label_text,
136 label_style,
137 keystroke_style,
138 action,
139 )
140}
141
142pub fn keystroke_label_for(
143 window_id: usize,
144 view_id: usize,
145 label_text: &'static str,
146 label_style: &ContainedText,
147 keystroke_style: &ContainedText,
148 action: Box<dyn Action>,
149) -> Container {
150 Flex::row()
151 .with_child(
152 Label::new(label_text, label_style.text.clone())
153 .contained()
154 .boxed(),
155 )
156 .with_child({
157 KeystrokeLabel::new(
158 window_id,
159 view_id,
160 action,
161 keystroke_style.container,
162 keystroke_style.text.clone(),
163 )
164 .flex_float()
165 .boxed()
166 })
167 .contained()
168 .with_style(label_style.container)
169}
170
171pub type ButtonStyle = Interactive<ContainedText>;
172
173pub fn cta_button<L, A, V>(
174 label: L,
175 action: A,
176 max_width: f32,
177 style: &ButtonStyle,
178 cx: &mut RenderContext<V>,
179) -> ElementBox
180where
181 L: Into<Cow<'static, str>>,
182 A: 'static + Action + Clone,
183 V: View,
184{
185 cta_button_with_click(label, max_width, style, cx, move |_, cx| {
186 cx.dispatch_action(action.clone())
187 })
188}
189
190pub fn cta_button_with_click<L, V, F>(
191 label: L,
192 max_width: f32,
193 style: &ButtonStyle,
194 cx: &mut RenderContext<V>,
195 f: F,
196) -> ElementBox
197where
198 L: Into<Cow<'static, str>>,
199 V: View,
200 F: Fn(MouseClick, &mut EventContext) + 'static,
201{
202 MouseEventHandler::<F>::new(0, cx, |state, _| {
203 let style = style.style_for(state, false);
204 Label::new(label, style.text.to_owned())
205 .aligned()
206 .contained()
207 .with_style(style.container)
208 .constrained()
209 .with_max_width(max_width)
210 .boxed()
211 })
212 .on_click(MouseButton::Left, f)
213 .with_cursor_style(gpui::CursorStyle::PointingHand)
214 .boxed()
215}