Ditch the hot reloading approach

This commit is contained in:
Nathan Sobo 2023-08-11 00:26:58 -06:00
parent 0bf607cd2d
commit d6eaa3c6b8
14 changed files with 340 additions and 275 deletions

13
Cargo.lock generated
View File

@ -5291,24 +5291,13 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
name = "playground"
version = "0.1.0"
dependencies = [
"gpui",
"log",
"playground_ui",
"simplelog",
]
[[package]]
name = "playground_ui"
version = "0.1.0"
dependencies = [
"collections",
"derive_more",
"gpui",
"log",
"optional_struct",
"serde",
"simplelog",
"smallvec",
"util",
]
[[package]]

View File

@ -29,7 +29,6 @@ members = [
"crates/go_to_line",
"crates/gpui",
"crates/gpui/playground",
"crates/gpui/playground/ui",
"crates/gpui_macros",
"crates/install_cli",
"crates/journal",

View File

@ -8,11 +8,13 @@ version = "0.1.0"
edition = "2021"
[dependencies]
playground_ui = { path = "ui" }
derive_more.workspace = true
gpui = { path = ".." }
log.workspace = true
optional_struct = "0.3.1"
serde.workspace = true
simplelog = "0.9"
smallvec.workspace = true
[dev-dependencies]
gpui = { path = "..", features = ["test-support"] }

View File

@ -35,6 +35,12 @@ impl Lerp for Range<Hsla> {
}
}
impl From<gpui::color::Color> for Rgba {
fn from(value: gpui::color::Color) -> Self {
todo!()
}
}
impl From<Hsla> for Rgba {
fn from(color: Hsla) -> Self {
let h = color.h;
@ -88,7 +94,7 @@ impl Into<gpui::color::Color> for Rgba {
}
}
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Hsla {
pub h: f32,
pub s: f32,
@ -97,7 +103,12 @@ pub struct Hsla {
}
pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
Hsla { h, s, l, a }
Hsla {
h: h.clamp(0., 1.),
s: s.clamp(0., 1.),
l: l.clamp(0., 1.),
a: a.clamp(0., 1.),
}
}
impl From<Rgba> for Hsla {
@ -182,6 +193,12 @@ impl Hsla {
}
}
impl From<gpui::color::Color> for Hsla {
fn from(value: gpui::color::Color) -> Self {
Rgba::from(value).into()
}
}
pub struct ColorScale {
colors: SmallVec<[Hsla; 2]>,
positions: SmallVec<[f32; 2]>,

View File

@ -1,10 +1,9 @@
#![allow(unused_variables, dead_code)]
use derive_more::{Add, Deref, DerefMut};
use gpui::elements::layout_highlighted_chunks;
use gpui::Entity;
use gpui::{
color::Color,
elements::layout_highlighted_chunks,
fonts::HighlightStyle,
geometry::{
rect::RectF,
@ -14,15 +13,17 @@ use gpui::{
scene,
serde_json::Value,
text_layout::{Line, ShapedBoundary},
AnyElement, AppContext, Element, LayoutContext, PaintContext, Quad, SceneBuilder,
SizeConstraint, View, ViewContext,
AnyElement, AppContext, Element, Entity, LayoutContext, PaintContext, Quad, SceneBuilder,
SizeConstraint, View, ViewContext, WindowContext,
};
use length::{Length, Rems};
use log::warn;
use optional_struct::*;
use std::{any::Any, borrow::Cow, f32, ops::Range, sync::Arc};
use crate::color::Rgba;
use crate::color::{Hsla, Rgba};
use self::length::rems;
pub struct Frame<V> {
style: FrameStyle,
@ -76,6 +77,12 @@ impl<V: 'static> Element<V> for Frame<V> {
view: &mut V,
cx: &mut LayoutContext<V>,
) -> (Vector2F, Self::LayoutState) {
if self.style.text.is_some() {
let mut style = TextStyle::from_legacy(&cx.text_style(), cx);
self.style.text.clone().apply_to(&mut style);
cx.push_text_style(style.to_legacy());
}
let layout = if let Some(axis) = self.style.axis.to_2d() {
self.layout_xy(axis, constraint, cx.rem_pixels(), view, cx)
} else {
@ -263,6 +270,11 @@ impl<V: 'static> Frame<V> {
self
}
pub fn text_color(mut self, color: Hsla) -> Self {
self.style.text.color = Some(color);
self
}
pub fn margins(mut self, margins: impl Into<Edges<Length>>) -> Self {
self.style.margins = margins.into();
self
@ -327,6 +339,8 @@ impl<V: 'static> Frame<V> {
view: &mut V,
cx: &mut LayoutContext<V>,
) -> FrameLayout {
self.style.text.is_some();
let cross_axis = primary_axis.rotate();
let total_flex = self.style.flex();
let mut layout = FrameLayout {
@ -608,8 +622,67 @@ struct TextStyle {
font_family: Arc<str>,
weight: FontWeight,
style: FontStyle,
color: Hsla,
}
impl TextStyle {
fn from_legacy(text_style: &gpui::fonts::TextStyle, _cx: &WindowContext) -> Self {
Self {
size: rems(text_style.font_size / 16.), // TODO: Get this from the context!
font_family: text_style.font_family_name.clone(),
weight: text_style.font_properties.weight.into(),
style: text_style.font_properties.style.into(),
color: text_style.color.into(),
}
}
fn to_legacy(&self, cx: &WindowContext) -> Result<gpui::fonts::TextStyle> {
let font_family_id = cx.font_cache().load_family(
&[self.font_family.as_ref()],
&gpui::fonts::Features::default(),
)?;
let font_properties = gpui::fonts::Properties {
style: self.style.into(),
weight: self.weight.into(),
stretch: Default::default(),
};
let font_id = cx
.font_cache()
.select_font(font_family_id, &font_properties);
Ok(gpui::fonts::TextStyle {
color: self.color.into(),
font_family_name: self.font_family.clone(),
font_family_id,
font_id,
font_size: todo!(),
font_properties,
underline: todo!(),
soft_wrap: true,
})
}
}
impl OptionalTextStyle {
pub fn is_some(&self) -> bool {
self.size.is_some()
&& self.font_family.is_some()
&& self.weight.is_some()
&& self.style.is_some()
&& self.color.is_some()
}
}
// pub color: Color,
// pub font_family_name: Arc<str>,
// pub font_family_id: FamilyId,
// pub font_id: FontId,
// pub font_size: f32,
// #[schemars(with = "PropertiesDef")]
// pub font_properties: Properties,
// pub underline: Underline,
// pub soft_wrap: bool,
#[derive(Add, Default, Clone)]
pub struct Size<T> {
width: T,
@ -1126,6 +1199,18 @@ enum FontStyle {
Oblique,
}
impl From<gpui::fonts::Style> for FontStyle {
fn from(value: gpui::fonts::Style) -> Self {
use gpui::fonts::Style;
match value {
Style::Normal => FontStyle::Normal,
Style::Italic => FontStyle::Italic,
Style::Oblique => FontStyle::Oblique,
}
}
}
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
enum FontWeight {
Thin,
@ -1140,6 +1225,24 @@ enum FontWeight {
Black,
}
impl From<gpui::fonts::Weight> for FontWeight {
fn from(value: gpui::fonts::Weight) -> Self {
use gpui::fonts::Weight;
match value {
Weight::THIN => FontWeight::Thin,
Weight::EXTRA_LIGHT => FontWeight::ExtraLight,
Weight::LIGHT => FontWeight::Light,
Weight::NORMAL => FontWeight::Normal,
Weight::MEDIUM => FontWeight::Medium,
Weight::SEMIBOLD => FontWeight::Semibold,
Weight::BOLD => FontWeight::Bold,
Weight::EXTRA_BOLD => FontWeight::ExtraBold,
Weight::BLACK => FontWeight::Black,
}
}
}
#[derive(Default)]
pub struct Text {
text: Cow<'static, str>,

View File

@ -1,17 +1,11 @@
#![allow(dead_code, unused_variables)]
use gpui::{
platform::{TitlebarOptions, WindowOptions},
AnyElement, Element, Entity, View,
AnyElement, Element,
};
use log::LevelFilter;
use simplelog::SimpleLogger;
use std::ops::{Deref, DerefMut};
// dymod! {
// #[path = "../ui/src/playground_ui.rs"]
// pub mod ui {
// // fn workspace<V>(theme: &ThemeColors) -> impl Element<V>;
// }
// }
fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
@ -26,38 +20,213 @@ fn main() {
}),
..Default::default()
},
|_| Playground::default(),
|_| view(|_| Playground::new()),
);
});
}
#[derive(Clone, Default)]
struct Playground(playground_ui::Playground<Self>);
use frame::{length::auto, *};
use gpui::{LayoutContext, ViewContext};
use std::{borrow::Cow, cell::RefCell, marker::PhantomData, rc::Rc};
use themes::{rose_pine, ThemeColors};
use tokens::{margin::m4, text::lg};
impl Deref for Playground {
type Target = playground_ui::Playground<Self>;
mod color;
mod frame;
mod themes;
mod tokens;
fn deref(&self) -> &Self::Target {
&self.0
#[derive(Element, Clone)]
pub struct Playground<V: 'static>(PhantomData<V>);
impl<V> Playground<V> {
pub fn new() -> Self {
Self(PhantomData)
}
pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext<V>) -> impl Element<V> {
workspace(&rose_pine::dawn())
}
}
impl DerefMut for Playground {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
fn workspace<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
column()
.size(auto())
.fill(theme.base(0.5))
.text_color(theme.text(0.5))
.child(title_bar(theme))
.child(stage(theme))
.child(status_bar(theme))
}
fn title_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
row()
.fill(theme.base(0.2))
.justify(0.)
.width(auto())
.child(text("Zed Playground"))
}
fn stage<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
row().fill(theme.surface(0.9))
}
fn status_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
row().fill(theme.surface(0.1))
}
pub trait DialogDelegate<V>: 'static {}
impl<V> DialogDelegate<V> for () {}
#[derive(Element)]
pub struct Dialog<V: 'static, D: DialogDelegate<V>> {
title: Cow<'static, str>,
description: Cow<'static, str>,
delegate: Option<Rc<RefCell<D>>>,
buttons: Vec<Box<dyn FnOnce() -> AnyElement<V>>>,
view_type: PhantomData<V>,
}
pub fn dialog<V>(
title: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>,
) -> Dialog<V, ()> {
Dialog {
title: title.into(),
description: description.into(),
delegate: None,
buttons: Vec::new(),
view_type: PhantomData,
}
}
impl Entity for Playground {
type Event = ();
}
impl View for Playground {
fn ui_name() -> &'static str {
"PlaygroundView"
impl<V, D: DialogDelegate<V>> Dialog<V, D> {
pub fn delegate(mut self, delegate: D) -> Dialog<V, D> {
let old_delegate = self.delegate.replace(Rc::new(RefCell::new(delegate)));
debug_assert!(old_delegate.is_none(), "delegate already set");
self
}
fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> AnyElement<Playground> {
self.0.clone().into_any()
pub fn button<L, Data, H>(mut self, label: L, data: Data, handler: H) -> Self
where
L: 'static + Into<Cow<'static, str>>,
Data: 'static + Clone,
H: ClickHandler<V, Data>,
{
let label = label.into();
self.buttons.push(Box::new(move || {
button(label).data(data).click(handler).into_any()
}));
self
}
}
#[derive(Element)]
struct Button<V: 'static, D: 'static, H: ClickHandler<V, D>> {
label: Cow<'static, str>,
click_handler: Option<H>,
data: Option<D>,
view_type: PhantomData<V>,
}
pub trait ClickHandler<V, D>: 'static {
fn handle(&self, view: &mut V, data: &D, cx: &mut ViewContext<V>);
}
impl<V, M, F: 'static + Fn(&mut V, &M, &mut ViewContext<V>)> ClickHandler<V, M> for F {
fn handle(&self, view: &mut V, data: &M, cx: &mut ViewContext<V>) {
self(view, data, cx)
}
}
impl<V, D> ClickHandler<V, D> for () {
fn handle(&self, view: &mut V, data: &D, cx: &mut ViewContext<V>) {}
}
fn button<V>(label: impl Into<Cow<'static, str>>) -> Button<V, (), ()> {
Button {
label: label.into(),
click_handler: None,
data: None,
view_type: PhantomData,
}
}
impl<V, D, F> Button<V, D, F>
where
F: ClickHandler<V, D>,
{
fn render(&mut self, _: &mut V, _: &mut LayoutContext<V>) -> AnyElement<V> {
// TODO! Handle click etc
row().child(text(self.label.clone())).into_any()
}
}
// impl<V, D, F> Button<V, D, F>
// where
// V,
// F: ClickHandler<V, D>,
// {
// fn render(&mut self, _: &mut V, _: &mut LayoutContext<V>) -> impl Element<V> {
// // TODO! Handle click etc
// row()
// .fill(theme.colors.primary(5))
// .child(text(self.label.clone()).text_color(theme.colors.on_primary()))
// }
// }
// struct Tab<V> {
// active: bool,
// }
// impl<V> Tab<V>
// where
// V,
// {
// fn tab(&mut self, _: &mut V, _: &mut LayoutContext<V>) -> impl Element<V> {
// let theme = todo!();
// // TODO! Handle click etc
// row()
// .fill(theme.colors.neutral(6))
// .child(text(self.label.clone()).text_color(theme.colors.on_neutral()))
// }
// }
impl<V> Button<V, (), ()> {
fn data<D>(self, data: D) -> Button<V, D, ()>
where
D: 'static,
{
Button {
label: self.label,
click_handler: self.click_handler,
data: Some(data),
view_type: self.view_type,
}
}
}
impl<V, D> Button<V, D, ()> {
fn click<H>(self, handler: H) -> Button<V, D, H>
where
H: 'static + ClickHandler<V, D>,
{
Button {
label: self.label,
click_handler: Some(handler),
data: self.data,
view_type: self.view_type,
}
}
}
impl<V, D: DialogDelegate<V>> Dialog<V, D> {
pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext<V>) -> AnyElement<V> {
column()
.child(text(self.title.clone()).text_size(lg()))
.child(text(self.description.clone()).margins((m4(), auto())))
.child(row().children(self.buttons.drain(..).map(|button| (button)())))
.into_any()
}
}

