1use gpui::elements::StyleableComponent;
2
3use crate::{Interactive, Toggleable};
4
5use self::{action_button::ButtonStyle, svg::SvgStyle, toggle::Toggle};
6
7pub type ToggleIconButtonStyle = Toggleable<Interactive<ButtonStyle<SvgStyle>>>;
8
9pub trait ComponentExt<C: StyleableComponent> {
10 fn toggleable(self, active: bool) -> Toggle<C, ()>;
11}
12
13impl<C: StyleableComponent> ComponentExt<C> for C {
14 fn toggleable(self, active: bool) -> Toggle<C, ()> {
15 Toggle::new(self, active)
16 }
17}
18
19pub mod toggle {
20 use gpui::elements::{GeneralComponent, StyleableComponent};
21
22 use crate::Toggleable;
23
24 pub struct Toggle<C, S> {
25 style: S,
26 active: bool,
27 component: C,
28 }
29
30 impl<C: StyleableComponent> Toggle<C, ()> {
31 pub fn new(component: C, active: bool) -> Self {
32 Toggle {
33 active,
34 component,
35 style: (),
36 }
37 }
38 }
39
40 impl<C: StyleableComponent> StyleableComponent for Toggle<C, ()> {
41 type Style = Toggleable<C::Style>;
42
43 type Output = Toggle<C, Self::Style>;
44
45 fn with_style(self, style: Self::Style) -> Self::Output {
46 Toggle {
47 active: self.active,
48 component: self.component,
49 style,
50 }
51 }
52 }
53
54 impl<C: StyleableComponent> GeneralComponent for Toggle<C, Toggleable<C::Style>> {
55 fn render<V: gpui::View>(
56 self,
57 v: &mut V,
58 cx: &mut gpui::ViewContext<V>,
59 ) -> gpui::AnyElement<V> {
60 self.component
61 .with_style(self.style.in_state(self.active).clone())
62 .render(v, cx)
63 }
64 }
65}
66
67pub mod action_button {
68 use std::borrow::Cow;
69
70 use gpui::{
71 elements::{
72 ContainerStyle, GeneralComponent, MouseEventHandler, StyleableComponent, TooltipStyle,
73 },
74 platform::{CursorStyle, MouseButton},
75 Action, Element, TypeTag, View,
76 };
77 use schemars::JsonSchema;
78 use serde_derive::Deserialize;
79
80 use crate::Interactive;
81
82 pub struct ActionButton<C, S> {
83 action: Box<dyn Action>,
84 tooltip: Option<(Cow<'static, str>, TooltipStyle)>,
85 tag: TypeTag,
86 contents: C,
87 style: Interactive<S>,
88 }
89
90 #[derive(Clone, Deserialize, Default, JsonSchema)]
91 pub struct ButtonStyle<C> {
92 #[serde(flatten)]
93 container: ContainerStyle,
94 button_width: Option<f32>,
95 button_height: Option<f32>,
96 #[serde(flatten)]
97 contents: C,
98 }
99
100 impl ActionButton<(), ()> {
101 pub fn new_dynamic(action: Box<dyn Action>) -> Self {
102 Self {
103 contents: (),
104 tag: action.type_tag(),
105 style: Interactive::new_blank(),
106 tooltip: None,
107 action,
108 }
109 }
110
111 pub fn new<A: Action + Clone>(action: A) -> Self {
112 Self::new_dynamic(Box::new(action))
113 }
114
115 pub fn with_tooltip(
116 mut self,
117 tooltip: impl Into<Cow<'static, str>>,
118 tooltip_style: TooltipStyle,
119 ) -> Self {
120 self.tooltip = Some((tooltip.into(), tooltip_style));
121 self
122 }
123
124 pub fn with_contents<C: StyleableComponent>(self, contents: C) -> ActionButton<C, ()> {
125 ActionButton {
126 action: self.action,
127 tag: self.tag,
128 style: self.style,
129 tooltip: self.tooltip,
130 contents,
131 }
132 }
133 }
134
135 impl<C: StyleableComponent> StyleableComponent for ActionButton<C, ()> {
136 type Style = Interactive<ButtonStyle<C::Style>>;
137 type Output = ActionButton<C, ButtonStyle<C::Style>>;
138
139 fn with_style(self, style: Self::Style) -> Self::Output {
140 ActionButton {
141 action: self.action,
142 tag: self.tag,
143 contents: self.contents,
144 tooltip: self.tooltip,
145
146 style,
147 }
148 }
149 }
150
151 impl<C: StyleableComponent> GeneralComponent for ActionButton<C, ButtonStyle<C::Style>> {
152 fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
153 let mut button = MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
154 let style = self.style.style_for(state);
155 let mut contents = self
156 .contents
157 .with_style(style.contents.to_owned())
158 .render(v, cx)
159 .contained()
160 .with_style(style.container)
161 .constrained();
162
163 if let Some(height) = style.button_height {
164 contents = contents.with_height(height);
165 }
166
167 if let Some(width) = style.button_width {
168 contents = contents.with_width(width);
169 }
170
171 contents.into_any()
172 })
173 .on_click(MouseButton::Left, {
174 let action = self.action.boxed_clone();
175 move |_, _, cx| {
176 cx.window()
177 .dispatch_action(cx.view_id(), action.as_ref(), cx);
178 }
179 })
180 .with_cursor_style(CursorStyle::PointingHand)
181 .into_any();
182
183 if let Some((tooltip, style)) = self.tooltip {
184 button = button
185 .with_dynamic_tooltip(self.tag, 0, tooltip, Some(self.action), style, cx)
186 .into_any()
187 }
188
189 button
190 }
191 }
192}
193
194pub mod svg {
195 use std::borrow::Cow;
196
197 use gpui::{
198 elements::{GeneralComponent, StyleableComponent},
199 Element,
200 };
201 use schemars::JsonSchema;
202 use serde::Deserialize;
203
204 #[derive(Clone, Default, JsonSchema)]
205 pub struct SvgStyle {
206 icon_width: f32,
207 icon_height: f32,
208 color: gpui::color::Color,
209 }
210
211 impl<'de> Deserialize<'de> for SvgStyle {
212 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
213 where
214 D: serde::Deserializer<'de>,
215 {
216 #[derive(Deserialize)]
217 #[serde(untagged)]
218 pub enum IconSize {
219 IconSize { icon_size: f32 },
220 Dimensions { width: f32, height: f32 },
221 }
222
223 #[derive(Deserialize)]
224 struct SvgStyleHelper {
225 #[serde(flatten)]
226 size: IconSize,
227 color: gpui::color::Color,
228 }
229
230 let json = SvgStyleHelper::deserialize(deserializer)?;
231 let color = json.color;
232
233 let result = match json.size {
234 IconSize::IconSize { icon_size } => SvgStyle {
235 icon_width: icon_size,
236 icon_height: icon_size,
237 color,
238 },
239 IconSize::Dimensions { width, height } => SvgStyle {
240 icon_width: width,
241 icon_height: height,
242 color,
243 },
244 };
245
246 Ok(result)
247 }
248 }
249
250 pub struct Svg<S> {
251 path: Cow<'static, str>,
252 style: S,
253 }
254
255 impl Svg<()> {
256 pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
257 Self {
258 path: path.into(),
259 style: (),
260 }
261 }
262 }
263
264 impl StyleableComponent for Svg<()> {
265 type Style = SvgStyle;
266
267 type Output = Svg<SvgStyle>;
268
269 fn with_style(self, style: Self::Style) -> Self::Output {
270 Svg {
271 path: self.path,
272 style,
273 }
274 }
275 }
276
277 impl GeneralComponent for Svg<SvgStyle> {
278 fn render<V: gpui::View>(
279 self,
280 _: &mut V,
281 _: &mut gpui::ViewContext<V>,
282 ) -> gpui::AnyElement<V> {
283 gpui::elements::Svg::new(self.path)
284 .with_color(self.style.color)
285 .constrained()
286 .with_width(self.style.icon_width)
287 .with_height(self.style.icon_height)
288 .into_any()
289 }
290 }
291}
292
293pub mod label {
294 use std::borrow::Cow;
295
296 use gpui::{
297 elements::{GeneralComponent, LabelStyle, StyleableComponent},
298 Element,
299 };
300
301 pub struct Label<S> {
302 text: Cow<'static, str>,
303 style: S,
304 }
305
306 impl Label<()> {
307 pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
308 Self {
309 text: text.into(),
310 style: (),
311 }
312 }
313 }
314
315 impl StyleableComponent for Label<()> {
316 type Style = LabelStyle;
317
318 type Output = Label<LabelStyle>;
319
320 fn with_style(self, style: Self::Style) -> Self::Output {
321 Label {
322 text: self.text,
323 style,
324 }
325 }
326 }
327
328 impl GeneralComponent for Label<LabelStyle> {
329 fn render<V: gpui::View>(
330 self,
331 _: &mut V,
332 _: &mut gpui::ViewContext<V>,
333 ) -> gpui::AnyElement<V> {
334 gpui::elements::Label::new(self.text, self.style).into_any()
335 }
336 }
337}