This commit is contained in:
Nathan Sobo 2023-07-24 23:27:14 -06:00
parent 19e4cad7a9
commit 54a7419fa2
13 changed files with 518 additions and 107 deletions

9
Cargo.lock generated
View File

@ -3041,6 +3041,7 @@ dependencies = [
name = "gpui_macros" name = "gpui_macros"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"lazy_static",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn 1.0.109",
@ -5043,9 +5044,17 @@ version = "0.1.0"
dependencies = [ dependencies = [
"gpui", "gpui",
"log", "log",
"playground_ui",
"simplelog", "simplelog",
] ]
[[package]]
name = "playground_ui"
version = "0.1.0"
dependencies = [
"gpui",
]
[[package]] [[package]]
name = "plist" name = "plist"
version = "1.5.0" version = "1.5.0"

View File

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

View File

@ -8,6 +8,8 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
playground_ui = { path = "ui" }
gpui = { path = ".." } gpui = { path = ".." }
log.workspace = true log.workspace = true
simplelog = "0.9" simplelog = "0.9"

View File

@ -1,52 +1,45 @@
use elements::{Length, Node, NodeStyle}; use std::ops::{Deref, DerefMut};
use gpui::{color::Color, AnyElement, Element, Entity, View, ViewContext};
use gpui::{AnyElement, Element, Entity, View};
use log::LevelFilter; use log::LevelFilter;
use simplelog::SimpleLogger; use simplelog::SimpleLogger;
mod elements;
fn main() { fn main() {
SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger"); SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
gpui::App::new(()).unwrap().run(|cx| { gpui::App::new(()).unwrap().run(|cx| {
cx.platform().activate(true); 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 = (); type Event = ();
} }
impl View for PlaygroundView { impl View for Playground {
fn ui_name() -> &'static str { fn ui_name() -> &'static str {
"PlaygroundView" "PlaygroundView"
} }
fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<PlaygroundView> { fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> AnyElement<Playground> {
// Node::with_style(NodeStyle) self.0.clone().into_any()
// 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()
} }
} }

View File

@ -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 = "../.." }

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -17,33 +17,73 @@ use serde_json::json;
#[repr(transparent)] #[repr(transparent)]
pub struct Color(#[schemars(with = "String")] ColorU); 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 { impl Color {
pub fn transparent_black() -> Self { pub fn transparent_black() -> Self {
Self(ColorU::transparent_black()) transparent_black()
} }
pub fn black() -> Self { pub fn black() -> Self {
Self(ColorU::black()) black()
} }
pub fn white() -> Self { pub fn white() -> Self {
Self(ColorU::white()) white()
} }
pub fn red() -> Self { pub fn red() -> Self {
Self(ColorU::from_u32(0xff0000ff)) Color::from_u32(0xff0000ff)
} }
pub fn green() -> Self { pub fn green() -> Self {
Self(ColorU::from_u32(0x00ff00ff)) Color::from_u32(0x00ff00ff)
} }
pub fn blue() -> Self { pub fn blue() -> Self {
Self(ColorU::from_u32(0x0000ffff)) Color::from_u32(0x0000ffff)
} }
pub fn yellow() -> Self { 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 { pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {

View File

@ -12,6 +12,7 @@ mod keystroke_label;
mod label; mod label;
mod list; mod list;
mod mouse_event_handler; mod mouse_event_handler;
pub mod node;
mod overlay; mod overlay;
mod resizable; mod resizable;
mod stack; mod stack;

View File

@ -1,4 +1,4 @@
use gpui::{ use crate::{
color::Color, color::Color,
geometry::{ geometry::{
rect::RectF, rect::RectF,
@ -9,47 +9,62 @@ use gpui::{
serde_json::Value, serde_json::Value,
AnyElement, Element, LayoutContext, Quad, SceneBuilder, SizeConstraint, View, ViewContext, 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. use self::length::Length;
//
// 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
//
//
pub struct Node<V: View> { pub struct Node<V: View> {
style: NodeStyle, 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> { impl<V: View> Default for Node<V> {
fn default() -> Self { fn default() -> Self {
Self { Self {
style: Default::default(), style: Default::default(),
children: Default::default(), content: Default::default(),
} }
} }
} }
impl<V: View> Node<V> { impl<V: View> Node<V> {
pub fn new() -> Self {
Self::default()
}
pub fn child(mut self, child: impl Element<V>) -> Self { pub fn child(mut self, child: impl Element<V>) -> Self {
self.children.push(child.into_any()); self.content.push(child.into_any());
self self
} }
@ -58,7 +73,7 @@ impl<V: View> Node<V> {
I: IntoIterator<Item = E>, I: IntoIterator<Item = E>,
E: Element<V>, E: Element<V>,
{ {
self.children self.content
.extend(children.into_iter().map(|child| child.into_any())); .extend(children.into_iter().map(|child| child.into_any()));
self self
} }
@ -100,7 +115,7 @@ impl<V: View> Node<V> {
let mut cross_axis_max: f32 = 0.0; let mut cross_axis_max: f32 = 0.0;
// First pass: Layout non-flex children only // 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 { let child_flex = child.metadata::<NodeStyle>().and_then(|style| match axis {
Axis2d::X => style.width.flex(), Axis2d::X => style.width.flex(),
Axis2d::Y => style.height.flex(), Axis2d::Y => style.height.flex(),
@ -138,7 +153,7 @@ impl<V: View> Node<V> {
if total_flex > 0. { if total_flex > 0. {
let space_per_flex = remaining_space.max(0.) / total_flex; 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 { let child_flex = child.metadata::<NodeStyle>().and_then(|style| match axis {
Axis2d::X => style.width.flex(), Axis2d::X => style.width.flex(),
Axis2d::Y => style.height.flex(), Axis2d::Y => style.height.flex(),
@ -209,7 +224,7 @@ impl<V: View> Node<V> {
align_vertically, align_vertically,
); );
for child in &mut self.children { for child in &mut self.content {
// Align each child along the cross axis // Align each child along the cross axis
align_horizontally = !align_horizontally; align_horizontally = !align_horizontally;
align_vertically = !align_vertically; 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. // Account for padding first.
let padding = &self.style.padding; let padding = &self.style.padding;
let padded_bounds = RectF::from_points( let padded_bounds = RectF::from_points(
@ -442,7 +457,7 @@ impl<V: View> Element<V> for Node<V> {
view: &V, view: &V,
cx: &ViewContext<V>, cx: &ViewContext<V>,
) -> Option<RectF> { ) -> Option<RectF> {
self.children self.content
.iter() .iter()
.find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx)) .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>, cx: &ViewContext<V>,
) -> Value { ) -> Value {
json!({ json!({
"type": "Cell", "type": "Node",
"bounds": bounds.to_json(), "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 mod length {
pub enum Length { #[derive(Clone, Copy, Default)]
#[default] pub enum Length {
Hug, #[default]
Fixed(f32), Hug,
Auto { Fixed(f32),
flex: f32, Auto {
min: f32, flex: f32,
max: f32, min: f32,
}, max: f32,
} },
impl From<f32> for Length {
fn from(value: f32) -> Self {
Length::Fixed(value)
} }
}
impl Length { impl From<f32> for Length {
pub fn auto(flex: f32) -> Self { fn from(value: f32) -> Self {
Length::Fixed(value)
}
}
pub fn auto() -> Length {
flex(1.)
}
pub fn flex(flex: f32) -> Length {
Length::Auto { Length::Auto {
flex, flex,
min: 0., 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 { Length::Auto {
flex, flex,
min: min.unwrap_or(0.), min: min.unwrap_or(0.),
@ -609,10 +629,12 @@ impl Length {
} }
} }
pub fn flex(&self) -> Option<f32> { impl Length {
match self { pub fn flex(&self) -> Option<f32> {
Length::Auto { flex, .. } => Some(*flex), match self {
_ => None, Length::Auto { flex, .. } => Some(*flex),
_ => None,
}
} }
} }
} }

View File

@ -400,6 +400,58 @@ fn layout_highlighted_chunks<'a>(
layouts 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -10,7 +10,7 @@ proc-macro = true
doctest = false doctest = false
[dependencies] [dependencies]
lazy_static.workspace = true
proc-macro2 = "1.0"
syn = "1.0" syn = "1.0"
quote = "1.0" quote = "1.0"
proc-macro2 = "1.0"

View File

@ -1,10 +1,11 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Ident; use proc_macro2::Ident;
use quote::{format_ident, quote}; use quote::{format_ident, quote, ToTokens};
use std::mem; use std::mem;
use syn::{ use syn::{
parse_macro_input, parse_quote, spanned::Spanned as _, AttributeArgs, DeriveInput, FnArg, 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] #[proc_macro_attribute]
@ -278,14 +279,44 @@ fn parse_bool(literal: &Lit) -> Result<bool, TokenStream> {
#[proc_macro_derive(Element)] #[proc_macro_derive(Element)]
pub fn element_derive(input: TokenStream) -> TokenStream { pub fn element_derive(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree let ast = parse_macro_input!(input as DeriveInput);
let input = parse_macro_input!(input as DeriveInput); let type_name = ast.ident;
// The name of the struct/enum let placeholder_view_generics: Generics = parse_quote! { <V: View> };
let name = input.ident; 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 LayoutState = gpui::elements::AnyElement<V>;
type PaintState = (); type PaintState = ();
@ -337,6 +368,6 @@ pub fn element_derive(input: TokenStream) -> TokenStream {
} }
} }
}; };
// Return generated code
TokenStream::from(expanded) gen.into()
} }