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: Cow<'static, str>,
85 tooltip_style: TooltipStyle,
86 tag: TypeTag,
87 contents: C,
88 style: Interactive<S>,
89 }
90
91 #[derive(Clone, Deserialize, Default, JsonSchema)]
92 pub struct ButtonStyle<C> {
93 #[serde(flatten)]
94 container: ContainerStyle,
95 button_width: Option<f32>,
96 button_height: Option<f32>,
97 #[serde(flatten)]
98 contents: C,
99 }
100
101 impl ActionButton<(), ()> {
102 pub fn new_dynamic(
103 action: Box<dyn Action>,
104 tooltip: impl Into<Cow<'static, str>>,
105 tooltip_style: TooltipStyle,
106 ) -> Self {
107 Self {
108 contents: (),
109 tag: action.type_tag(),
110 style: Interactive::new_blank(),
111 tooltip: tooltip.into(),
112 tooltip_style,
113 action,
114 }
115 }
116
117 pub fn new<A: Action + Clone>(
118 action: A,
119 tooltip: impl Into<Cow<'static, str>>,
120 tooltip_style: TooltipStyle,
121 ) -> Self {
122 Self::new_dynamic(Box::new(action), tooltip, tooltip_style)
123 }
124
125 pub fn with_contents<C: StyleableComponent>(self, contents: C) -> ActionButton<C, ()> {
126 ActionButton {
127 action: self.action,
128 tag: self.tag,
129 style: self.style,
130 tooltip: self.tooltip,
131 tooltip_style: self.tooltip_style,
132 contents,
133 }
134 }
135 }
136
137 impl<C: StyleableComponent> StyleableComponent for ActionButton<C, ()> {
138 type Style = Interactive<ButtonStyle<C::Style>>;
139 type Output = ActionButton<C, ButtonStyle<C::Style>>;
140
141 fn with_style(self, style: Self::Style) -> Self::Output {
142 ActionButton {
143 action: self.action,
144 tag: self.tag,
145 contents: self.contents,
146 tooltip: self.tooltip,
147 tooltip_style: self.tooltip_style,
148 style,
149 }
150 }
151 }
152
153 impl<C: StyleableComponent> GeneralComponent for ActionButton<C, ButtonStyle<C::Style>> {
154 fn render<V: View>(self, v: &mut V, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {
155 MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| {
156 let style = self.style.style_for(state);
157 let mut contents = self
158 .contents
159 .with_style(style.contents.to_owned())
160 .render(v, cx)
161 .contained()
162 .with_style(style.container)
163 .constrained();
164
165 if let Some(height) = style.button_height {
166 contents = contents.with_height(height);
167 }
168
169 if let Some(width) = style.button_width {
170 contents = contents.with_width(width);
171 }
172
173 contents.into_any()
174 })
175 .on_click(MouseButton::Left, {
176 let action = self.action.boxed_clone();
177 move |_, _, cx| {
178 let window = cx.window();
179 let view = cx.view_id();
180 let action = action.boxed_clone();
181 cx.spawn(|_, mut cx| async move {
182 window.dispatch_action(view, action.as_ref(), &mut cx)
183 })
184 .detach();
185 }
186 })
187 .with_cursor_style(CursorStyle::PointingHand)
188 .with_dynamic_tooltip(
189 self.tag,
190 0,
191 self.tooltip,
192 Some(self.action),
193 self.tooltip_style,
194 cx,
195 )
196 .into_any()
197 }
198 }
199}
200
201pub mod svg {
202 use std::borrow::Cow;
203
204 use gpui::{
205 elements::{GeneralComponent, StyleableComponent},
206 Element,
207 };
208 use schemars::JsonSchema;
209 use serde::Deserialize;
210
211 #[derive(Clone, Default, JsonSchema)]
212 pub struct SvgStyle {
213 icon_width: f32,
214 icon_height: f32,
215 color: gpui::color::Color,
216 }
217
218 impl<'de> Deserialize<'de> for SvgStyle {
219 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
220 where
221 D: serde::Deserializer<'de>,
222 {
223 #[derive(Deserialize)]
224 #[serde(untagged)]
225 pub enum IconSize {
226 IconSize { icon_size: f32 },
227 Dimensions { width: f32, height: f32 },
228 }
229
230 #[derive(Deserialize)]
231 struct SvgStyleHelper {
232 #[serde(flatten)]
233 size: IconSize,
234 color: gpui::color::Color,
235 }
236
237 let json = SvgStyleHelper::deserialize(deserializer)?;
238 let color = json.color;
239
240 let result = match json.size {
241 IconSize::IconSize { icon_size } => SvgStyle {
242 icon_width: icon_size,
243 icon_height: icon_size,
244 color,
245 },
246 IconSize::Dimensions { width, height } => SvgStyle {
247 icon_width: width,
248 icon_height: height,
249 color,
250 },
251 };
252
253 Ok(result)
254 }
255 }
256
257 pub struct Svg<S> {
258 path: Cow<'static, str>,
259 style: S,
260 }
261
262 impl Svg<()> {
263 pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
264 Self {
265 path: path.into(),
266 style: (),
267 }
268 }
269 }
270
271 impl StyleableComponent for Svg<()> {
272 type Style = SvgStyle;
273
274 type Output = Svg<SvgStyle>;
275
276 fn with_style(self, style: Self::Style) -> Self::Output {
277 Svg {
278 path: self.path,
279 style,
280 }
281 }
282 }
283
284 impl GeneralComponent for Svg<SvgStyle> {
285 fn render<V: gpui::View>(
286 self,
287 _: &mut V,
288 _: &mut gpui::ViewContext<V>,
289 ) -> gpui::AnyElement<V> {
290 gpui::elements::Svg::new(self.path)
291 .with_color(self.style.color)
292 .constrained()
293 .with_width(self.style.icon_width)
294 .with_height(self.style.icon_height)
295 .into_any()
296 }
297 }
298}
299
300pub mod label {
301 use std::borrow::Cow;
302
303 use gpui::{
304 elements::{GeneralComponent, LabelStyle, StyleableComponent},
305 Element,
306 };
307
308 pub struct Label<S> {
309 text: Cow<'static, str>,
310 style: S,
311 }
312
313 impl Label<()> {
314 pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
315 Self {
316 text: text.into(),
317 style: (),
318 }
319 }
320 }
321
322 impl StyleableComponent for Label<()> {
323 type Style = LabelStyle;
324
325 type Output = Label<LabelStyle>;
326
327 fn with_style(self, style: Self::Style) -> Self::Output {
328 Label {
329 text: self.text,
330 style,
331 }
332 }
333 }
334
335 impl GeneralComponent for Label<LabelStyle> {
336 fn render<V: gpui::View>(
337 self,
338 _: &mut V,
339 _: &mut gpui::ViewContext<V>,
340 ) -> gpui::AnyElement<V> {
341 gpui::elements::Label::new(self.text, self.style).into_any()
342 }
343 }
344}