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