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::{
|
|
|
|
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-04-12 18:10:43 +00:00
|
|
|
Action, Drawable, Element, EventContext, MouseState, View, ViewContext,
|
2023-03-09 01:56:39 +00:00
|
|
|
};
|
|
|
|
use serde::Deserialize;
|
|
|
|
|
2023-03-27 21:25:11 +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-12 00:21:56 +00:00
|
|
|
pub fn checkbox<Tag: 'static, V: View>(
|
2023-03-09 01:56:39 +00:00
|
|
|
label: &'static str,
|
|
|
|
style: &CheckboxStyle,
|
|
|
|
checked: bool,
|
2023-04-12 00:21:56 +00:00
|
|
|
cx: &mut ViewContext<V>,
|
|
|
|
change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
|
|
|
|
) -> MouseEventHandler<Tag, V> {
|
2023-03-09 02:47:52 +00:00
|
|
|
let label = Label::new(label, style.label.text.clone())
|
|
|
|
.contained()
|
|
|
|
.with_style(style.label.container)
|
|
|
|
.boxed();
|
|
|
|
|
|
|
|
checkbox_with_label(label, style, checked, cx, change)
|
|
|
|
}
|
|
|
|
|
2023-04-12 00:21:56 +00:00
|
|
|
pub fn checkbox_with_label<Tag: 'static, V: View>(
|
2023-04-12 18:10:43 +00:00
|
|
|
label: Element<V>,
|
2023-03-09 02:47:52 +00:00
|
|
|
style: &CheckboxStyle,
|
|
|
|
checked: bool,
|
2023-04-12 00:21:56 +00:00
|
|
|
cx: &mut ViewContext<V>,
|
|
|
|
change: fn(checked: bool, cx: &mut EventContext<V>) -> (),
|
|
|
|
) -> MouseEventHandler<Tag, V> {
|
|
|
|
MouseEventHandler::new(0, 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()
|
|
|
|
.with_children([
|
|
|
|
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
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.boxed(),
|
2023-03-09 02:47:52 +00:00
|
|
|
label,
|
2023-03-09 01:56:39 +00:00
|
|
|
])
|
|
|
|
.align_children_center()
|
|
|
|
.boxed()
|
|
|
|
})
|
2023-04-12 00:21:56 +00:00
|
|
|
.on_click(platform::MouseButton::Left, move |_, _, cx| {
|
2023-04-07 17:41:39 +00:00
|
|
|
change(!checked, cx)
|
|
|
|
})
|
|
|
|
.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
|
2023-03-09 02:11:11 +00:00
|
|
|
keystroke_label_for(
|
|
|
|
cx.handle().id(),
|
|
|
|
label_text,
|
|
|
|
label_style,
|
|
|
|
keystroke_style,
|
|
|
|
action,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-04-12 00:21:56 +00:00
|
|
|
pub fn keystroke_label_for<V: View>(
|
2023-03-09 02:11:11 +00:00
|
|
|
view_id: usize,
|
|
|
|
label_text: &'static str,
|
|
|
|
label_style: &ContainedText,
|
|
|
|
keystroke_style: &ContainedText,
|
|
|
|
action: Box<dyn Action>,
|
2023-04-12 00:21:56 +00:00
|
|
|
) -> Container<V> {
|
2023-03-09 01:56:39 +00:00
|
|
|
Flex::row()
|
|
|
|
.with_child(
|
|
|
|
Label::new(label_text, label_style.text.clone())
|
|
|
|
.contained()
|
|
|
|
.boxed(),
|
|
|
|
)
|
|
|
|
.with_child({
|
|
|
|
KeystrokeLabel::new(
|
2023-03-09 02:11:11 +00:00
|
|
|
view_id,
|
2023-03-09 01:56:39 +00:00
|
|
|
action,
|
|
|
|
keystroke_style.container,
|
|
|
|
keystroke_style.text.clone(),
|
|
|
|
)
|
|
|
|
.flex_float()
|
|
|
|
.boxed()
|
|
|
|
})
|
|
|
|
.contained()
|
|
|
|
.with_style(label_style.container)
|
|
|
|
}
|
2023-03-27 21:25:11 +00:00
|
|
|
|
|
|
|
pub type ButtonStyle = Interactive<ContainedText>;
|
|
|
|
|
2023-04-12 16:07:17 +00:00
|
|
|
pub fn cta_button<L, A, V>(
|
2023-03-27 21:25:11 +00:00
|
|
|
label: L,
|
|
|
|
action: A,
|
|
|
|
max_width: f32,
|
|
|
|
style: &ButtonStyle,
|
2023-04-12 00:21:56 +00:00
|
|
|
cx: &mut ViewContext<V>,
|
2023-04-12 18:10:43 +00:00
|
|
|
) -> Element<V>
|
2023-03-27 21:25:11 +00:00
|
|
|
where
|
|
|
|
L: Into<Cow<'static, str>>,
|
|
|
|
A: 'static + Action + Clone,
|
|
|
|
V: View,
|
|
|
|
{
|
2023-04-12 16:07:17 +00:00
|
|
|
cta_button_with_click::<A, _, _, _>(label, max_width, style, cx, move |_, _, cx| {
|
2023-03-27 21:25:11 +00:00
|
|
|
cx.dispatch_action(action.clone())
|
|
|
|
})
|
2023-03-30 23:50:33 +00:00
|
|
|
.boxed()
|
2023-03-27 21:25:11 +00:00
|
|
|
}
|
|
|
|
|
2023-04-12 00:21:56 +00:00
|
|
|
pub fn cta_button_with_click<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)
|
|
|
|
.boxed()
|
|
|
|
})
|
|
|
|
.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-12 00:21:56 +00:00
|
|
|
pub fn modal<Tag, V, I, 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-12 18:10:43 +00:00
|
|
|
) -> 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-12 18:10:43 +00:00
|
|
|
F: FnOnce(&mut gpui::ViewContext<V>) -> Element<V>,
|
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()
|
|
|
|
.with_children([
|
2023-03-29 01:00:09 +00:00
|
|
|
Label::new(
|
|
|
|
title,
|
|
|
|
style
|
|
|
|
.title_text
|
2023-03-30 23:50:33 +00:00
|
|
|
.style_for(&mut MouseState::default(), false)
|
2023-03-29 01:00:09 +00:00
|
|
|
.clone(),
|
|
|
|
)
|
|
|
|
.boxed(),
|
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-03-28 06:16:30 +00:00
|
|
|
icon(style).boxed()
|
|
|
|
})
|
2023-04-12 00:21:56 +00:00
|
|
|
.on_click(platform::MouseButton::Left, move |_, _, cx| {
|
2023-03-28 06:16:30 +00:00
|
|
|
let window_id = cx.window_id();
|
|
|
|
cx.remove_window(window_id);
|
|
|
|
})
|
2023-04-07 17:41:39 +00:00
|
|
|
.with_cursor_style(platform::CursorStyle::PointingHand)
|
2023-03-28 06:16:30 +00:00
|
|
|
.aligned()
|
|
|
|
.right()
|
|
|
|
.boxed(),
|
|
|
|
])
|
|
|
|
.contained()
|
|
|
|
.with_style(style.titlebar)
|
2023-03-30 23:50:33 +00:00
|
|
|
.constrained()
|
|
|
|
.with_height(TITLEBAR_HEIGHT)
|
|
|
|
.boxed(),
|
|
|
|
)
|
|
|
|
.with_child(
|
|
|
|
Container::new(build_modal(cx))
|
|
|
|
.with_style(style.container)
|
|
|
|
.constrained()
|
|
|
|
.with_width(style.dimensions().x())
|
|
|
|
.with_height(style.dimensions().y() - TITLEBAR_HEIGHT)
|
2023-03-28 06:16:30 +00:00
|
|
|
.boxed(),
|
|
|
|
)
|
|
|
|
.constrained()
|
|
|
|
.with_height(style.dimensions().y())
|
|
|
|
.boxed()
|
|
|
|
}
|