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 cx.window()
179 .dispatch_action(cx.view_id(), action.as_ref(), cx);
180 }
181 })
182 .with_cursor_style(CursorStyle::PointingHand)
183 .with_dynamic_tooltip(
184 self.tag,
185 0,
186 self.tooltip,
187 Some(self.action),
188 self.tooltip_style,
189 cx,
190 )
191 .into_any()
192 }
193 }
194}
195
196pub mod svg {
197 use std::borrow::Cow;
198
199 use gpui::{
200 elements::{GeneralComponent, StyleableComponent},
201 Element,
202 };
203 use schemars::JsonSchema;
204 use serde::Deserialize;
205
206 #[derive(Clone, Default, JsonSchema)]
207 pub struct SvgStyle {
208 icon_width: f32,
209 icon_height: f32,
210 color: gpui::color::Color,
211 }
212
213 impl<'de> Deserialize<'de> for SvgStyle {
214 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
215 where
216 D: serde::Deserializer<'de>,
217 {
218 #[derive(Deserialize)]
219 #[serde(untagged)]
220 pub enum IconSize {
221 IconSize { icon_size: f32 },
222 Dimensions { width: f32, height: f32 },
223 }
224
225 #[derive(Deserialize)]
226 struct SvgStyleHelper {
227 #[serde(flatten)]
228 size: IconSize,
229 color: gpui::color::Color,
230 }
231
232 let json = SvgStyleHelper::deserialize(deserializer)?;
233 let color = json.color;
234
235 let result = match json.size {
236 IconSize::IconSize { icon_size } => SvgStyle {
237 icon_width: icon_size,
238 icon_height: icon_size,
239 color,
240 },
241 IconSize::Dimensions { width, height } => SvgStyle {
242 icon_width: width,
243 icon_height: height,
244 color,
245 },
246 };
247
248 Ok(result)
249 }
250 }
251
252 pub struct Svg<S> {
253 path: Cow<'static, str>,
254 style: S,
255 }
256
257 impl Svg<()> {
258 pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
259 Self {
260 path: path.into(),
261 style: (),
262 }
263 }
264 }
265
266 impl StyleableComponent for Svg<()> {
267 type Style = SvgStyle;
268
269 type Output = Svg<SvgStyle>;
270
271 fn with_style(self, style: Self::Style) -> Self::Output {
272 Svg {
273 path: self.path,
274 style,
275 }
276 }
277 }
278
279 impl GeneralComponent for Svg<SvgStyle> {
280 fn render<V: gpui::View>(
281 self,
282 _: &mut V,
283 _: &mut gpui::ViewContext<V>,
284 ) -> gpui::AnyElement<V> {
285 gpui::elements::Svg::new(self.path)
286 .with_color(self.style.color)
287 .constrained()
288 .with_width(self.style.icon_width)
289 .with_height(self.style.icon_height)
290 .into_any()
291 }
292 }
293}
294
295pub mod label {
296 use std::borrow::Cow;
297
298 use gpui::{
299 elements::{GeneralComponent, LabelStyle, StyleableComponent},
300 Element,
301 };
302
303 pub struct Label<S> {
304 text: Cow<'static, str>,
305 style: S,
306 }
307
308 impl Label<()> {
309 pub fn new(text: impl Into<Cow<'static, str>>) -> Self {
310 Self {
311 text: text.into(),
312 style: (),
313 }
314 }
315 }
316
317 impl StyleableComponent for Label<()> {
318 type Style = LabelStyle;
319
320 type Output = Label<LabelStyle>;
321
322 fn with_style(self, style: Self::Style) -> Self::Output {
323 Label {
324 text: self.text,
325 style,
326 }
327 }
328 }
329
330 impl GeneralComponent for Label<LabelStyle> {
331 fn render<V: gpui::View>(
332 self,
333 _: &mut V,
334 _: &mut gpui::ViewContext<V>,
335 ) -> gpui::AnyElement<V> {
336 gpui::elements::Label::new(self.text, self.style).into_any()
337 }
338 }
339}