481 lines · 13.8 KB · Rust
RawDownload
use gpui::{elements::SafeStylable, Action}; use crate::{Interactive, Toggleable}; use self::{action_button::ButtonStyle, disclosure::Disclosable, svg::SvgStyle, toggle::Toggle}; pub type IconButtonStyle = Interactive<ButtonStyle<SvgStyle>>;pub type ToggleIconButtonStyle = Toggleable<IconButtonStyle>; pub trait ComponentExt<C: SafeStylable> {    fn toggleable(self, active: bool) -> Toggle<C, ()>;    fn disclosable(self, disclosed: Option<bool>, action: Box<dyn Action>) -> Disclosable<C, ()>;} impl<C: SafeStylable> ComponentExt<C> for C {    fn toggleable(self, active: bool) -> Toggle<C, ()> {        Toggle::new(self, active)    }     /// Some(True) => disclosed => content is visible    /// Some(false) => closed => content is hidden    /// None => No disclosure button, but reserve disclosure spacing    fn disclosable(self, disclosed: Option<bool>, action: Box<dyn Action>) -> Disclosable<C, ()> {        Disclosable::new(disclosed, self, action)    }} pub mod disclosure {     use gpui::{        elements::{Component, ContainerStyle, Empty, Flex, ParentElement, SafeStylable},        Action, Element,    };    use schemars::JsonSchema;    use serde_derive::Deserialize;     use super::{action_button::Button, svg::Svg, IconButtonStyle};     #[derive(Clone, Default, Deserialize, JsonSchema)]    pub struct DisclosureStyle<S> {        pub button: IconButtonStyle,        #[serde(flatten)]        pub container: ContainerStyle,        pub spacing: f32,        #[serde(flatten)]        content: S,    }     impl<S> DisclosureStyle<S> {        pub fn button_space(&self) -> f32 {            self.spacing + self.button.button_width.unwrap()        }    }     pub struct Disclosable<C, S> {        disclosed: Option<bool>,        action: Box<dyn Action>,        id: usize,        content: C,        style: S,    }     impl Disclosable<(), ()> {        pub fn new<C>(            disclosed: Option<bool>,            content: C,            action: Box<dyn Action>,        ) -> Disclosable<C, ()> {            Disclosable {                disclosed,                content,                action,                id: 0,                style: (),            }        }    }     impl<C> Disclosable<C, ()> {        pub fn with_id(mut self, id: usize) -> Disclosable<C, ()> {            self.id = id;            self        }    }     impl<C: SafeStylable> SafeStylable for Disclosable<C, ()> {        type Style = DisclosureStyle<C::Style>;         type Output = Disclosable<C, Self::Style>;         fn with_style(self, style: Self::Style) -> Self::Output {            Disclosable {                disclosed: self.disclosed,                action: self.action,                content: self.content,                id: self.id,                style,            }        }    }     impl<C: SafeStylable> Component for Disclosable<C, DisclosureStyle<C::Style>> {        fn render<V: 'static>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {            Flex::row()                .with_spacing(self.style.spacing)                .with_child(if let Some(disclosed) = self.disclosed {                    Button::dynamic_action(self.action)                        .with_id(self.id)                        .with_contents(Svg::new(if disclosed {                            "icons/file_icons/chevron_down.svg"                        } else {                            "icons/file_icons/chevron_right.svg"                        }))                        .with_style(self.style.button)                        .element()                        .into_any()                } else {                    Empty::new()                        .into_any()                        .constrained()                        // TODO: Why is this optional at all?                        .with_width(self.style.button.button_width.unwrap())                        .into_any()                })                .with_child(                    self.content                        .with_style(self.style.content)                        .render(cx)                        .flex(1., true),                )                .align_children_center()                .contained()                .with_style(self.style.container)                .into_any()        }    }} pub mod toggle {    use gpui::elements::{Component, SafeStylable};     use crate::Toggleable;     pub struct Toggle<C, S> {        style: S,        active: bool,        component: C,    }     impl<C: SafeStylable> Toggle<C, ()> {        pub fn new(component: C, active: bool) -> Self {            Toggle {                active,                component,                style: (),            }        }    }     impl<C: SafeStylable> SafeStylable for Toggle<C, ()> {        type Style = Toggleable<C::Style>;         type Output = Toggle<C, Self::Style>;         fn with_style(self, style: Self::Style) -> Self::Output {            Toggle {                active: self.active,                component: self.component,                style,            }        }    }     impl<C: SafeStylable> Component for Toggle<C, Toggleable<C::Style>> {        fn render<V: 'static>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {            self.component                .with_style(self.style.in_state(self.active).clone())                .render(cx)        }    }} pub mod action_button {    use std::borrow::Cow;     use gpui::{        elements::{Component, ContainerStyle, MouseEventHandler, SafeStylable, TooltipStyle},        platform::{CursorStyle, MouseButton},        Action, Element, TypeTag,    };    use schemars::JsonSchema;    use serde_derive::Deserialize;     use crate::Interactive;     #[derive(Clone, Deserialize, Default, JsonSchema)]    pub struct ButtonStyle<C> {        #[serde(flatten)]        pub container: ContainerStyle,        // TODO: These are incorrect for the intended usage of the buttons.        // The size should be constant, but putting them here duplicates them        // across the states the buttons can be in        pub button_width: Option<f32>,        pub button_height: Option<f32>,        #[serde(flatten)]        contents: C,    }     pub struct Button<C, S> {        action: Box<dyn Action>,        tooltip: Option<(Cow<'static, str>, TooltipStyle)>,        tag: TypeTag,        id: usize,        contents: C,        style: Interactive<S>,    }     impl Button<(), ()> {        pub fn dynamic_action(action: Box<dyn Action>) -> Button<(), ()> {            Self {                contents: (),                tag: action.type_tag(),                action,                style: Interactive::new_blank(),                tooltip: None,                id: 0,            }        }         pub fn action<A: Action + Clone>(action: A) -> Self {            Self::dynamic_action(Box::new(action))        }         pub fn with_tooltip(            mut self,            tooltip: impl Into<Cow<'static, str>>,            tooltip_style: TooltipStyle,        ) -> Self {            self.tooltip = Some((tooltip.into(), tooltip_style));            self        }         pub fn with_id(mut self, id: usize) -> Self {            self.id = id;            self        }         pub fn with_contents<C: SafeStylable>(self, contents: C) -> Button<C, ()> {            Button {                action: self.action,                tag: self.tag,                style: self.style,                tooltip: self.tooltip,                id: self.id,                contents,            }        }    }     impl<C: SafeStylable> SafeStylable for Button<C, ()> {        type Style = Interactive<ButtonStyle<C::Style>>;        type Output = Button<C, ButtonStyle<C::Style>>;         fn with_style(self, style: Self::Style) -> Self::Output {            Button {                action: self.action,                tag: self.tag,                contents: self.contents,                tooltip: self.tooltip,                id: self.id,                style,            }        }    }     impl<C: SafeStylable> Component for Button<C, ButtonStyle<C::Style>> {        fn render<V: 'static>(self, cx: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {            let mut button = MouseEventHandler::new_dynamic(self.tag, self.id, cx, |state, cx| {                let style = self.style.style_for(state);                let mut contents = self                    .contents                    .with_style(style.contents.to_owned())                    .render(cx)                    .contained()                    .with_style(style.container)                    .constrained();                 if let Some(height) = style.button_height {                    contents = contents.with_height(height);                }                 if let Some(width) = style.button_width {                    contents = contents.with_width(width);                }                 contents.into_any()            })            .on_click(MouseButton::Left, {                let action = self.action.boxed_clone();                move |_, _, cx| {                    let window = cx.window();                    let view = cx.view_id();                    let action = action.boxed_clone();                    cx.spawn(|_, mut cx| async move {                        window.dispatch_action(view, action.as_ref(), &mut cx)                    })                    .detach();                }            })            .with_cursor_style(CursorStyle::PointingHand)            .into_any();             if let Some((tooltip, style)) = self.tooltip {                button = button                    .with_dynamic_tooltip(self.tag, 0, tooltip, Some(self.action), style, cx)                    .into_any()            }             button        }    }} pub mod svg {    use std::borrow::Cow;     use gpui::{        elements::{Component, Empty, SafeStylable},        Element,    };    use schemars::JsonSchema;    use serde::Deserialize;     #[derive(Clone, Default, JsonSchema)]    pub struct SvgStyle {        icon_width: f32,        icon_height: f32,        color: gpui::color::Color,    }     impl<'de> Deserialize<'de> for SvgStyle {        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>        where            D: serde::Deserializer<'de>,        {            #[derive(Deserialize)]            #[serde(untagged)]            pub enum IconSize {                IconSize { icon_size: f32 },                Dimensions { width: f32, height: f32 },                IconDimensions { icon_width: f32, icon_height: f32 },            }             #[derive(Deserialize)]            struct SvgStyleHelper {                #[serde(flatten)]                size: IconSize,                color: gpui::color::Color,            }             let json = SvgStyleHelper::deserialize(deserializer)?;            let color = json.color;             let result = match json.size {                IconSize::IconSize { icon_size } => SvgStyle {                    icon_width: icon_size,                    icon_height: icon_size,                    color,                },                IconSize::Dimensions { width, height } => SvgStyle {                    icon_width: width,                    icon_height: height,                    color,                },                IconSize::IconDimensions {                    icon_width,                    icon_height,                } => SvgStyle {                    icon_width,                    icon_height,                    color,                },            };             Ok(result)        }    }     pub struct Svg<S> {        path: Option<Cow<'static, str>>,        style: S,    }     impl Svg<()> {        pub fn new(path: impl Into<Cow<'static, str>>) -> Self {            Self {                path: Some(path.into()),                style: (),            }        }         pub fn optional(path: Option<impl Into<Cow<'static, str>>>) -> Self {            Self {                path: path.map(Into::into),                style: (),            }        }    }     impl SafeStylable for Svg<()> {        type Style = SvgStyle;         type Output = Svg<SvgStyle>;         fn with_style(self, style: Self::Style) -> Self::Output {            Svg {                path: self.path,                style,            }        }    }     impl Component for Svg<SvgStyle> {        fn render<V: 'static>(self, _: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {            if let Some(path) = self.path {                gpui::elements::Svg::new(path)                    .with_color(self.style.color)                    .constrained()            } else {                Empty::new().constrained()            }            .constrained()            .with_width(self.style.icon_width)            .with_height(self.style.icon_height)            .into_any()        }    }} pub mod label {    use std::borrow::Cow;     use gpui::{        elements::{Component, LabelStyle, SafeStylable},        fonts::TextStyle,        Element,    };     pub struct Label<S> {        text: Cow<'static, str>,        style: S,    }     impl Label<()> {        pub fn new(text: impl Into<Cow<'static, str>>) -> Self {            Self {                text: text.into(),                style: (),            }        }    }     impl SafeStylable for Label<()> {        type Style = TextStyle;         type Output = Label<LabelStyle>;         fn with_style(self, style: Self::Style) -> Self::Output {            Label {                text: self.text,                style: style.into(),            }        }    }     impl Component for Label<LabelStyle> {        fn render<V: 'static>(self, _: &mut gpui::ViewContext<V>) -> gpui::AnyElement<V> {            gpui::elements::Label::new(self.text, self.style).into_any()        }    }}