View File

@ -1,22 +0,0 @@
[package]
name = "playground_ui"
version = "0.1.0"
edition = "2021"
[lib]
name = "playground_ui"
path = "src/playground_ui.rs"
crate-type = ["dylib"]
[dependencies]
collections = { path = "../../../collections" }
util = { path = "../../../util" }
gpui = { path = "../.." }
derive_more = "0.99.17"
log.workspace = true
optional_struct = "0.3.1"
smallvec.workspace = true
serde.workspace = true
[dev-dependencies]
gpui = { path = "../..", features = ["test-support"] }

View File

@ -1,200 +0,0 @@
#![allow(dead_code, unused_variables)]
use frame::{length::auto, *};
use gpui::{AnyElement, Element, LayoutContext, ViewContext};
use std::{borrow::Cow, cell::RefCell, marker::PhantomData, rc::Rc};
use themes::{rose_pine, ThemeColors};
use tokens::{margin::m4, text::lg};
mod color;
mod frame;
mod themes;
mod tokens;
#[derive(Element, Clone, Default)]
pub struct Playground<V: 'static>(PhantomData<V>);
impl<V> Frame<V> {}
impl<V> Playground<V> {
pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext<V>) -> impl Element<V> {
workspace(&rose_pine::dawn())
}
}
fn workspace<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
column()
.size(auto())
.fill(theme.base(0.1))
.child(title_bar(theme))
.child(stage(theme))
.child(status_bar(theme))
}
fn title_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
row().fill(theme.surface(1.0))
}
fn stage<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
row().fill(theme.surface(0.9))
}
fn status_bar<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
row().fill(theme.surface(0.1))
}
pub trait DialogDelegate<V>: 'static {}
impl<V> DialogDelegate<V> for () {}
#[derive(Element)]
pub struct Dialog<V: 'static, D: DialogDelegate<V>> {
title: Cow<'static, str>,
description: Cow<'static, str>,
delegate: Option<Rc<RefCell<D>>>,
buttons: Vec<Box<dyn FnOnce() -> AnyElement<V>>>,
view_type: PhantomData<V>,
}
pub fn dialog<V>(
title: impl Into<Cow<'static, str>>,
description: impl Into<Cow<'static, str>>,
) -> Dialog<V, ()> {
Dialog {
title: title.into(),
description: description.into(),
delegate: None,
buttons: Vec::new(),
view_type: PhantomData,
}
}
impl<V, D: DialogDelegate<V>> Dialog<V, D> {
pub fn delegate(mut self, delegate: D) -> Dialog<V, D> {
let old_delegate = self.delegate.replace(Rc::new(RefCell::new(delegate)));
debug_assert!(old_delegate.is_none(), "delegate already set");
self
}
pub fn button<L, Data, H>(mut self, label: L, data: Data, handler: H) -> Self
where
L: 'static + Into<Cow<'static, str>>,
Data: 'static + Clone,
H: ClickHandler<V, Data>,
{
let label = label.into();
self.buttons.push(Box::new(move || {
button(label).data(data).click(handler).into_any()
}));
self
}
}
#[derive(Element)]
struct Button<V: 'static, D: 'static, H: ClickHandler<V, D>> {
label: Cow<'static, str>,
click_handler: Option<H>,
data: Option<D>,
view_type: PhantomData<V>,
}
pub trait ClickHandler<V, D>: 'static {
fn handle(&self, view: &mut V, data: &D, cx: &mut ViewContext<V>);
}
impl<V, M, F: 'static + Fn(&mut V, &M, &mut ViewContext<V>)> ClickHandler<V, M> for F {
fn handle(&self, view: &mut V, data: &M, cx: &mut ViewContext<V>) {
self(view, data, cx)
}
}
impl<V, D> ClickHandler<V, D> for () {
fn handle(&self, view: &mut V, data: &D, cx: &mut ViewContext<V>) {}
}
fn button<V>(label: impl Into<Cow<'static, str>>) -> Button<V, (), ()> {
Button {
label: label.into(),
click_handler: None,
data: None,
view_type: PhantomData,
}
}
impl<V, D, F> Button<V, D, F>
where
F: ClickHandler<V, D>,
{
fn render(&mut self, _: &mut V, _: &mut LayoutContext<V>) -> AnyElement<V> {
// TODO! Handle click etc
row().child(text(self.label.clone())).into_any()
}
}
// impl<V, D, F> Button<V, D, F>
// where
// V,
// F: ClickHandler<V, D>,
// {
// fn render(&mut self, _: &mut V, _: &mut LayoutContext<V>) -> impl Element<V> {
// // TODO! Handle click etc
// row()
// .fill(theme.colors.primary(5))
// .child(text(self.label.clone()).text_color(theme.colors.on_primary()))
// }
// }
// struct Tab<V> {
// active: bool,
// }
// impl<V> Tab<V>
// where
// V,
// {
// fn tab(&mut self, _: &mut V, _: &mut LayoutContext<V>) -> impl Element<V> {
// let theme = todo!();
// // TODO! Handle click etc
// row()
// .fill(theme.colors.neutral(6))
// .child(text(self.label.clone()).text_color(theme.colors.on_neutral()))
// }
// }
impl<V> Button<V, (), ()> {
fn data<D>(self, data: D) -> Button<V, D, ()>
where
D: 'static,
{
Button {
label: self.label,
click_handler: self.click_handler,
data: Some(data),
view_type: self.view_type,
}
}
}
impl<V, D> Button<V, D, ()> {
fn click<H>(self, handler: H) -> Button<V, D, H>
where
H: 'static + ClickHandler<V, D>,
{
Button {
label: self.label,
click_handler: Some(handler),
data: self.data,
view_type: self.view_type,
}
}
}
impl<V, D: DialogDelegate<V>> Dialog<V, D> {
pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext<V>) -> AnyElement<V> {
column()
.child(text(self.title.clone()).text_size(lg()))
.child(text(self.description.clone()).margins((m4(), auto())))
.child(row().children(self.buttons.drain(..).map(|button| (button)())))
.into_any()
}
}

View File

@ -3440,14 +3440,22 @@ impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
.unwrap_or(Arc::new(TextStyle::default(&self.font_cache)))
}
pub fn push_text_style<S: Into<Arc<TextStyle>>>(&mut self, style: S) {
self.text_style_stack.push(style.into());
}
pub fn pop_text_style(&mut self) {
self.text_style_stack.pop();
}
pub fn with_text_style<S, F, T>(&mut self, style: S, f: F) -> T
where
S: Into<Arc<TextStyle>>,
F: FnOnce(&mut Self) -> T,
{
self.text_style_stack.push(style.into());
self.push_text_style(style);
let result = f(self);
self.text_style_stack.pop();
self.pop_text_style();
result
}
}

View File

@ -15,7 +15,7 @@ use serde_json::json;
#[derive(Clone, Copy, Default, PartialEq, Eq, Hash, PartialOrd, Ord, JsonSchema)]
#[repr(transparent)]
pub struct Color(#[schemars(with = "String")] ColorU);
pub struct Color(#[schemars(with = "String")] pub ColorU);
pub fn color(rgba: u32) -> Color {
Color::from_u32(rgba)