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"
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"

View File

@ -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",

View File

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

View File

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

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)]
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 {

View File

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

View File

@ -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<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,
},
}
impl From<f32> 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<f32> 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<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,
}
}
}
}

View File

@ -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::*;

View File

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

View File

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