1use gpui::{
2 color::Color,
3 elements::{
4 ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label,
5 MouseEventHandler, ParentElement, Svg,
6 },
7 Action, Element, ElementBox, EventContext, RenderContext, View,
8};
9use serde::Deserialize;
10
11use crate::ContainedText;
12
13#[derive(Clone, Deserialize, Default)]
14pub struct CheckboxStyle {
15 pub icon: IconStyle,
16 pub label: ContainedText,
17 pub default: ContainerStyle,
18 pub checked: ContainerStyle,
19 pub hovered: ContainerStyle,
20 pub hovered_and_checked: ContainerStyle,
21}
22
23pub fn checkbox<T: 'static, V: View>(
24 label: &'static str,
25 style: &CheckboxStyle,
26 checked: bool,
27 cx: &mut RenderContext<V>,
28 change: fn(checked: bool, cx: &mut EventContext) -> (),
29) -> MouseEventHandler<T> {
30 let label = Label::new(label, style.label.text.clone())
31 .contained()
32 .with_style(style.label.container)
33 .boxed();
34
35 checkbox_with_label(label, style, checked, cx, change)
36}
37
38pub fn checkbox_with_label<T: 'static, V: View>(
39 label: ElementBox,
40 style: &CheckboxStyle,
41 checked: bool,
42 cx: &mut RenderContext<V>,
43 change: fn(checked: bool, cx: &mut EventContext) -> (),
44) -> MouseEventHandler<T> {
45 MouseEventHandler::<T>::new(0, cx, |state, _| {
46 let indicator = if checked {
47 icon(&style.icon)
48 } else {
49 Empty::new()
50 .constrained()
51 .with_width(style.icon.dimensions.width)
52 .with_height(style.icon.dimensions.height)
53 };
54
55 Flex::row()
56 .with_children([
57 indicator
58 .contained()
59 .with_style(if checked {
60 if state.hovered() {
61 style.hovered_and_checked
62 } else {
63 style.checked
64 }
65 } else {
66 if state.hovered() {
67 style.hovered
68 } else {
69 style.default
70 }
71 })
72 .boxed(),
73 label,
74 ])
75 .align_children_center()
76 .boxed()
77 })
78 .on_click(gpui::MouseButton::Left, move |_, cx| change(!checked, cx))
79 .with_cursor_style(gpui::CursorStyle::PointingHand)
80}
81
82#[derive(Clone, Deserialize, Default)]
83pub struct IconStyle {
84 pub color: Color,
85 pub icon: String,
86 pub dimensions: Dimensions,
87}
88
89#[derive(Clone, Deserialize, Default)]
90pub struct Dimensions {
91 pub width: f32,
92 pub height: f32,
93}
94
95pub fn icon(style: &IconStyle) -> ConstrainedBox {
96 Svg::new(style.icon.clone())
97 .with_color(style.color)
98 .constrained()
99 .with_width(style.dimensions.width)
100 .with_height(style.dimensions.height)
101}
102
103pub fn keystroke_label<V: View>(
104 label_text: &'static str,
105 label_style: &ContainedText,
106 keystroke_style: &ContainedText,
107 action: Box<dyn Action>,
108 cx: &mut RenderContext<V>,
109) -> Container {
110 // FIXME: Put the theme in it's own global so we can
111 // query the keystroke style on our own
112 keystroke_label_for(
113 cx.window_id(),
114 cx.handle().id(),
115 label_text,
116 label_style,
117 keystroke_style,
118 action,
119 )
120}
121
122pub fn keystroke_label_for(
123 window_id: usize,
124 view_id: usize,
125 label_text: &'static str,
126 label_style: &ContainedText,
127 keystroke_style: &ContainedText,
128 action: Box<dyn Action>,
129) -> Container {
130 Flex::row()
131 .with_child(
132 Label::new(label_text, label_style.text.clone())
133 .contained()
134 .boxed(),
135 )
136 .with_child({
137 KeystrokeLabel::new(
138 window_id,
139 view_id,
140 action,
141 keystroke_style.container,
142 keystroke_style.text.clone(),
143 )
144 .flex_float()
145 .boxed()
146 })
147 .contained()
148 .with_style(label_style.container)
149}