2023-03-27 21:25:11 +00:00
|
|
|
use std::borrow::Cow;
|
|
|
|
|
2023-03-09 01:56:39 +00:00
|
|
|
use gpui::{
|
|
|
|
color::Color,
|
|
|
|
elements::{
|
2023-05-22 22:51:31 +00:00
|
|
|
ConstrainedBox, Container, ContainerStyle, Empty, Flex, KeystrokeLabel, Label,
|
2023-03-28 06:16:30 +00:00
|
|
|
MouseEventHandler, ParentElement, Stack, Svg,
|
2023-03-09 01:56:39 +00:00
|
|
|
},
|
2023-03-28 06:16:30 +00:00
|
|
|
fonts::TextStyle,
|
2023-03-27 21:25:11 +00:00
|
|
|
geometry::vector::{vec2f, Vector2F},
|
2023-04-07 17:41:39 +00:00
|
|
|
platform,
|
|
|
|
platform::MouseButton,
|
2023-03-27 21:25:11 +00:00
|
|
|
scene::MouseClick,
|
2023-05-22 22:51:31 +00:00
|
|
|
Action, Element, EventContext, MouseState, View, ViewContext,
|
2023-03-09 01:56:39 +00:00
|
|
|
};
|
|
|
|
use serde::Deserialize;
|
|
|
|
|
2023-05-22 22:51:31 +00:00
|
|
|
use crate::{ContainedText, Interactive};
|
2023-03-09 01:56:39 +00:00
|
|
|
|
|
|
|
#[derive(Clone, Deserialize, Default)]
|
|
|
|
pub struct CheckboxStyle {
|
2023-03-27 21:25:11 +00:00
|
|
|
pub icon: SvgStyle,
|
2023-03-09 01:56:39 +00:00
|
|
|
pub label: ContainedText,
|
|
|
|
pub default: ContainerStyle,
|
|
|
|
pub checked: ContainerStyle,
|
|
|
|
pub hovered: ContainerStyle,
|
|
|
|
pub hovered_and_checked: ContainerStyle,
|
|
|
|
}
|
|
|
|
|
2023-04-21 23:46:58 +00:00
|
|
|
pub fn checkbox<Tag, V, F>(
|
2023-03-09 01:56:39 +00:00
|
|
|
label: &'static str,
|
|
|
|
style: &CheckboxStyle,
|
|
|
|
checked: bool,
|
2023-04-21 23:46:58 +00:00
|
|
|
id: usize,
|
2023-04-12 00:21:56 +00:00
|
|
|
cx: &mut ViewContext<V>,
|
2023-04-21 23:46:58 +00:00
|
|
|
change: F,
|
|
|
|
) -> MouseEventHandler<Tag, V>
|
|
|
|
where
|
|
|
|
Tag: 'static,
|
|
|
|
V: View,
|
|
|
|
F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
|
|
|
|
{
|
2023-03-09 02:47:52 +00:00
|
|
|
let label = Label::new(label, style.label.text.clone())
|
|
|
|
.contained()
|
2023-04-21 16:36:21 +00:00
|
|
|
.with_style(style.label.container);
|
2023-04-21 23:46:58 +00:00
|
|
|
checkbox_with_label(label, style, checked, id, cx, change)
|
2023-03-09 02:47:52 +00:00
|
|
|
}
|
|
|
|
|
2023-04-21 23:46:58 +00:00
|
|
|
pub fn checkbox_with_label<Tag, D, V, F>(
|
2023-04-21 16:36:21 +00:00
|
|
|
label: D,
|
2023-03-09 02:47:52 +00:00
|
|
|
style: &CheckboxStyle,
|
|
|
|
checked: bool,
|
2023-04-21 23:46:58 +00:00
|
|
|
id: usize,
|
2023-04-12 00:21:56 +00:00
|
|
|
cx: &mut ViewContext<V>,
|
2023-04-21 23:46:58 +00:00
|
|
|
change: F,
|
|
|
|
) -> MouseEventHandler<Tag, V>
|
|
|
|
where
|
|
|
|
Tag: 'static,
|
|
|
|
D: Element<V>,
|
|
|
|
V: View,
|
|
|
|
F: 'static + Fn(&mut V, bool, &mut EventContext<V>),
|
|
|
|
{
|
|
|
|
MouseEventHandler::new(id, cx, |state, _| {
|
2023-03-09 01:56:39 +00:00
|
|
|
let indicator = if checked {
|
2023-03-27 21:25:11 +00:00
|
|
|
svg(&style.icon)
|
2023-03-09 01:56:39 +00:00
|
|
|
} else {
|
|
|
|
Empty::new()
|
|
|
|
.constrained()
|
|
|
|
.with_width(style.icon.dimensions.width)
|
|
|
|
.with_height(style.icon.dimensions.height)
|
|
|
|
};
|
|
|
|
|
|
|
|
Flex::row()
|
2023-04-21 16:36:21 +00:00
|
|
|
.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)
|
2023-03-09 01:56:39 +00:00
|
|
|
.align_children_center()
|
|
|
|
})
|
2023-04-21 23:46:58 +00:00
|
|
|
.on_click(platform::MouseButton::Left, move |_, view, cx| {
|
|
|
|
change(view, !checked, cx)
|
2023-04-07 17:41:39 +00:00
|
|
|
})
|
|
|
|
.with_cursor_style(platform::CursorStyle::PointingHand)
|
2023-03-09 01:56:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Deserialize, Default)]
|
2023-03-27 21:25:11 +00:00
|
|
|
pub struct SvgStyle {
|
2023-03-09 01:56:39 +00:00
|
|
|
pub color: Color,
|
2023-03-27 21:25:11 +00:00
|
|
|
pub asset: String,
|
2023-03-09 01:56:39 +00:00
|
|
|
pub dimensions: Dimensions,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Deserialize, Default)]
|
|
|
|
pub struct Dimensions {
|
|
|
|
pub width: f32,
|
|
|
|
pub height: f32,
|
|
|
|
}
|
|
|
|
|
2023-03-27 21:25:11 +00:00
|
|
|
impl Dimensions {
|
|
|
|
pub fn to_vec(&self) -> Vector2F {
|
|
|
|
vec2f(self.width, self.height)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-12 00:21:56 +00:00
|
|
|
pub fn svg<V: View>(style: &SvgStyle) -> ConstrainedBox<V> {
|
2023-03-27 21:25:11 +00:00
|
|
|
Svg::new(style.asset.clone())
|
2023-03-09 01:56:39 +00:00
|
|
|
.with_color(style.color)
|
|
|
|
.constrained()
|
|
|
|
.with_width(style.dimensions.width)
|
|
|
|
.with_height(style.dimensions.height)
|
|
|
|
}
|
|
|
|
|
2023-03-27 21:25:11 +00:00
|
|
|
#[derive(Clone, Deserialize, Default)]
|
|
|
|
pub struct IconStyle {
|
|
|
|
icon: SvgStyle,
|
|
|
|
container: ContainerStyle,
|
|
|
|
}
|
|
|
|
|
2023-04-12 00:21:56 +00:00
|
|
|
pub fn icon<V: View>(style: &IconStyle) -> Container<V> {
|
2023-03-27 21:25:11 +00:00
|
|
|
svg(&style.icon).contained().with_style(style.container)
|
|
|
|
}
|
|
|
|
|
2023-03-09 01:56:39 +00:00
|
|
|
pub fn keystroke_label<V: View>(
|
|
|
|
label_text: &'static str,
|
|
|
|
label_style: &ContainedText,
|
|
|
|
keystroke_style: &ContainedText,
|
|
|
|
action: Box<dyn Action>,
|
2023-04-12 00:21:56 +00:00
|
|
|
cx: &mut ViewContext<V>,
|
|
|
|
) -> Container<V> {
|
2023-03-09 01:56:39 +00:00
|
|
|
// FIXME: Put the theme in it's own global so we can
|
|
|
|
// query the keystroke style on our own
|
|
|
|
Flex::row()
|
2023-04-21 16:36:21 +00:00
|
|
|
.with_child(Label::new(label_text, label_style.text.clone()).contained())
|
2023-03-09 01:56:39 +00:00
|
|
|
.with_child(
|
|
|
|
KeystrokeLabel::new(
|
2023-04-27 08:55:14 +00:00
|
|
|
cx.view_id(),
|
2023-03-09 01:56:39 +00:00
|
|
|
action,
|
|
|
|
keystroke_style.container,
|
|
|
|
keystroke_style.text.clone(),
|
|
|
|
)
|
2023-04-21 16:36:21 +00:00
|
|
|
.flex_float(),
|
|
|
|
)
|
2023-03-09 01:56:39 +00:00
|
|
|
.contained()
|
|
|
|
.with_style(label_style.container)
|
|
|
|
}
|
2023-03-27 21:25:11 +00:00
|
|
|
|
|
|
|
pub type ButtonStyle = Interactive<ContainedText>;
|
|
|
|
|
2023-05-01 13:48:41 +00:00
|
|
|
pub fn cta_button<Tag, L, V, F>(
|
2023-03-27 21:25:11 +00:00
|
|
|
label: L,
|
|
|
|
max_width: f32,
|
|
|
|
style: &ButtonStyle,
|
2023-04-12 00:21:56 +00:00
|
|
|
cx: &mut ViewContext<V>,
|
2023-03-27 21:25:11 +00:00
|
|
|
f: F,
|
2023-04-12 00:21:56 +00:00
|
|
|
) -> MouseEventHandler<Tag, V>
|
2023-03-27 21:25:11 +00:00
|
|
|
where
|
2023-04-12 00:21:56 +00:00
|
|
|
Tag: 'static,
|
2023-03-27 21:25:11 +00:00
|
|
|
L: Into<Cow<'static, str>>,
|
|
|
|
V: View,
|
2023-04-12 00:21:56 +00:00
|
|
|
F: Fn(MouseClick, &mut V, &mut EventContext<V>) + 'static,
|
2023-03-27 21:25:11 +00:00
|
|
|
{
|
2023-04-12 00:21:56 +00:00
|
|
|
MouseEventHandler::<Tag, V>::new(0, cx, |state, _| {
|
2023-03-27 21:25:11 +00:00
|
|
|
let style = style.style_for(state, false);
|
|
|
|
Label::new(label, style.text.to_owned())
|
|
|
|
.aligned()
|
|
|
|
.contained()
|
|
|
|
.with_style(style.container)
|
|
|
|
.constrained()
|
|
|
|
.with_max_width(max_width)
|
|
|
|
})
|
|
|
|
.on_click(MouseButton::Left, f)
|
2023-04-07 17:41:39 +00:00
|
|
|
.with_cursor_style(platform::CursorStyle::PointingHand)
|
2023-03-27 21:25:11 +00:00
|
|
|
}
|
2023-03-28 06:16:30 +00:00
|
|
|
|
|
|
|
#[derive(Clone, Deserialize, Default)]
|
|
|
|
pub struct ModalStyle {
|
|
|
|
close_icon: Interactive<IconStyle>,
|
|
|
|
container: ContainerStyle,
|
|
|
|
titlebar: ContainerStyle,
|
2023-03-29 01:00:09 +00:00
|
|
|
title_text: Interactive<TextStyle>,
|
2023-03-28 06:16:30 +00:00
|
|
|
dimensions: Dimensions,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ModalStyle {
|
|
|
|
pub fn dimensions(&self) -> Vector2F {
|
|
|
|
self.dimensions.to_vec()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-21 16:36:21 +00:00
|
|
|
pub fn modal<Tag, V, I, D, F>(
|
2023-03-28 06:16:30 +00:00
|
|
|
title: I,
|
|
|
|
style: &ModalStyle,
|
2023-04-12 00:21:56 +00:00
|
|
|
cx: &mut ViewContext<V>,
|
2023-03-28 06:16:30 +00:00
|
|
|
build_modal: F,
|
2023-04-21 19:04:03 +00:00
|
|
|
) -> impl Element<V>
|
2023-03-28 06:16:30 +00:00
|
|
|
where
|
2023-04-12 00:21:56 +00:00
|
|
|
Tag: 'static,
|
2023-03-28 06:16:30 +00:00
|
|
|
V: View,
|
|
|
|
I: Into<Cow<'static, str>>,
|
2023-04-21 19:04:03 +00:00
|
|
|
D: Element<V>,
|
2023-04-21 16:36:21 +00:00
|
|
|
F: FnOnce(&mut gpui::ViewContext<V>) -> D,
|
2023-03-28 06:16:30 +00:00
|
|
|
{
|
2023-03-30 23:50:33 +00:00
|
|
|
const TITLEBAR_HEIGHT: f32 = 28.;
|
|
|
|
// let active = cx.window_is_active(cx.window_id());
|
2023-03-29 01:00:09 +00:00
|
|
|
|
2023-03-28 06:16:30 +00:00
|
|
|
Flex::column()
|
|
|
|
.with_child(
|
|
|
|
Stack::new()
|
2023-04-21 16:36:21 +00:00
|
|
|
.with_child(Label::new(
|
|
|
|
title,
|
|
|
|
style
|
|
|
|
.title_text
|
|
|
|
.style_for(&mut MouseState::default(), false)
|
|
|
|
.clone(),
|
|
|
|
))
|
|
|
|
.with_child(
|
2023-03-28 06:16:30 +00:00
|
|
|
// FIXME: Get a better tag type
|
2023-04-12 00:21:56 +00:00
|
|
|
MouseEventHandler::<Tag, V>::new(999999, cx, |state, _cx| {
|
2023-03-30 23:50:33 +00:00
|
|
|
let style = style.close_icon.style_for(state, false);
|
2023-04-21 16:36:21 +00:00
|
|
|
icon(style)
|
2023-03-28 06:16:30 +00:00
|
|
|
})
|
2023-04-12 00:21:56 +00:00
|
|
|
.on_click(platform::MouseButton::Left, move |_, _, cx| {
|
2023-04-21 14:39:53 +00:00
|
|
|
cx.remove_window();
|
2023-03-28 06:16:30 +00:00
|
|
|
})
|
2023-04-07 17:41:39 +00:00
|
|
|
.with_cursor_style(platform::CursorStyle::PointingHand)
|
2023-03-28 06:16:30 +00:00
|
|
|
.aligned()
|
2023-04-21 16:36:21 +00:00
|
|
|
.right(),
|
|
|
|
)
|
2023-03-28 06:16:30 +00:00
|
|
|
.contained()
|
|
|
|
.with_style(style.titlebar)
|
2023-03-30 23:50:33 +00:00
|
|
|
.constrained()
|
2023-04-21 16:36:21 +00:00
|
|
|
.with_height(TITLEBAR_HEIGHT),
|
2023-03-30 23:50:33 +00:00
|
|
|
)
|
|
|
|
.with_child(
|
2023-04-21 16:36:21 +00:00
|
|
|
build_modal(cx)
|
|
|
|
.contained()
|
2023-03-30 23:50:33 +00:00
|
|
|
.with_style(style.container)
|
|
|
|
.constrained()
|
|
|
|
.with_width(style.dimensions().x())
|
2023-04-21 16:36:21 +00:00
|
|
|
.with_height(style.dimensions().y() - TITLEBAR_HEIGHT),
|
2023-03-28 06:16:30 +00:00
|
|
|
)
|
|
|
|
.constrained()
|
|
|
|
.with_height(style.dimensions().y())
|
|
|
|
}
|