zed/crates/theme/src/ui.rs

298 lines
8.1 KiB
Rust
Raw Normal View History

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},
platform,
platform::MouseButton,
2023-03-27 21:25:11 +00:00
scene::MouseClick,
2023-04-12 00:21:56 +00:00
Action, Element, ElementBox, 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> {
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>(
label: ElementBox<V>,
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(),
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| {
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
keystroke_label_for(
cx.window_id(),
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>(
window_id: usize,
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(
window_id,
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 00:21:56 +00:00
pub fn cta_button<Tag, 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>,
) -> ElementBox<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>>,
A: 'static + Action + Clone,
V: View,
{
2023-04-12 00:21:56 +00:00
cta_button_with_click::<Tag, _, _, _>(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)
.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 00:21:56 +00:00
) -> ElementBox<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 00:21:56 +00:00
F: FnOnce(&mut gpui::ViewContext<V>) -> ElementBox<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);
})
.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()
}