diff --git a/Cargo.lock b/Cargo.lock index ecde6fd9c5..bf64781781 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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]] diff --git a/Cargo.toml b/Cargo.toml index cb1f146d3d..0faff00dbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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", diff --git a/crates/gpui/playground/Cargo.toml b/crates/gpui/playground/Cargo.toml index abd639e01b..a9d9e226f4 100644 --- a/crates/gpui/playground/Cargo.toml +++ b/crates/gpui/playground/Cargo.toml @@ -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"] } diff --git a/crates/gpui/playground/ui/src/color.rs b/crates/gpui/playground/src/color.rs similarity index 93% rename from crates/gpui/playground/ui/src/color.rs rename to crates/gpui/playground/src/color.rs index 1de9aec572..2742c4a483 100644 --- a/crates/gpui/playground/ui/src/color.rs +++ b/crates/gpui/playground/src/color.rs @@ -35,6 +35,12 @@ impl Lerp for Range { } } +impl From for Rgba { + fn from(value: gpui::color::Color) -> Self { + todo!() + } +} + impl From for Rgba { fn from(color: Hsla) -> Self { let h = color.h; @@ -88,7 +94,7 @@ impl Into 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 for Hsla { @@ -182,6 +193,12 @@ impl Hsla { } } +impl From for Hsla { + fn from(value: gpui::color::Color) -> Self { + Rgba::from(value).into() + } +} + pub struct ColorScale { colors: SmallVec<[Hsla; 2]>, positions: SmallVec<[f32; 2]>, diff --git a/crates/gpui/playground/ui/src/editor_layout_demo.rs b/crates/gpui/playground/src/editor_layout_demo.rs similarity index 100% rename from crates/gpui/playground/ui/src/editor_layout_demo.rs rename to crates/gpui/playground/src/editor_layout_demo.rs diff --git a/crates/gpui/playground/ui/src/frame.rs b/crates/gpui/playground/src/frame.rs similarity index 92% rename from crates/gpui/playground/ui/src/frame.rs rename to crates/gpui/playground/src/frame.rs index 6f33613578..5f059a67cc 100644 --- a/crates/gpui/playground/ui/src/frame.rs +++ b/crates/gpui/playground/src/frame.rs @@ -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 { style: FrameStyle, @@ -76,6 +77,12 @@ impl Element for Frame { view: &mut V, cx: &mut LayoutContext, ) -> (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 Frame { 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>) -> Self { self.style.margins = margins.into(); self @@ -327,6 +339,8 @@ impl Frame { view: &mut V, cx: &mut LayoutContext, ) -> 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, 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 { + 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, +// 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 { width: T, @@ -1126,6 +1199,18 @@ enum FontStyle { Oblique, } +impl From 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 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>, diff --git a/crates/gpui/playground/src/playground.rs b/crates/gpui/playground/src/playground.rs index e65277cf03..ac5f6864b2 100644 --- a/crates/gpui/playground/src/playground.rs +++ b/crates/gpui/playground/src/playground.rs @@ -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(theme: &ThemeColors) -> impl Element; -// } -// } 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); +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; +mod color; +mod frame; +mod themes; +mod tokens; - fn deref(&self) -> &Self::Target { - &self.0 +#[derive(Element, Clone)] +pub struct Playground(PhantomData); + +impl Playground { + pub fn new() -> Self { + Self(PhantomData) + } + + pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext) -> impl Element { + workspace(&rose_pine::dawn()) } } -impl DerefMut for Playground { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 +fn workspace(theme: &ThemeColors) -> impl Element { + 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(theme: &ThemeColors) -> impl Element { + row() + .fill(theme.base(0.2)) + .justify(0.) + .width(auto()) + .child(text("Zed Playground")) +} + +fn stage(theme: &ThemeColors) -> impl Element { + row().fill(theme.surface(0.9)) +} + +fn status_bar(theme: &ThemeColors) -> impl Element { + row().fill(theme.surface(0.1)) +} + +pub trait DialogDelegate: 'static {} + +impl DialogDelegate for () {} + +#[derive(Element)] +pub struct Dialog> { + title: Cow<'static, str>, + description: Cow<'static, str>, + delegate: Option>>, + buttons: Vec AnyElement>>, + view_type: PhantomData, +} + +pub fn dialog( + title: impl Into>, + description: impl Into>, +) -> Dialog { + 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> Dialog { + pub fn delegate(mut self, delegate: D) -> Dialog { + 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) -> AnyElement { - self.0.clone().into_any() + pub fn button(mut self, label: L, data: Data, handler: H) -> Self + where + L: 'static + Into>, + Data: 'static + Clone, + H: ClickHandler, + { + let label = label.into(); + self.buttons.push(Box::new(move || { + button(label).data(data).click(handler).into_any() + })); + self + } +} + +#[derive(Element)] +struct Button> { + label: Cow<'static, str>, + click_handler: Option, + data: Option, + view_type: PhantomData, +} + +pub trait ClickHandler: 'static { + fn handle(&self, view: &mut V, data: &D, cx: &mut ViewContext); +} + +impl)> ClickHandler for F { + fn handle(&self, view: &mut V, data: &M, cx: &mut ViewContext) { + self(view, data, cx) + } +} + +impl ClickHandler for () { + fn handle(&self, view: &mut V, data: &D, cx: &mut ViewContext) {} +} + +fn button(label: impl Into>) -> Button { + Button { + label: label.into(), + click_handler: None, + data: None, + view_type: PhantomData, + } +} + +impl Button +where + F: ClickHandler, +{ + fn render(&mut self, _: &mut V, _: &mut LayoutContext) -> AnyElement { + // TODO! Handle click etc + row().child(text(self.label.clone())).into_any() + } +} + +// impl Button +// where +// V, +// F: ClickHandler, +// { +// fn render(&mut self, _: &mut V, _: &mut LayoutContext) -> impl Element { +// // TODO! Handle click etc +// row() +// .fill(theme.colors.primary(5)) +// .child(text(self.label.clone()).text_color(theme.colors.on_primary())) +// } +// } + +// struct Tab { +// active: bool, +// } + +// impl Tab +// where +// V, +// { +// fn tab(&mut self, _: &mut V, _: &mut LayoutContext) -> impl Element { +// 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 Button { + fn data(self, data: D) -> Button + where + D: 'static, + { + Button { + label: self.label, + click_handler: self.click_handler, + data: Some(data), + view_type: self.view_type, + } + } +} + +impl Button { + fn click(self, handler: H) -> Button + where + H: 'static + ClickHandler, + { + Button { + label: self.label, + click_handler: Some(handler), + data: self.data, + view_type: self.view_type, + } + } +} + +impl> Dialog { + pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext) -> AnyElement { + 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() } } diff --git a/crates/gpui/playground/ui/src/themes.rs b/crates/gpui/playground/src/themes.rs similarity index 100% rename from crates/gpui/playground/ui/src/themes.rs rename to crates/gpui/playground/src/themes.rs diff --git a/crates/gpui/playground/ui/src/themes/rose_pine.rs b/crates/gpui/playground/src/themes/rose_pine.rs similarity index 100% rename from crates/gpui/playground/ui/src/themes/rose_pine.rs rename to crates/gpui/playground/src/themes/rose_pine.rs diff --git a/crates/gpui/playground/ui/src/tokens.rs b/crates/gpui/playground/src/tokens.rs similarity index 100% rename from crates/gpui/playground/ui/src/tokens.rs rename to crates/gpui/playground/src/tokens.rs diff --git a/crates/gpui/playground/ui/Cargo.toml b/crates/gpui/playground/ui/Cargo.toml deleted file mode 100644 index 867ce08ead..0000000000 --- a/crates/gpui/playground/ui/Cargo.toml +++ /dev/null @@ -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"] } diff --git a/crates/gpui/playground/ui/src/playground_ui.rs b/crates/gpui/playground/ui/src/playground_ui.rs deleted file mode 100644 index 3686b00c65..0000000000 --- a/crates/gpui/playground/ui/src/playground_ui.rs +++ /dev/null @@ -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(PhantomData); - -impl Frame {} - -impl Playground { - pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext) -> impl Element { - workspace(&rose_pine::dawn()) - } -} - -fn workspace(theme: &ThemeColors) -> impl Element { - column() - .size(auto()) - .fill(theme.base(0.1)) - .child(title_bar(theme)) - .child(stage(theme)) - .child(status_bar(theme)) -} - -fn title_bar(theme: &ThemeColors) -> impl Element { - row().fill(theme.surface(1.0)) -} - -fn stage(theme: &ThemeColors) -> impl Element { - row().fill(theme.surface(0.9)) -} - -fn status_bar(theme: &ThemeColors) -> impl Element { - row().fill(theme.surface(0.1)) -} - -pub trait DialogDelegate: 'static {} - -impl DialogDelegate for () {} - -#[derive(Element)] -pub struct Dialog> { - title: Cow<'static, str>, - description: Cow<'static, str>, - delegate: Option>>, - buttons: Vec AnyElement>>, - view_type: PhantomData, -} - -pub fn dialog( - title: impl Into>, - description: impl Into>, -) -> Dialog { - Dialog { - title: title.into(), - description: description.into(), - delegate: None, - buttons: Vec::new(), - view_type: PhantomData, - } -} - -impl> Dialog { - pub fn delegate(mut self, delegate: D) -> Dialog { - let old_delegate = self.delegate.replace(Rc::new(RefCell::new(delegate))); - debug_assert!(old_delegate.is_none(), "delegate already set"); - self - } - - pub fn button(mut self, label: L, data: Data, handler: H) -> Self - where - L: 'static + Into>, - Data: 'static + Clone, - H: ClickHandler, - { - let label = label.into(); - self.buttons.push(Box::new(move || { - button(label).data(data).click(handler).into_any() - })); - self - } -} - -#[derive(Element)] -struct Button> { - label: Cow<'static, str>, - click_handler: Option, - data: Option, - view_type: PhantomData, -} - -pub trait ClickHandler: 'static { - fn handle(&self, view: &mut V, data: &D, cx: &mut ViewContext); -} - -impl)> ClickHandler for F { - fn handle(&self, view: &mut V, data: &M, cx: &mut ViewContext) { - self(view, data, cx) - } -} - -impl ClickHandler for () { - fn handle(&self, view: &mut V, data: &D, cx: &mut ViewContext) {} -} - -fn button(label: impl Into>) -> Button { - Button { - label: label.into(), - click_handler: None, - data: None, - view_type: PhantomData, - } -} - -impl Button -where - F: ClickHandler, -{ - fn render(&mut self, _: &mut V, _: &mut LayoutContext) -> AnyElement { - // TODO! Handle click etc - row().child(text(self.label.clone())).into_any() - } -} - -// impl Button -// where -// V, -// F: ClickHandler, -// { -// fn render(&mut self, _: &mut V, _: &mut LayoutContext) -> impl Element { -// // TODO! Handle click etc -// row() -// .fill(theme.colors.primary(5)) -// .child(text(self.label.clone()).text_color(theme.colors.on_primary())) -// } -// } - -// struct Tab { -// active: bool, -// } - -// impl Tab -// where -// V, -// { -// fn tab(&mut self, _: &mut V, _: &mut LayoutContext) -> impl Element { -// 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 Button { - fn data(self, data: D) -> Button - where - D: 'static, - { - Button { - label: self.label, - click_handler: self.click_handler, - data: Some(data), - view_type: self.view_type, - } - } -} - -impl Button { - fn click(self, handler: H) -> Button - where - H: 'static + ClickHandler, - { - Button { - label: self.label, - click_handler: Some(handler), - data: self.data, - view_type: self.view_type, - } - } -} - -impl> Dialog { - pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext) -> AnyElement { - 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() - } -} diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 571f42456f..be29b7029e 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -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>>(&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(&mut self, style: S, f: F) -> T where S: Into>, 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 } } diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index b058384087..af56cad76c 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -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)