Detailed changes
@@ -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"
@@ -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",
@@ -8,6 +8,8 @@ version = "0.1.0"
edition = "2021"
[dependencies]
+playground_ui = { path = "ui" }
+
gpui = { path = ".." }
log.workspace = true
simplelog = "0.9"
@@ -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 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 PlaygroundView {
+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<Self>) -> AnyElement<PlaygroundView> {
- // 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<Self>) -> AnyElement<Playground> {
+ self.0.clone().into_any()
}
}
@@ -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 = "../.." }
@@ -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<V: View>(&mut self, _: &mut V, _: &mut gpui::ViewContext<V>) -> AnyElement<V> {
+ column()
+ .width(auto())
+ .child(dialog(
+ "This is a dialog",
+ "You would see a description here.",
+ ))
+ .into_any()
+ }
+}
+
+pub trait DialogDelegate<V: View>: 'static {
+ fn handle_submit<B>(&mut self, view: &mut V, button: B);
+}
+
+impl<V: View> DialogDelegate<V> for () {
+ fn handle_submit<B>(&mut self, _: &mut V, _: B) {}
+}
+
+#[derive(Element)]
+pub struct Dialog<V: View, D: DialogDelegate<V>> {
+ title: Cow<'static, str>,
+ description: Cow<'static, str>,
+ delegate: Option<D>,
+ view_type: PhantomData<V>,
+}
+
+pub fn dialog<V: View>(
+ title: impl Into<Cow<'static, str>>,
+ description: impl Into<Cow<'static, str>>,
+) -> Dialog<V, ()> {
+ Dialog {
+ title: title.into(),
+ description: description.into(),
+ delegate: None,
+ view_type: PhantomData,
+ }
+}
+
+impl<V: View, D: DialogDelegate<V>> Dialog<V, D> {
+ pub fn with_delegate(mut self, delegate: D) -> Dialog<V, D> {
+ let old_delegate = self.delegate.replace(delegate);
+ debug_assert!(old_delegate.is_none(), "delegate already set");
+ self
+ }
+}
+
+struct Button<V: View, F: FnOnce(&mut V, &mut ViewContext<V>)> {
+ label: Cow<'static, str>,
+ on_click: Option<F>,
+ view_type: PhantomData<V>,
+}
+
+fn button<V: View, F: FnOnce(&mut V, &mut ViewContext<V>)>(
+ label: impl Into<Cow<'static, str>>,
+) -> Button<V, F> {
+ Button {
+ label: label.into(),
+ on_click: None,
+ view_type: PhantomData,
+ }
+}
+
+impl<V: View, 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([
+ button("Cancel").margin_left(auto()),
+ button("OK").margin_left(m4()),
+ ]))
+ .into_any()
+ }
+}
@@ -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
+ }
+}
@@ -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 {
@@ -12,6 +12,7 @@ mod keystroke_label;
mod label;
mod list;
mod mouse_event_handler;
+pub mod node;
mod overlay;
mod resizable;
mod stack;
@@ -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};
-
-// 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 std::{any::Any, borrow::Cow, f32, ops::Range};
+
+use self::length::Length;
pub struct Node<V: View> {
style: NodeStyle,
- children: Vec<AnyElement<V>>,
+ content: Content<V>,
+}
+
+enum Content<V: View> {
+ Children(Vec<AnyElement<V>>),
+ Text(Cow<'static, str>),
+}
+
+impl<V: View> Default for Content<V> {
+ fn default() -> Self {
+ Self::Children(Vec::new())
+ }
+}
+
+pub fn column<V: View>() -> Node<V> {
+ Node::default()
+}
+
+pub fn row<V: View>() -> Node<V> {
+ Node {
+ style: NodeStyle {
+ axis: Axis3d::X,
+ ..Default::default()
+ },
+ content: Default::default(),
+ }
+}
+
+pub fn stack<V: View>() -> Node<V> {
+ Node {
+ style: NodeStyle {
+ axis: Axis3d::Z,
+ ..Default::default()
+ },
+ content: Default::default(),
+ }
}
impl<V: View> Default for Node<V> {
fn default() -> Self {
Self {
style: Default::default(),
- children: Default::default(),
+ content: Default::default(),
}
}
}
impl<V: View> Node<V> {
- pub fn new() -> Self {
- Self::default()
- }
-
pub fn child(mut self, child: impl Element<V>) -> Self {
- self.children.push(child.into_any());
+ self.content.push(child.into_any());
self
}
@@ -58,7 +73,7 @@ impl<V: View> Node<V> {
I: IntoIterator<Item = E>,
E: Element<V>,
{
- self.children
+ self.content
.extend(children.into_iter().map(|child| child.into_any()));
self
}
@@ -100,7 +115,7 @@ impl<V: View> Node<V> {
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::<NodeStyle>().and_then(|style| match axis {
Axis2d::X => style.width.flex(),
Axis2d::Y => style.height.flex(),
@@ -138,7 +153,7 @@ impl<V: View> Node<V> {
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::<NodeStyle>().and_then(|style| match axis {
Axis2d::X => style.width.flex(),
Axis2d::Y => style.height.flex(),
@@ -209,7 +224,7 @@ impl<V: View> Node<V> {
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<V: View> Element<V> for Node<V> {
});
}
- 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<V: View> Element<V> for Node<V> {
view: &V,
cx: &ViewContext<V>,
) -> Option<RectF> {
- self.children
+ self.content
.iter()
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
}
@@ -456,9 +471,10 @@ impl<V: View> Element<V> for Node<V> {
cx: &ViewContext<V>,
) -> Value {
json!({
- "type": "Cell",
+ "type": "Node",
"bounds": bounds.to_json(),
- "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<Value>>()
+ // TODO!
+ // "children": self.content.iter().map(|child| child.debug(view, cx)).collect::<Vec<Value>>()
})
}
@@ -574,26 +590,30 @@ impl Border {
}
}
-#[derive(Clone, Copy, Default)]
-pub enum Length {
- #[default]
- Hug,
- Fixed(f32),
- Auto {
- flex: f32,
- min: f32,
- max: f32,
- },
-}
+pub mod length {
+ #[derive(Clone, Copy, Default)]
+ pub enum Length {
+ #[default]
+ Hug,
+ Fixed(f32),
+ Auto {
+ flex: f32,
+ min: f32,
+ max: f32,
+ },
+ }
+
+ impl From<f32> for Length {
+ fn from(value: f32) -> Self {
+ Length::Fixed(value)
+ }
+ }
-impl From<f32> for Length {
- fn from(value: f32) -> Self {
- Length::Fixed(value)
+ pub fn auto() -> Length {
+ flex(1.)
}
-}
-impl Length {
- pub fn auto(flex: f32) -> Self {
+ 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<f32>, max: Option<f32>) -> Self {
+ pub fn constrained(flex: f32, min: Option<f32>, max: Option<f32>) -> Length {
Length::Auto {
flex,
min: min.unwrap_or(0.),
@@ -609,10 +629,12 @@ impl Length {
}
}
- pub fn flex(&self) -> Option<f32> {
- match self {
- Length::Auto { flex, .. } => Some(*flex),
- _ => None,
+ impl Length {
+ pub fn flex(&self) -> Option<f32> {
+ match self {
+ Length::Auto { flex, .. } => Some(*flex),
+ _ => None,
+ }
}
}
}
@@ -400,6 +400,58 @@ fn layout_highlighted_chunks<'a>(
layouts
}
+// Need to figure out how fonts flow through the tree to implement this.
+impl<V: View> Element<V> for Cow<'static, str> {
+ type LayoutState = ();
+
+ type PaintState = ();
+
+ fn layout(
+ &mut self,
+ constraint: SizeConstraint,
+ view: &mut V,
+ cx: &mut LayoutContext<V>,
+ ) -> (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<V>,
+ ) -> Self::PaintState {
+ todo!()
+ }
+
+ fn rect_for_text_range(
+ &self,
+ range_utf16: Range<usize>,
+ bounds: RectF,
+ visible_bounds: RectF,
+ layout: &Self::LayoutState,
+ paint: &Self::PaintState,
+ view: &V,
+ cx: &ViewContext<V>,
+ ) -> Option<RectF> {
+ todo!()
+ }
+
+ fn debug(
+ &self,
+ bounds: RectF,
+ layout: &Self::LayoutState,
+ paint: &Self::PaintState,
+ view: &V,
+ cx: &ViewContext<V>,
+ ) -> crate::serde_json::Value {
+ todo!()
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
@@ -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"
-
@@ -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<bool, TokenStream> {
#[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! { <V: View> };
+ let placeholder_view_type_name: Ident = parse_quote! { V };
+ let view_type_name: Ident;
+ let impl_generics: syn::ImplGenerics<'_>;
+ let type_generics: Option<syn::TypeGenerics<'_>>;
+ 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<V: gpui::View> gpui::elements::Element<V> for #name {
type LayoutState = gpui::elements::AnyElement<V>;
type PaintState = ();
@@ -337,6 +368,6 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
}
}
};
- // Return generated code
- TokenStream::from(expanded)
+
+ gen.into()
}