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, 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<V>
38where
39 Tag: 'static,
40 V: 'static,
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::<Tag, _, _, _>(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<V>
57where
58 Tag: 'static,
59 D: Element<V>,
60 V: 'static,
61 F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
62{
63 MouseEventHandler::new::<Tag, _>(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: 'static>(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
110impl IconStyle {
111 pub fn width(&self) -> f32 {
112 self.icon.dimensions.width
113 + self.container.padding.left
114 + self.container.padding.right
115 + self.container.margin.left
116 + self.container.margin.right
117 }
118}
119
120pub fn icon<V: 'static>(style: &IconStyle) -> Container<V> {
121 svg(&style.icon).contained().with_style(style.container)
122}
123
124pub fn keystroke_label<V: 'static>(
125 label_text: &'static str,
126 label_style: &ContainedText,
127 keystroke_style: &ContainedText,
128 action: Box<dyn Action>,
129 cx: &mut ViewContext<V>,
130) -> Container<V> {
131 // FIXME: Put the theme in it's own global so we can
132 // query the keystroke style on our own
133 Flex::row()
134 .with_child(Label::new(label_text, label_style.text.clone()).contained())
135 .with_child(
136 KeystrokeLabel::new(
137 cx.view_id(),
138 action,
139 keystroke_style.container,
140 keystroke_style.text.clone(),
141 )
142 .flex_float(),
143 )
144 .contained()
145 .with_style(label_style.container)
146}
147
148pub type CopilotCTAButton = Interactive<ContainedText>;
149
150pub fn cta_button<Tag, L, V, F>(
151 label: L,
152 max_width: f32,
153 style: &CopilotCTAButton,
154 cx: &mut ViewContext<V>,
155 f: F,
156) -> MouseEventHandler<V>
157where
158 Tag: 'static,
159 L: Into<Cow<'static, str>>,
160 V: 'static,
161 F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
162{
163 MouseEventHandler::new::<Tag, _>(0, cx, |state, _| {
164 let style = style.style_for(state);
165 Label::new(label, style.text.to_owned())
166 .aligned()
167 .contained()
168 .with_style(style.container)
169 .constrained()
170 .with_max_width(max_width)
171 })
172 .on_click(MouseButton::Left, f)
173 .with_cursor_style(platform::CursorStyle::PointingHand)
174}
175
176#[derive(Clone, Deserialize, Default, JsonSchema)]
177pub struct ModalStyle {
178 close_icon: Interactive<IconStyle>,
179 container: ContainerStyle,
180 titlebar: ContainerStyle,
181 title_text: Interactive<TextStyle>,
182 dimensions: Dimensions,
183}
184
185impl ModalStyle {
186 pub fn dimensions(&self) -> Vector2F {
187 self.dimensions.to_vec()
188 }
189}
190
191pub fn modal<Tag, V, I, D, F>(
192 title: I,
193 style: &ModalStyle,
194 cx: &mut ViewContext<V>,
195 build_modal: F,
196) -> impl Element<V>
197where
198 Tag: 'static,
199 I: Into<Cow<'static, str>>,
200 D: Element<V>,
201 V: 'static,
202 F: FnOnce(&mut gpui::ViewContext<V>) -> D,
203{
204 const TITLEBAR_HEIGHT: f32 = 28.;
205
206 Flex::column()
207 .with_child(
208 Stack::new()
209 .with_child(Label::new(
210 title,
211 style
212 .title_text
213 .style_for(&mut MouseState::default())
214 .clone(),
215 ))
216 .with_child(
217 // FIXME: Get a better tag type
218 MouseEventHandler::new::<Tag, _>(999999, cx, |state, _cx| {
219 let style = style.close_icon.style_for(state);
220 icon(style)
221 })
222 .on_click(platform::MouseButton::Left, move |_, _, cx| {
223 cx.remove_window();
224 })
225 .with_cursor_style(platform::CursorStyle::PointingHand)
226 .aligned()
227 .right(),
228 )
229 .contained()
230 .with_style(style.titlebar)
231 .constrained()
232 .with_height(TITLEBAR_HEIGHT),
233 )
234 .with_child(
235 build_modal(cx)
236 .contained()
237 .with_style(style.container)
238 .constrained()
239 .with_width(style.dimensions().x())
240 .with_height(style.dimensions().y() - TITLEBAR_HEIGHT),
241 )
242 .constrained()
243 .with_height(style.dimensions().y())
244}