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