1use std::borrow::Cow;
2
3use gpui::{
4 elements::{
5 ConstrainedBox, Container, ContainerStyle, Dimensions, Empty, Flex, KeystrokeLabel, Label,
6 MouseEventHandler, ParentElement, Stack, Svg, SvgStyle,
7 },
8 fonts::TextStyle,
9 geometry::vector::Vector2F,
10 platform,
11 platform::MouseButton,
12 scene::MouseClick,
13 Action, Element, EventContext, MouseState, View, ViewContext,
14};
15use schemars::JsonSchema;
16use serde::Deserialize;
17
18use crate::{ContainedText, Interactive};
19
20#[derive(Clone, Deserialize, Default, JsonSchema)]
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
96pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
97 Svg::new(style.asset.clone())
98 .with_color(style.color)
99 .constrained()
100 .with_width(style.dimensions.width)
101 .with_height(style.dimensions.height)
102}
103
104#[derive(Clone, Deserialize, Default, JsonSchema)]
105pub struct IconStyle {
106 pub icon: SvgStyle,
107 pub container: ContainerStyle,
108}
109
110pub fn icon<V: View>(style: &IconStyle) -> Container<V> {
111 svg(&style.icon).contained().with_style(style.container)
112}
113
114pub fn keystroke_label<V: View>(
115 label_text: &'static str,
116 label_style: &ContainedText,
117 keystroke_style: &ContainedText,
118 action: Box<dyn Action>,
119 cx: &mut ViewContext<V>,
120) -> Container<V> {
121 // FIXME: Put the theme in it's own global so we can
122 // query the keystroke style on our own
123 Flex::row()
124 .with_child(Label::new(label_text, label_style.text.clone()).contained())
125 .with_child(
126 KeystrokeLabel::new(
127 cx.view_id(),
128 action,
129 keystroke_style.container,
130 keystroke_style.text.clone(),
131 )
132 .flex_float(),
133 )
134 .contained()
135 .with_style(label_style.container)
136}
137
138pub type ButtonStyle = Interactive<ContainedText>;
139
140pub fn cta_button<Tag, L, V, F>(
141 label: L,
142 max_width: f32,
143 style: &ButtonStyle,
144 cx: &mut ViewContext<V>,
145 f: F,
146) -> MouseEventHandler<Tag, V>
147where
148 Tag: 'static,
149 L: Into<Cow<'static, str>>,
150 V: View,
151 F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
152{
153 MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
154 let style = style.style_for(state);
155 Label::new(label, style.text.to_owned())
156 .aligned()
157 .contained()
158 .with_style(style.container)
159 .constrained()
160 .with_max_width(max_width)
161 })
162 .on_click(MouseButton::Left, f)
163 .with_cursor_style(platform::CursorStyle::PointingHand)
164}
165
166#[derive(Clone, Deserialize, Default, JsonSchema)]
167pub struct ModalStyle {
168 close_icon: Interactive<IconStyle>,
169 container: ContainerStyle,
170 titlebar: ContainerStyle,
171 title_text: Interactive<TextStyle>,
172 dimensions: Dimensions,
173}
174
175impl ModalStyle {
176 pub fn dimensions(&self) -> Vector2F {
177 self.dimensions.to_vec()
178 }
179}
180
181pub fn modal<Tag, V, I, D, F>(
182 title: I,
183 style: &ModalStyle,
184 cx: &mut ViewContext<V>,
185 build_modal: F,
186) -> impl Element<V>
187where
188 Tag: 'static,
189 V: View,
190 I: Into<Cow<'static, str>>,
191 D: Element<V>,
192 F: FnOnce(&mut gpui::ViewContext<V>) -> D,
193{
194 const TITLEBAR_HEIGHT: f32 = 28.;
195 // let active = cx.window_is_active(cx.window_id());
196
197 Flex::column()
198 .with_child(
199 Stack::new()
200 .with_child(Label::new(
201 title,
202 style
203 .title_text
204 .style_for(&mut MouseState::default())
205 .clone(),
206 ))
207 .with_child(
208 // FIXME: Get a better tag type
209 MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
210 let style = style.close_icon.style_for(state);
211 icon(style)
212 })
213 .on_click(platform::MouseButton::Left, move |_, _, cx| {
214 cx.remove_window();
215 })
216 .with_cursor_style(platform::CursorStyle::PointingHand)
217 .aligned()
218 .right(),
219 )
220 .contained()
221 .with_style(style.titlebar)
222 .constrained()
223 .with_height(TITLEBAR_HEIGHT),
224 )
225 .with_child(
226 build_modal(cx)
227 .contained()
228 .with_style(style.container)
229 .constrained()
230 .with_width(style.dimensions().x())
231 .with_height(style.dimensions().y() - TITLEBAR_HEIGHT),
232 )
233 .constrained()
234 .with_height(style.dimensions().y())
235}