use std::borrow::Cow; use gpui::{ elements::{ ConstrainedBox, Container, ContainerStyle, Dimensions, Empty, Flex, KeystrokeLabel, Label, MouseEventHandler, ParentElement, Stack, Svg, SvgStyle, }, fonts::TextStyle, geometry::vector::Vector2F, platform, platform::MouseButton, scene::MouseClick, Action, Element, EventContext, MouseState, ViewContext, }; use schemars::JsonSchema; use serde::Deserialize; use crate::{ContainedText, Interactive}; #[derive(Clone, Deserialize, Default, JsonSchema)] pub struct CheckboxStyle { pub icon: SvgStyle, pub label: ContainedText, pub default: ContainerStyle, pub checked: ContainerStyle, pub hovered: ContainerStyle, pub hovered_and_checked: ContainerStyle, } pub fn checkbox( label: &'static str, style: &CheckboxStyle, checked: bool, id: usize, cx: &mut ViewContext, change: F, ) -> MouseEventHandler where Tag: 'static, V: 'static, F: 'static + Fn(&mut V, bool, &mut EventContext), { let label = Label::new(label, style.label.text.clone()) .contained() .with_style(style.label.container); checkbox_with_label::(label, style, checked, id, cx, change) } pub fn checkbox_with_label( label: D, style: &CheckboxStyle, checked: bool, id: usize, cx: &mut ViewContext, change: F, ) -> MouseEventHandler where Tag: 'static, D: Element, V: 'static, F: 'static + Fn(&mut V, bool, &mut EventContext), { MouseEventHandler::new::(id, cx, |state, _| { let indicator = if checked { svg(&style.icon) } else { Empty::new() .constrained() .with_width(style.icon.dimensions.width) .with_height(style.icon.dimensions.height) }; Flex::row() .with_child(indicator.contained().with_style(if checked { if state.hovered() { style.hovered_and_checked } else { style.checked } } else { if state.hovered() { style.hovered } else { style.default } })) .with_child(label) .align_children_center() }) .on_click(platform::MouseButton::Left, move |_, view, cx| { change(view, !checked, cx) }) .with_cursor_style(platform::CursorStyle::PointingHand) } pub fn svg(style: &SvgStyle) -> ConstrainedBox { Svg::new(style.asset.clone()) .with_color(style.color) .constrained() .with_width(style.dimensions.width) .with_height(style.dimensions.height) } #[derive(Clone, Deserialize, Default, JsonSchema)] pub struct IconStyle { pub icon: SvgStyle, pub container: ContainerStyle, } impl IconStyle { pub fn width(&self) -> f32 { self.icon.dimensions.width + self.container.padding.left + self.container.padding.right + self.container.margin.left + self.container.margin.right } } pub fn icon(style: &IconStyle) -> Container { svg(&style.icon).contained().with_style(style.container) } pub fn keystroke_label( label_text: &'static str, label_style: &ContainedText, keystroke_style: &ContainedText, action: Box, cx: &mut ViewContext, ) -> Container { // FIXME: Put the theme in it's own global so we can // query the keystroke style on our own Flex::row() .with_child(Label::new(label_text, label_style.text.clone()).contained()) .with_child( KeystrokeLabel::new( cx.view_id(), action, keystroke_style.container, keystroke_style.text.clone(), ) .flex_float(), ) .contained() .with_style(label_style.container) } pub type CopilotCTAButton = Interactive; pub fn cta_button( label: L, max_width: f32, style: &CopilotCTAButton, cx: &mut ViewContext, f: F, ) -> MouseEventHandler where Tag: 'static, L: Into>, V: 'static, F: Fn(MouseClick, &mut V, &mut EventContext) + 'static, { MouseEventHandler::new::(0, cx, |state, _| { let style = style.style_for(state); Label::new(label, style.text.to_owned()) .aligned() .contained() .with_style(style.container) .constrained() .with_max_width(max_width) }) .on_click(MouseButton::Left, f) .with_cursor_style(platform::CursorStyle::PointingHand) } #[derive(Clone, Deserialize, Default, JsonSchema)] pub struct ModalStyle { close_icon: Interactive, container: ContainerStyle, titlebar: ContainerStyle, title_text: Interactive, dimensions: Dimensions, } impl ModalStyle { pub fn dimensions(&self) -> Vector2F { self.dimensions.to_vec() } } pub fn modal( title: I, style: &ModalStyle, cx: &mut ViewContext, build_modal: F, ) -> impl Element where Tag: 'static, I: Into>, D: Element, V: 'static, F: FnOnce(&mut gpui::ViewContext) -> D, { const TITLEBAR_HEIGHT: f32 = 28.; Flex::column() .with_child( Stack::new() .with_child(Label::new( title, style .title_text .style_for(&mut MouseState::default()) .clone(), )) .with_child( // FIXME: Get a better tag type MouseEventHandler::new::(999999, cx, |state, _cx| { let style = style.close_icon.style_for(state); icon(style) }) .on_click(platform::MouseButton::Left, move |_, _, cx| { cx.remove_window(); }) .with_cursor_style(platform::CursorStyle::PointingHand) .aligned() .right(), ) .contained() .with_style(style.titlebar) .constrained() .with_height(TITLEBAR_HEIGHT), ) .with_child( build_modal(cx) .contained() .with_style(style.container) .constrained() .with_width(style.dimensions().x()) .with_height(style.dimensions().y() - TITLEBAR_HEIGHT), ) .constrained() .with_height(style.dimensions().y()) }