use gpui::elements::StyleableComponent; use crate::{Interactive, Toggleable}; use self::{action_button::ButtonStyle, svg::SvgStyle, toggle::Toggle}; pub type ToggleIconButtonStyle = Toggleable>>; pub trait ComponentExt { fn toggleable(self, active: bool) -> Toggle; } impl ComponentExt for C { fn toggleable(self, active: bool) -> Toggle { Toggle::new(self, active) } } pub mod toggle { use gpui::elements::{GeneralComponent, StyleableComponent}; use crate::Toggleable; pub struct Toggle { style: S, active: bool, component: C, } impl Toggle { pub fn new(component: C, active: bool) -> Self { Toggle { active, component, style: (), } } } impl StyleableComponent for Toggle { type Style = Toggleable; type Output = Toggle; fn with_style(self, style: Self::Style) -> Self::Output { Toggle { active: self.active, component: self.component, style, } } } impl GeneralComponent for Toggle> { fn render( self, v: &mut V, cx: &mut gpui::ViewContext, ) -> gpui::AnyElement { self.component .with_style(self.style.in_state(self.active).clone()) .render(v, cx) } } } pub mod action_button { use std::borrow::Cow; use gpui::{ elements::{ ContainerStyle, GeneralComponent, MouseEventHandler, StyleableComponent, TooltipStyle, }, platform::{CursorStyle, MouseButton}, Action, Element, TypeTag, View, }; use schemars::JsonSchema; use serde_derive::Deserialize; use crate::Interactive; pub struct ActionButton { action: Box, tooltip: Cow<'static, str>, tooltip_style: TooltipStyle, tag: TypeTag, contents: C, style: Interactive, } #[derive(Clone, Deserialize, Default, JsonSchema)] pub struct ButtonStyle { #[serde(flatten)] container: ContainerStyle, button_width: Option, button_height: Option, #[serde(flatten)] contents: C, } impl ActionButton<(), ()> { pub fn new_dynamic( action: Box, tooltip: impl Into>, tooltip_style: TooltipStyle, ) -> Self { Self { contents: (), tag: action.type_tag(), style: Interactive::new_blank(), tooltip: tooltip.into(), tooltip_style, action, } } pub fn new( action: A, tooltip: impl Into>, tooltip_style: TooltipStyle, ) -> Self { Self::new_dynamic(Box::new(action), tooltip, tooltip_style) } pub fn with_contents(self, contents: C) -> ActionButton { ActionButton { action: self.action, tag: self.tag, style: self.style, tooltip: self.tooltip, tooltip_style: self.tooltip_style, contents, } } } impl StyleableComponent for ActionButton { type Style = Interactive>; type Output = ActionButton>; fn with_style(self, style: Self::Style) -> Self::Output { ActionButton { action: self.action, tag: self.tag, contents: self.contents, tooltip: self.tooltip, tooltip_style: self.tooltip_style, style, } } } impl GeneralComponent for ActionButton> { fn render(self, v: &mut V, cx: &mut gpui::ViewContext) -> gpui::AnyElement { MouseEventHandler::new_dynamic(self.tag, 0, cx, |state, cx| { let style = self.style.style_for(state); let mut contents = self .contents .with_style(style.contents.to_owned()) .render(v, 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) .with_dynamic_tooltip( self.tag, 0, self.tooltip, Some(self.action), self.tooltip_style, cx, ) .into_any() } } } pub mod svg { use std::borrow::Cow; use gpui::{ elements::{GeneralComponent, StyleableComponent}, 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(deserializer: D) -> Result where D: serde::Deserializer<'de>, { #[derive(Deserialize)] #[serde(untagged)] pub enum IconSize { IconSize { icon_size: f32 }, Dimensions { width: f32, 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, }, }; Ok(result) } } pub struct Svg { path: Cow<'static, str>, style: S, } impl Svg<()> { pub fn new(path: impl Into>) -> Self { Self { path: path.into(), style: (), } } } impl StyleableComponent for Svg<()> { type Style = SvgStyle; type Output = Svg; fn with_style(self, style: Self::Style) -> Self::Output { Svg { path: self.path, style, } } } impl GeneralComponent for Svg { fn render( self, _: &mut V, _: &mut gpui::ViewContext, ) -> gpui::AnyElement { gpui::elements::Svg::new(self.path) .with_color(self.style.color) .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::{GeneralComponent, LabelStyle, StyleableComponent}, Element, }; pub struct Label { text: Cow<'static, str>, style: S, } impl Label<()> { pub fn new(text: impl Into>) -> Self { Self { text: text.into(), style: (), } } } impl StyleableComponent for Label<()> { type Style = LabelStyle; type Output = Label; fn with_style(self, style: Self::Style) -> Self::Output { Label { text: self.text, style, } } } impl GeneralComponent for Label { fn render( self, _: &mut V, _: &mut gpui::ViewContext, ) -> gpui::AnyElement { gpui::elements::Label::new(self.text, self.style).into_any() } } }