From 54a7419fa26ee7013ea0368cec6381839c379bff Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 24 Jul 2023 23:27:14 -0600 Subject: [PATCH] WIP --- Cargo.lock | 9 + Cargo.toml | 1 + crates/gpui/playground/Cargo.toml | 2 + crates/gpui/playground/src/playground.rs | 55 +++--- crates/gpui/playground/ui/Cargo.toml | 12 ++ .../gpui/playground/ui/src/playground_ui.rs | 91 ++++++++++ crates/gpui/playground/ui/src/tokens.rs | 157 ++++++++++++++++++ crates/gpui/src/color.rs | 54 +++++- crates/gpui/src/elements.rs | 1 + .../src/elements.rs => src/elements/node.rs} | 136 ++++++++------- crates/gpui/src/elements/text.rs | 52 ++++++ crates/gpui_macros/Cargo.toml | 4 +- crates/gpui_macros/src/gpui_macros.rs | 51 ++++-- 13 files changed, 518 insertions(+), 107 deletions(-) create mode 100644 crates/gpui/playground/ui/Cargo.toml create mode 100644 crates/gpui/playground/ui/src/playground_ui.rs create mode 100644 crates/gpui/playground/ui/src/tokens.rs rename crates/gpui/{playground/src/elements.rs => src/elements/node.rs} (90%) diff --git a/Cargo.lock b/Cargo.lock index 2f52541ef3..b2f60e3867 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3041,6 +3041,7 @@ dependencies = [ name = "gpui_macros" version = "0.1.0" dependencies = [ + "lazy_static", "proc-macro2", "quote", "syn 1.0.109", @@ -5043,9 +5044,17 @@ version = "0.1.0" dependencies = [ "gpui", "log", + "playground_ui", "simplelog", ] +[[package]] +name = "playground_ui" +version = "0.1.0" +dependencies = [ + "gpui", +] + [[package]] name = "plist" version = "1.5.0" diff --git a/Cargo.toml b/Cargo.toml index 114971e169..4944608a1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ 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 6cef416103..7bf5e50c67 100644 --- a/crates/gpui/playground/Cargo.toml +++ b/crates/gpui/playground/Cargo.toml @@ -8,6 +8,8 @@ version = "0.1.0" edition = "2021" [dependencies] +playground_ui = { path = "ui" } + gpui = { path = ".." } log.workspace = true simplelog = "0.9" diff --git a/crates/gpui/playground/src/playground.rs b/crates/gpui/playground/src/playground.rs index 1c944e6500..a5ecd7104e 100644 --- a/crates/gpui/playground/src/playground.rs +++ b/crates/gpui/playground/src/playground.rs @@ -1,52 +1,45 @@ -use elements::{Length, Node, NodeStyle}; -use gpui::{color::Color, AnyElement, Element, Entity, View, ViewContext}; +use std::ops::{Deref, DerefMut}; + +use gpui::{AnyElement, Element, Entity, View}; use log::LevelFilter; use simplelog::SimpleLogger; -mod elements; - fn main() { SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); gpui::App::new(()).unwrap().run(|cx| { cx.platform().activate(true); - cx.add_window(Default::default(), |_| PlaygroundView); + cx.add_window(Default::default(), |_| Playground::default()); }); } -struct PlaygroundView; +#[derive(Clone, Default)] +struct Playground(playground_ui::Playground); -impl Entity for PlaygroundView { +impl Deref for Playground { + type Target = playground_ui::Playground; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Playground { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Entity for Playground { type Event = (); } -impl View for PlaygroundView { +impl View for Playground { fn ui_name() -> &'static str { "PlaygroundView" } - fn render(&mut self, cx: &mut gpui::ViewContext) -> AnyElement { - // Node::with_style(NodeStyle) - // Node::new().width(100.0).fill(Color::red()) - // - Node::new() - .width(Length::auto(1.)) - .fill(Color::red()) - .row() - .children([ - Node::new().width(20.).height(20.).fill(Color::green()), - Node::new().width(20.).height(20.).fill(Color::blue()), - Node::new().width(30.).height(30.).fill(Color::yellow()), - Node::new().width(50.).height(50.).fill(Color::yellow()), - ]) - .into_any() - - // Node::with_style( - // NodeStyle::default() - // .width(100.) - // .height(100.) - // .fill(Color::red()), - // ) - // .into_any() + fn render(&mut self, _: &mut gpui::ViewContext) -> AnyElement { + self.0.clone().into_any() } } diff --git a/crates/gpui/playground/ui/Cargo.toml b/crates/gpui/playground/ui/Cargo.toml new file mode 100644 index 0000000000..c57dc508f1 --- /dev/null +++ b/crates/gpui/playground/ui/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "playground_ui" +version = "0.1.0" +edition = "2021" + +[lib] +name = "playground_ui" +path = "src/playground_ui.rs" +crate-type = ["dylib"] + +[dependencies] +gpui = { path = "../.." } diff --git a/crates/gpui/playground/ui/src/playground_ui.rs b/crates/gpui/playground/ui/src/playground_ui.rs new file mode 100644 index 0000000000..21608b3edb --- /dev/null +++ b/crates/gpui/playground/ui/src/playground_ui.rs @@ -0,0 +1,91 @@ +use gpui::{ + elements::{ + node::{column, length::auto, row}, + Text, + }, + AnyElement, Element, View, ViewContext, +}; +use std::{borrow::Cow, marker::PhantomData}; +use tokens::{margin::m4, text::lg}; + +mod tokens; + +#[derive(Element, Clone, Default)] +pub struct Playground; + +impl Playground { + pub fn render(&mut self, _: &mut V, _: &mut gpui::ViewContext) -> AnyElement { + column() + .width(auto()) + .child(dialog( + "This is a dialog", + "You would see a description here.", + )) + .into_any() + } +} + +pub trait DialogDelegate: 'static { + fn handle_submit(&mut self, view: &mut V, button: B); +} + +impl DialogDelegate for () { + fn handle_submit(&mut self, _: &mut V, _: B) {} +} + +#[derive(Element)] +pub struct Dialog> { + title: Cow<'static, str>, + description: Cow<'static, str>, + delegate: Option, + view_type: PhantomData, +} + +pub fn dialog( + title: impl Into>, + description: impl Into>, +) -> Dialog { + Dialog { + title: title.into(), + description: description.into(), + delegate: None, + view_type: PhantomData, + } +} + +impl> Dialog { + pub fn with_delegate(mut self, delegate: D) -> Dialog { + let old_delegate = self.delegate.replace(delegate); + debug_assert!(old_delegate.is_none(), "delegate already set"); + self + } +} + +struct Button)> { + label: Cow<'static, str>, + on_click: Option, + view_type: PhantomData, +} + +fn button)>( + label: impl Into>, +) -> Button { + Button { + label: label.into(), + on_click: None, + view_type: PhantomData, + } +} + +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([ + button("Cancel").margin_left(auto()), + button("OK").margin_left(m4()), + ])) + .into_any() + } +} diff --git a/crates/gpui/playground/ui/src/tokens.rs b/crates/gpui/playground/ui/src/tokens.rs new file mode 100644 index 0000000000..194f50477d --- /dev/null +++ b/crates/gpui/playground/ui/src/tokens.rs @@ -0,0 +1,157 @@ +pub mod color { + use gpui::color::Color; + + pub fn background(elevation: f32) -> Color { + todo!() + } +} + +pub mod text { + pub fn xs() -> f32 { + 0.75 + } + + pub fn sm() -> f32 { + 0.875 + } + + pub fn base() -> f32 { + 1.0 + } + + pub fn lg() -> f32 { + 1.125 + } + + pub fn xl() -> f32 { + 1.25 + } + + pub fn xxl() -> f32 { + 1.5 + } + + pub fn xxxl() -> f32 { + 1.875 + } + + pub fn xxxx() -> f32 { + 2.25 + } + + pub fn xxxxx() -> f32 { + 3.0 + } + + pub fn xxxxxx() -> f32 { + 4.0 + } +} + +pub mod padding { + pub fn p1() -> f32 { + 0.25 + } + + pub fn p2() -> f32 { + 0.5 + } + + pub fn p3() -> f32 { + 0.75 + } + + pub fn p4() -> f32 { + 1.0 + } + + pub fn p5() -> f32 { + 1.25 + } + + pub fn p6() -> f32 { + 1.5 + } + + pub fn p8() -> f32 { + 2.0 + } + + pub fn p10() -> f32 { + 2.5 + } + + pub fn p12() -> f32 { + 3.0 + } + + pub fn p16() -> f32 { + 4.0 + } + + pub fn p20() -> f32 { + 5.0 + } + + pub fn p24() -> f32 { + 6.0 + } + + pub fn p32() -> f32 { + 8.0 + } +} + +pub mod margin { + pub fn m1() -> f32 { + 0.25 + } + + pub fn m2() -> f32 { + 0.5 + } + + pub fn m3() -> f32 { + 0.75 + } + + pub fn m4() -> f32 { + 1.0 + } + + pub fn m5() -> f32 { + 1.25 + } + + pub fn m6() -> f32 { + 1.5 + } + + pub fn m8() -> f32 { + 2.0 + } + + pub fn m10() -> f32 { + 2.5 + } + + pub fn m12() -> f32 { + 3.0 + } + + pub fn m16() -> f32 { + 4.0 + } + + pub fn m20() -> f32 { + 5.0 + } + + pub fn m24() -> f32 { + 6.0 + } + + pub fn m32() -> f32 { + 8.0 + } +} diff --git a/crates/gpui/src/color.rs b/crates/gpui/src/color.rs index 2a0c2c1dc1..bc7cae6643 100644 --- a/crates/gpui/src/color.rs +++ b/crates/gpui/src/color.rs @@ -17,33 +17,73 @@ use serde_json::json; #[repr(transparent)] pub struct Color(#[schemars(with = "String")] ColorU); +pub fn color(rgba: u32) -> Color { + color(rgba) +} + +pub fn rgb(r: f32, g: f32, b: f32) -> Color { + Color(ColorF::new(r, g, b, 1.).to_u8()) +} + +pub fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { + Color(ColorF::new(r, g, b, a).to_u8()) +} + +pub fn transparent_black() -> Color { + Color(ColorU::transparent_black()) +} + +pub fn black() -> Color { + Color(ColorU::black()) +} + +pub fn white() -> Color { + Color(ColorU::white()) +} + +pub fn red() -> Color { + color(0xff0000ff) +} + +pub fn green() -> Color { + color(0x00ff00ff) +} + +pub fn blue() -> Color { + color(0x0000ffff) +} + +pub fn yellow() -> Color { + color(0xffff00ff) +} + impl Color { pub fn transparent_black() -> Self { - Self(ColorU::transparent_black()) + transparent_black() } pub fn black() -> Self { - Self(ColorU::black()) + black() } pub fn white() -> Self { - Self(ColorU::white()) + white() } pub fn red() -> Self { - Self(ColorU::from_u32(0xff0000ff)) + Color::from_u32(0xff0000ff) } pub fn green() -> Self { - Self(ColorU::from_u32(0x00ff00ff)) + Color::from_u32(0x00ff00ff) } pub fn blue() -> Self { - Self(ColorU::from_u32(0x0000ffff)) + Color::from_u32(0x0000ffff) } pub fn yellow() -> Self { - Self(ColorU::from_u32(0xffff00ff)) + Color::from_u32(0xffff00ff) } pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { diff --git a/crates/gpui/src/elements.rs b/crates/gpui/src/elements.rs index 78403444ff..8192a41783 100644 --- a/crates/gpui/src/elements.rs +++ b/crates/gpui/src/elements.rs @@ -12,6 +12,7 @@ mod keystroke_label; mod label; mod list; mod mouse_event_handler; +pub mod node; mod overlay; mod resizable; mod stack; diff --git a/crates/gpui/playground/src/elements.rs b/crates/gpui/src/elements/node.rs similarity index 90% rename from crates/gpui/playground/src/elements.rs rename to crates/gpui/src/elements/node.rs index 64a1f8638a..58d412e2d2 100644 --- a/crates/gpui/playground/src/elements.rs +++ b/crates/gpui/src/elements/node.rs @@ -1,4 +1,4 @@ -use gpui::{ +use crate::{ color::Color, geometry::{ rect::RectF, @@ -9,47 +9,62 @@ use gpui::{ serde_json::Value, AnyElement, Element, LayoutContext, Quad, SceneBuilder, SizeConstraint, View, ViewContext, }; -use std::{any::Any, f32, ops::Range}; +use std::{any::Any, borrow::Cow, f32, ops::Range}; -// Core idea is that everything is a channel, and channels are heirarchical. -// -// Tree 🌲 of channels -// - (Potentially v0.2) All channels associated with a conversation (Slack model) -// - Audio -// - You can share projects into the channel -// - 1. -// -// -// - 2 thoughts: -// - Difference from where we are to the above: -// - Channels = rooms + chat + persistence -// - Chat = multiplayer assistant panel + server integrated persistence -// - The tree structure, is good for navigating chats, AND it's good for distributing permissions. -// #zed-public// /zed- <- Share a pointer (URL) for this -// -// +use self::length::Length; pub struct Node { style: NodeStyle, - children: Vec>, + content: Content, +} + +enum Content { + Children(Vec>), + Text(Cow<'static, str>), +} + +impl Default for Content { + fn default() -> Self { + Self::Children(Vec::new()) + } +} + +pub fn column() -> Node { + Node::default() +} + +pub fn row() -> Node { + Node { + style: NodeStyle { + axis: Axis3d::X, + ..Default::default() + }, + content: Default::default(), + } +} + +pub fn stack() -> Node { + Node { + style: NodeStyle { + axis: Axis3d::Z, + ..Default::default() + }, + content: Default::default(), + } } impl Default for Node { fn default() -> Self { Self { style: Default::default(), - children: Default::default(), + content: Default::default(), } } } impl Node { - pub fn new() -> Self { - Self::default() - } - pub fn child(mut self, child: impl Element) -> Self { - self.children.push(child.into_any()); + self.content.push(child.into_any()); self } @@ -58,7 +73,7 @@ impl Node { I: IntoIterator, E: Element, { - self.children + self.content .extend(children.into_iter().map(|child| child.into_any())); self } @@ -100,7 +115,7 @@ impl Node { let mut cross_axis_max: f32 = 0.0; // First pass: Layout non-flex children only - for child in &mut self.children { + for child in &mut self.content { let child_flex = child.metadata::().and_then(|style| match axis { Axis2d::X => style.width.flex(), Axis2d::Y => style.height.flex(), @@ -138,7 +153,7 @@ impl Node { if total_flex > 0. { let space_per_flex = remaining_space.max(0.) / total_flex; - for child in &mut self.children { + for child in &mut self.content { let child_flex = child.metadata::().and_then(|style| match axis { Axis2d::X => style.width.flex(), Axis2d::Y => style.height.flex(), @@ -209,7 +224,7 @@ impl Node { align_vertically, ); - for child in &mut self.children { + for child in &mut self.content { // Align each child along the cross axis align_horizontally = !align_horizontally; align_vertically = !align_vertically; @@ -400,7 +415,7 @@ impl Element for Node { }); } - if !self.children.is_empty() { + if !self.content.is_empty() { // Account for padding first. let padding = &self.style.padding; let padded_bounds = RectF::from_points( @@ -442,7 +457,7 @@ impl Element for Node { view: &V, cx: &ViewContext, ) -> Option { - self.children + self.content .iter() .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) } @@ -456,9 +471,10 @@ impl Element for Node { cx: &ViewContext, ) -> Value { json!({ - "type": "Cell", + "type": "Node", "bounds": bounds.to_json(), - "children": self.children.iter().map(|child| child.debug(view, cx)).collect::>() + // TODO! + // "children": self.content.iter().map(|child| child.debug(view, cx)).collect::>() }) } @@ -574,26 +590,30 @@ impl Border { } } -#[derive(Clone, Copy, Default)] -pub enum Length { - #[default] - Hug, - Fixed(f32), - Auto { - flex: f32, - min: f32, - max: f32, - }, -} - -impl From for Length { - fn from(value: f32) -> Self { - Length::Fixed(value) +pub mod length { + #[derive(Clone, Copy, Default)] + pub enum Length { + #[default] + Hug, + Fixed(f32), + Auto { + flex: f32, + min: f32, + max: f32, + }, } -} -impl Length { - pub fn auto(flex: f32) -> Self { + impl From for Length { + fn from(value: f32) -> Self { + Length::Fixed(value) + } + } + + pub fn auto() -> Length { + flex(1.) + } + + pub fn flex(flex: f32) -> Length { Length::Auto { flex, min: 0., @@ -601,7 +621,7 @@ impl Length { } } - pub fn auto_constrained(flex: f32, min: Option, max: Option) -> Self { + pub fn constrained(flex: f32, min: Option, max: Option) -> Length { Length::Auto { flex, min: min.unwrap_or(0.), @@ -609,10 +629,12 @@ impl Length { } } - pub fn flex(&self) -> Option { - match self { - Length::Auto { flex, .. } => Some(*flex), - _ => None, + impl Length { + pub fn flex(&self) -> Option { + match self { + Length::Auto { flex, .. } => Some(*flex), + _ => None, + } } } } diff --git a/crates/gpui/src/elements/text.rs b/crates/gpui/src/elements/text.rs index 66654fbe93..743b4bdf6c 100644 --- a/crates/gpui/src/elements/text.rs +++ b/crates/gpui/src/elements/text.rs @@ -400,6 +400,58 @@ fn layout_highlighted_chunks<'a>( layouts } +// Need to figure out how fonts flow through the tree to implement this. +impl Element for Cow<'static, str> { + type LayoutState = (); + + type PaintState = (); + + fn layout( + &mut self, + constraint: SizeConstraint, + view: &mut V, + cx: &mut LayoutContext, + ) -> (Vector2F, Self::LayoutState) { + todo!() + } + + fn paint( + &mut self, + scene: &mut SceneBuilder, + bounds: RectF, + visible_bounds: RectF, + layout: &mut Self::LayoutState, + view: &mut V, + cx: &mut ViewContext, + ) -> Self::PaintState { + todo!() + } + + fn rect_for_text_range( + &self, + range_utf16: Range, + bounds: RectF, + visible_bounds: RectF, + layout: &Self::LayoutState, + paint: &Self::PaintState, + view: &V, + cx: &ViewContext, + ) -> Option { + todo!() + } + + fn debug( + &self, + bounds: RectF, + layout: &Self::LayoutState, + paint: &Self::PaintState, + view: &V, + cx: &ViewContext, + ) -> crate::serde_json::Value { + todo!() + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/gpui_macros/Cargo.toml b/crates/gpui_macros/Cargo.toml index 76daeae2a8..c11a4af33d 100644 --- a/crates/gpui_macros/Cargo.toml +++ b/crates/gpui_macros/Cargo.toml @@ -10,7 +10,7 @@ proc-macro = true doctest = false [dependencies] +lazy_static.workspace = true +proc-macro2 = "1.0" syn = "1.0" quote = "1.0" -proc-macro2 = "1.0" - diff --git a/crates/gpui_macros/src/gpui_macros.rs b/crates/gpui_macros/src/gpui_macros.rs index 62a4fd04f1..e605aa10e6 100644 --- a/crates/gpui_macros/src/gpui_macros.rs +++ b/crates/gpui_macros/src/gpui_macros.rs @@ -1,10 +1,11 @@ use proc_macro::TokenStream; use proc_macro2::Ident; -use quote::{format_ident, quote}; +use quote::{format_ident, quote, ToTokens}; use std::mem; use syn::{ parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, DeriveInput, FnArg, - ItemFn, Lit, Meta, NestedMeta, Type, + GenericParam, Generics, ItemFn, Lit, Meta, NestedMeta, Type, TypeGenerics, TypeParam, + WhereClause, }; #[proc_macro_attribute] @@ -278,14 +279,44 @@ fn parse_bool(literal: &Lit) -> Result { #[proc_macro_derive(Element)] pub fn element_derive(input: TokenStream) -> TokenStream { - // Parse the input tokens into a syntax tree - let input = parse_macro_input!(input as DeriveInput); + let ast = parse_macro_input!(input as DeriveInput); + let type_name = ast.ident; - // The name of the struct/enum - let name = input.ident; + let placeholder_view_generics: Generics = parse_quote! { }; + let placeholder_view_type_name: Ident = parse_quote! { V }; + let view_type_name: Ident; + let impl_generics: syn::ImplGenerics<'_>; + let type_generics: Option>; + let where_clause: Option<&'_ WhereClause>; + + match ast.generics.params.iter().find_map(|param| { + if let GenericParam::Type(type_param) = param { + Some(type_param.ident.clone()) + } else { + None + } + }) { + Some(type_name) => { + view_type_name = type_name; + let generics = ast.generics.split_for_impl(); + impl_generics = generics.0; + type_generics = Some(generics.1); + where_clause = generics.2; + } + _ => { + view_type_name = placeholder_view_type_name; + let generics = placeholder_view_generics.split_for_impl(); + impl_generics = generics.0; + type_generics = None; + where_clause = generics.2; + } + } + + let gen = quote! { + impl #impl_generics Element<#view_type_name> for #type_name #type_generics + #where_clause + { - let expanded = quote! { - impl gpui::elements::Element for #name { type LayoutState = gpui::elements::AnyElement; type PaintState = (); @@ -337,6 +368,6 @@ pub fn element_derive(input: TokenStream) -> TokenStream { } } }; - // Return generated code - TokenStream::from(expanded) + + gen.into() }