ui.rs

  1use std::borrow::Cow;
  2
  3use gpui::{
  4    color::Color,
  5    elements::{
  6        ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label,
  7        MouseEventHandler, ParentElement, Svg,
  8    },
  9    geometry::vector::{vec2f, Vector2F},
 10    scene::MouseClick,
 11    Action, Element, ElementBox, EventContext, MouseButton, MouseState, RenderContext, View,
 12};
 13use serde::Deserialize;
 14
 15use crate::{ContainedText, Interactive};
 16
 17#[derive(Clone, Deserialize, Default)]
 18pub struct CheckboxStyle {
 19    pub icon: SvgStyle,
 20    pub label: ContainedText,
 21    pub default: ContainerStyle,
 22    pub checked: ContainerStyle,
 23    pub hovered: ContainerStyle,
 24    pub hovered_and_checked: ContainerStyle,
 25}
 26
 27pub fn checkbox<T: 'static, V: View>(
 28    label: &'static str,
 29    style: &CheckboxStyle,
 30    checked: bool,
 31    cx: &mut RenderContext<V>,
 32    change: fn(checked: bool, cx: &mut EventContext) -> (),
 33) -> MouseEventHandler<T> {
 34    let label = Label::new(label, style.label.text.clone())
 35        .contained()
 36        .with_style(style.label.container)
 37        .boxed();
 38
 39    checkbox_with_label(label, style, checked, cx, change)
 40}
 41
 42pub fn checkbox_with_label<T: 'static, V: View>(
 43    label: ElementBox,
 44    style: &CheckboxStyle,
 45    checked: bool,
 46    cx: &mut RenderContext<V>,
 47    change: fn(checked: bool, cx: &mut EventContext) -> (),
 48) -> MouseEventHandler<T> {
 49    MouseEventHandler::<T>::new(0, cx, |state, _| {
 50        let indicator = if checked {
 51            svg(&style.icon)
 52        } else {
 53            Empty::new()
 54                .constrained()
 55                .with_width(style.icon.dimensions.width)
 56                .with_height(style.icon.dimensions.height)
 57        };
 58
 59        Flex::row()
 60            .with_children([
 61                indicator
 62                    .contained()
 63                    .with_style(if checked {
 64                        if state.hovered() {
 65                            style.hovered_and_checked
 66                        } else {
 67                            style.checked
 68                        }
 69                    } else {
 70                        if state.hovered() {
 71                            style.hovered
 72                        } else {
 73                            style.default
 74                        }
 75                    })
 76                    .boxed(),
 77                label,
 78            ])
 79            .align_children_center()
 80            .boxed()
 81    })
 82    .on_click(gpui::MouseButton::Left, move |_, cx| change(!checked, cx))
 83    .with_cursor_style(gpui::CursorStyle::PointingHand)
 84}
 85
 86#[derive(Clone, Deserialize, Default)]
 87pub struct SvgStyle {
 88    pub color: Color,
 89    pub asset: String,
 90    pub dimensions: Dimensions,
 91}
 92
 93#[derive(Clone, Deserialize, Default)]
 94pub struct Dimensions {
 95    pub width: f32,
 96    pub height: f32,
 97}
 98
 99impl Dimensions {
100    pub fn to_vec(&self) -> Vector2F {
101        vec2f(self.width, self.height)
102    }
103}
104
105pub fn svg(style: &SvgStyle) -> ConstrainedBox {
106    Svg::new(style.asset.clone())
107        .with_color(style.color)
108        .constrained()
109        .with_width(style.dimensions.width)
110        .with_height(style.dimensions.height)
111}
112
113#[derive(Clone, Deserialize, Default)]
114pub struct IconStyle {
115    icon: SvgStyle,
116    container: ContainerStyle,
117}
118
119pub fn icon(style: &IconStyle) -> Container {
120    svg(&style.icon).contained().with_style(style.container)
121}
122
123pub fn keystroke_label<V: View>(
124    label_text: &'static str,
125    label_style: &ContainedText,
126    keystroke_style: &ContainedText,
127    action: Box<dyn Action>,
128    cx: &mut RenderContext<V>,
129) -> Container {
130    // FIXME: Put the theme in it's own global so we can
131    // query the keystroke style on our own
132    keystroke_label_for(
133        cx.window_id(),
134        cx.handle().id(),
135        label_text,
136        label_style,
137        keystroke_style,
138        action,
139    )
140}
141
142pub fn keystroke_label_for(
143    window_id: usize,
144    view_id: usize,
145    label_text: &'static str,
146    label_style: &ContainedText,
147    keystroke_style: &ContainedText,
148    action: Box<dyn Action>,
149) -> Container {
150    Flex::row()
151        .with_child(
152            Label::new(label_text, label_style.text.clone())
153                .contained()
154                .boxed(),
155        )
156        .with_child({
157            KeystrokeLabel::new(
158                window_id,
159                view_id,
160                action,
161                keystroke_style.container,
162                keystroke_style.text.clone(),
163            )
164            .flex_float()
165            .boxed()
166        })
167        .contained()
168        .with_style(label_style.container)
169}
170
171pub type ButtonStyle = Interactive<ContainedText>;
172
173pub fn cta_button<L, A, V>(
174    label: L,
175    action: A,
176    max_width: f32,
177    style: &ButtonStyle,
178    cx: &mut RenderContext<V>,
179) -> ElementBox
180where
181    L: Into<Cow<'static, str>>,
182    A: 'static + Action + Clone,
183    V: View,
184{
185    cta_button_with_click(label, max_width, style, cx, move |_, cx| {
186        cx.dispatch_action(action.clone())
187    })
188}
189
190pub fn cta_button_with_click<L, V, F>(
191    label: L,
192    max_width: f32,
193    style: &ButtonStyle,
194    cx: &mut RenderContext<V>,
195    f: F,
196) -> ElementBox
197where
198    L: Into<Cow<'static, str>>,
199    V: View,
200    F: Fn(MouseClick, &mut EventContext) + 'static,
201{
202    MouseEventHandler::<F>::new(0, cx, |state, _| {
203        let style = style.style_for(state, false);
204        Label::new(label, style.text.to_owned())
205            .aligned()
206            .contained()
207            .with_style(style.container)
208            .constrained()
209            .with_max_width(max_width)
210            .boxed()
211    })
212    .on_click(MouseButton::Left, f)
213    .with_cursor_style(gpui::CursorStyle::PointingHand)
214    .boxed()
215}