Get text rendering

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
This commit is contained in:
Nathan Sobo 2023-08-16 12:50:35 -06:00
parent fea987b459
commit f1aafab61d
15 changed files with 437 additions and 71 deletions

1
Cargo.lock generated
View File

@ -5304,6 +5304,7 @@ dependencies = [
"gpui",
"log",
"optional_struct",
"parking_lot 0.11.2",
"playground_macros",
"serde",
"simplelog",

View File

@ -14,6 +14,7 @@ gpui = { path = ".." }
log.workspace = true
optional_struct = "0.3.1"
playground_macros = { path = "../playground_macros" }
parking_lot.workspace = true
serde.workspace = true
simplelog = "0.9"
smallvec.workspace = true

View File

@ -116,6 +116,15 @@ pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla {
}
}
pub fn black() -> Hsla {
Hsla {
h: 0.,
s: 0.,
l: 0.,
a: 1.,
}
}
impl From<Rgba> for Hsla {
fn from(color: Rgba) -> Self {
let r = color.r;

View File

@ -1,11 +1,12 @@
use crate::{
element::{Element, ElementMetadata},
frame,
text::ArcCow,
themes::rose_pine,
};
use gpui::ViewContext;
use playground_macros::Element;
use std::{borrow::Cow, marker::PhantomData, rc::Rc};
use std::{marker::PhantomData, rc::Rc};
struct ButtonHandlers<V, D> {
click: Option<Rc<dyn Fn(&mut V, &D)>>,
@ -22,8 +23,8 @@ impl<V, D> Default for ButtonHandlers<V, D> {
pub struct Button<V: 'static, D: 'static> {
metadata: ElementMetadata<V>,
handlers: ButtonHandlers<V, D>,
label: Option<Cow<'static, str>>,
icon: Option<Cow<'static, str>>,
label: Option<ArcCow<'static, str>>,
icon: Option<ArcCow<'static, str>>,
data: Rc<D>,
view_type: PhantomData<V>,
}
@ -56,17 +57,17 @@ impl<V: 'static> Button<V, ()> {
// Impl block for *any* button.
impl<V: 'static, D: 'static> Button<V, D> {
fn label(mut self, label: impl Into<Cow<'static, str>>) -> Self {
pub fn label(mut self, label: impl Into<ArcCow<'static, str>>) -> Self {
self.label = Some(label.into());
self
}
fn icon(mut self, icon: impl Into<Cow<'static, str>>) -> Self {
pub fn icon(mut self, icon: impl Into<ArcCow<'static, str>>) -> Self {
self.icon = Some(icon.into());
self
}
fn click(self, handler: impl Fn(&mut V, &D) + 'static) -> Self {
pub fn click(self, handler: impl Fn(&mut V, &D) + 'static) -> Self {
let data = self.data.clone();
Element::click(self, move |view, _| {
handler(view, data.as_ref());
@ -80,8 +81,11 @@ pub fn button<V>() -> Button<V, ()> {
impl<V: 'static, D: 'static> Button<V, D> {
fn render(&mut self, view: &mut V, cx: &mut ViewContext<V>) -> impl Element<V> {
// TODO: Drive from the context
let button = frame().fill(rose_pine::dawn().error(0.5)).h_5().w_9();
// TODO: Drive theme from the context
let button = frame()
.fill(rose_pine::dawn().error(0.5))
.h_4()
.children(self.label.clone());
if let Some(handler) = self.handlers.click.clone() {
let data = self.data.clone();

View File

@ -1,5 +1,6 @@
use crate::{
adapter::Adapter,
color::Hsla,
style::{Display, ElementStyle, Fill, Overflow, Position},
};
use anyhow::Result;
@ -8,7 +9,7 @@ pub use gpui::LayoutContext;
use gpui::{
geometry::{DefinedLength, Length},
scene::MouseClick,
EngineLayout, PaintContext as LegacyPaintContext,
EngineLayout, PaintContext as LegacyPaintContext, RenderContext,
};
use playground_macros::tailwind_lengths;
use std::{any::Any, rc::Rc};
@ -22,6 +23,20 @@ pub struct PaintContext<'a, 'b, 'c, 'd, V> {
pub(crate) scene: &'d mut gpui::SceneBuilder,
}
impl<V> RenderContext for PaintContext<'_, '_, '_, '_, V> {
fn text_style(&self) -> gpui::fonts::TextStyle {
self.legacy_cx.text_style()
}
fn push_text_style(&mut self, style: gpui::fonts::TextStyle) {
self.legacy_cx.push_text_style(style)
}
fn pop_text_style(&mut self) {
self.legacy_cx.pop_text_style()
}
}
pub struct Layout<'a, E: ?Sized> {
pub from_engine: EngineLayout,
pub from_element: &'a mut E,
@ -303,9 +318,18 @@ pub trait Element<V: 'static>: 'static {
self.style_mut().fill = fill.into();
self
}
fn text_color(mut self, color: impl Into<Hsla>) -> Self
where
Self: Sized,
{
self.style_mut().text_color = Some(color.into());
self
}
}
pub trait ElementObject<V> {
// Object-safe counterpart of Element used by AnyElement to store elements as trait objects.
trait ElementObject<V> {
fn style_mut(&mut self) -> &mut ElementStyle;
fn handlers_mut(&mut self) -> &mut ElementHandlers<V>;
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>)
@ -360,12 +384,33 @@ pub struct AnyElement<V> {
impl<V> AnyElement<V> {
pub fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<NodeId> {
let pushed_text_style = self.push_text_style(cx);
let (node_id, layout) = self.element.layout(view, cx)?;
self.layout = Some((node_id, layout));
if pushed_text_style {
cx.pop_text_style();
}
Ok(node_id)
}
pub fn push_text_style(&mut self, cx: &mut impl RenderContext) -> bool {
let text_style = self.element.style_mut().text_style();
if let Some(text_style) = text_style {
let mut current_text_style = cx.text_style();
text_style.apply(&mut current_text_style);
cx.push_text_style(current_text_style);
true
} else {
false
}
}
pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) -> Result<()> {
let pushed_text_style = self.push_text_style(cx);
let (layout_node_id, element_layout) =
self.layout.as_mut().expect("paint called before layout");
@ -378,7 +423,12 @@ impl<V> AnyElement<V> {
from_element: element_layout.as_mut(),
};
self.element.paint(layout, view, cx)
self.element.paint(layout, view, cx)?;
if pushed_text_style {
cx.pop_text_style();
}
Ok(())
}
}
@ -405,3 +455,16 @@ impl<V: 'static> Element<V> for AnyElement<V> {
self.paint(view, cx)
}
}
pub trait IntoElement<V: 'static> {
type Element: Element<V>;
fn into_element(self) -> Self::Element;
fn into_any_element(self) -> AnyElement<V>
where
Self: Sized,
{
self.into_element().into_any()
}
}

View File

@ -1,11 +1,17 @@
use crate::{
element::{AnyElement, Element, ElementHandlers, Layout, LayoutContext, NodeId, PaintContext},
element::{
AnyElement, Element, ElementHandlers, IntoElement, Layout, LayoutContext, NodeId,
PaintContext,
},
style::ElementStyle,
};
use anyhow::{anyhow, Result};
use gpui::LayoutNodeId;
use playground_macros::IntoElement;
pub struct Frame<V> {
#[derive(IntoElement)]
#[element_crate = "crate"]
pub struct Frame<V: 'static> {
style: ElementStyle,
handlers: ElementHandlers<V>,
children: Vec<AnyElement<V>>,
@ -66,8 +72,18 @@ impl<V: 'static> Element<V> for Frame<V> {
}
impl<V: 'static> Frame<V> {
pub fn child(mut self, child: impl Element<V>) -> Self {
self.children.push(child.into_any());
pub fn child(mut self, child: impl IntoElement<V>) -> Self {
self.children.push(child.into_any_element());
self
}
pub fn children<I, E>(mut self, children: I) -> Self
where
I: IntoIterator<Item = E>,
E: IntoElement<V>,
{
self.children
.extend(children.into_iter().map(|e| e.into_any_element()));
self
}
}

View File

@ -1,9 +1,10 @@
#![allow(dead_code, unused_variables)]
use color::black;
use components::button;
use element::Element;
use frame::frame;
use gpui::{
geometry::{percent, rect::RectF, vector::vec2f},
geometry::{rect::RectF, vector::vec2f},
platform::WindowOptions,
};
use log::LevelFilter;
@ -35,19 +36,21 @@ fn main() {
center: true,
..Default::default()
},
|_| view(|_| workspace(&rose_pine::moon())),
|_| view(|_| playground(&rose_pine::moon())),
);
cx.platform().activate(true);
});
}
fn workspace<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
frame()
.text_color(black())
.h_full()
.w(percent(50.))
.w_half()
.fill(theme.success(0.5))
.child(button())
.child(button().label("Hello").click(|_, _| (println!("hey!"))))
}
// todo!()
// // column()
// // .size(auto())

View File

@ -66,6 +66,8 @@ pub struct ElementStyle {
/// The fill color of this element
pub fill: Fill,
/// The color of text within this element. Cascades to children unless overridden.
pub text_color: Option<Hsla>,
}
impl ElementStyle {
@ -103,6 +105,7 @@ impl ElementStyle {
l: 0.,
a: 0.,
}),
text_color: None,
};
pub fn new() -> Self {
@ -136,6 +139,16 @@ impl ElementStyle {
..Default::default() // Ignore grid properties for now
}
}
pub fn text_style(&self) -> Option<OptionalTextStyle> {
if self.text_color.is_some() {
Some(OptionalTextStyle {
color: self.text_color,
})
} else {
None
}
}
}
impl Default for ElementStyle {
@ -144,6 +157,18 @@ impl Default for ElementStyle {
}
}
pub struct OptionalTextStyle {
color: Option<Hsla>,
}
impl OptionalTextStyle {
pub fn apply(&self, style: &mut gpui::fonts::TextStyle) {
if let Some(color) = self.color {
style.color = color.into();
}
}
}
#[derive(Clone)]
pub enum Fill {
Color(Hsla),

View File

@ -1,36 +1,155 @@
use std::borrow::Cow;
use crate::element::{Element, ElementMetadata, IntoElement};
use gpui::{geometry::Size, text_layout::LineLayout, RenderContext};
use parking_lot::Mutex;
use std::sync::Arc;
use crate::element::Element;
impl<V: 'static, S: Into<ArcCow<'static, str>>> IntoElement<V> for S {
type Element = Text<V>;
impl<V, S> Element<V> for S
where
V: 'static,
S: 'static + Into<Cow<'static, str>>,
{
type Layout = Cow<'static, str>;
fn into_element(self) -> Self::Element {
Text {
text: self.into(),
metadata: Default::default(),
}
}
}
pub struct Text<V> {
text: ArcCow<'static, str>,
metadata: ElementMetadata<V>,
}
impl<V: 'static> Element<V> for Text<V> {
type Layout = Arc<Mutex<Option<TextLayout>>>;
fn style_mut(&mut self) -> &mut crate::style::ElementStyle {
todo!()
&mut self.metadata.style
}
fn handlers_mut(&mut self) -> &mut crate::element::ElementHandlers<V> {
todo!()
&mut self.metadata.handlers
}
fn layout(
&mut self,
view: &mut V,
cx: &mut crate::element::LayoutContext<V>,
cx: &mut gpui::LayoutContext<V>,
) -> anyhow::Result<(taffy::tree::NodeId, Self::Layout)> {
todo!()
let rem_size = cx.rem_pixels();
let fonts = cx.platform().fonts();
let text_style = cx.text_style();
let line_height = cx.font_cache().line_height(text_style.font_size);
let layout_engine = cx.layout_engine().expect("no layout engine present");
let text = self.text.clone();
let layout = Arc::new(Mutex::new(None));
let node_id = layout_engine.add_measured_node(self.metadata.style.to_taffy(rem_size), {
let layout = layout.clone();
move |params| {
let line_layout = fonts.layout_line(
text.as_ref(),
text_style.font_size,
&[(text.len(), text_style.to_run())],
);
let size = Size {
width: line_layout.width,
height: line_height,
};
layout.lock().replace(TextLayout {
line_layout: Arc::new(line_layout),
line_height,
});
size
}
})?;
Ok((node_id, layout))
}
fn paint<'a>(
&mut self,
layout: crate::element::Layout<Self::Layout>,
layout: crate::element::Layout<Arc<Mutex<Option<TextLayout>>>>,
view: &mut V,
cx: &mut crate::element::PaintContext<V>,
) -> anyhow::Result<()> {
todo!()
let element_layout_lock = layout.from_element.lock();
let element_layout = element_layout_lock
.as_ref()
.expect("layout has not been performed");
let line_layout = element_layout.line_layout.clone();
let line_height = element_layout.line_height;
drop(element_layout_lock);
let text_style = cx.text_style();
let line =
gpui::text_layout::Line::new(line_layout, &[(self.text.len(), text_style.to_run())]);
line.paint(
cx.scene,
layout.from_engine.bounds.origin(),
layout.from_engine.bounds,
line_height,
cx.legacy_cx,
);
Ok(())
}
}
pub struct TextLayout {
line_layout: Arc<LineLayout>,
line_height: f32,
}
pub enum ArcCow<'a, T: ?Sized> {
Borrowed(&'a T),
Owned(Arc<T>),
}
impl<'a, T: ?Sized> Clone for ArcCow<'a, T> {
fn clone(&self) -> Self {
match self {
Self::Borrowed(borrowed) => Self::Borrowed(borrowed),
Self::Owned(owned) => Self::Owned(owned.clone()),
}
}
}
impl<'a, T: ?Sized> From<&'a T> for ArcCow<'a, T> {
fn from(s: &'a T) -> Self {
Self::Borrowed(s)
}
}
impl<T> From<Arc<T>> for ArcCow<'_, T> {
fn from(s: Arc<T>) -> Self {
Self::Owned(s)
}
}
impl From<String> for ArcCow<'_, str> {
fn from(value: String) -> Self {
Self::Owned(value.into())
}
}
impl<T: ?Sized> std::ops::Deref for ArcCow<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
ArcCow::Borrowed(s) => s,
ArcCow::Owned(s) => s.as_ref(),
}
}
}
impl<T: ?Sized> AsRef<T> for ArcCow<'_, T> {
fn as_ref(&self) -> &T {
match self {
ArcCow::Borrowed(borrowed) => borrowed,
ArcCow::Owned(owned) => owned.as_ref(),
}
}
}

View File

@ -5,6 +5,8 @@ use syn::{
WhereClause,
};
use crate::derive_into_element::impl_into_element;
pub fn derive_element(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let type_name = ast.ident;
@ -62,6 +64,15 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
}
}
let impl_into_element = impl_into_element(
&impl_generics,
&crate_name,
&view_type_name,
&type_name,
&type_generics,
&where_clause,
);
let gen = quote! {
impl #impl_generics #crate_name::element::Element<#view_type_name> for #type_name #type_generics
#where_clause
@ -96,6 +107,8 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
Ok(())
}
}
#impl_into_element
};
gen.into()

View File

@ -0,0 +1,95 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{
parse_macro_input, parse_quote, DeriveInput, GenericParam, Generics, Ident, Lit, Meta,
WhereClause,
};
pub fn derive_into_element(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let type_name = ast.ident;
let crate_name: String = ast
.attrs
.iter()
.find_map(|attr| {
if attr.path.is_ident("element_crate") {
match attr.parse_meta() {
Ok(Meta::NameValue(nv)) => {
if let Lit::Str(s) = nv.lit {
Some(s.value())
} else {
None
}
}
_ => None,
}
} else {
None
}
})
.unwrap_or_else(|| String::from("playground"));
let crate_name = format_ident!("{}", crate_name);
let placeholder_view_generics: Generics = parse_quote! { <V: 'static> };
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;
}
}
impl_into_element(
&impl_generics,
&crate_name,
&view_type_name,
&type_name,
&type_generics,
&where_clause,
)
.into()
}
pub fn impl_into_element(
impl_generics: &syn::ImplGenerics<'_>,
crate_name: &Ident,
view_type_name: &Ident,
type_name: &Ident,
type_generics: &Option<syn::TypeGenerics<'_>>,
where_clause: &Option<&WhereClause>,
) -> proc_macro2::TokenStream {
quote! {
impl #impl_generics #crate_name::element::IntoElement<#view_type_name> for #type_name #type_generics
#where_clause
{
type Element = Self;
fn into_element(self) -> Self {
self
}
}
}
}

View File

@ -1,14 +1,20 @@
use proc_macro::TokenStream;
mod derive_element;
mod derive_into_element;
mod tailwind_lengths;
#[proc_macro_attribute]
pub fn tailwind_lengths(attr: TokenStream, item: TokenStream) -> TokenStream {
tailwind_lengths::tailwind_lengths(attr, item)
}
#[proc_macro_derive(Element, attributes(element_crate))]
pub fn derive_element(input: TokenStream) -> TokenStream {
derive_element::derive_element(input)
}
#[proc_macro_derive(IntoElement, attributes(element_crate))]
pub fn derive_into_element(input: TokenStream) -> TokenStream {
derive_into_element::derive_into_element(input)
}
#[proc_macro_attribute]
pub fn tailwind_lengths(attr: TokenStream, item: TokenStream) -> TokenStream {
tailwind_lengths::tailwind_lengths(attr, item)
}

View File

@ -3361,11 +3361,21 @@ impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
}
}
/// Methods shared by both LayoutContext and PaintContext
///
/// It's that PaintContext should be implemented in terms of layout context and
/// deref to it, in which case we wouldn't need this.
pub trait RenderContext {
fn text_style(&self) -> TextStyle;
fn push_text_style(&mut self, style: TextStyle);
fn pop_text_style(&mut self);
}
pub struct LayoutContext<'a, 'b, 'c, V> {
view_context: &'c mut ViewContext<'a, 'b, V>,
new_parents: &'c mut HashMap<usize, usize>,
views_to_notify_if_ancestors_change: &'c mut HashMap<usize, SmallVec<[usize; 2]>>,
text_style_stack: Vec<Arc<TextStyle>>,
text_style_stack: Vec<TextStyle>,
pub refreshing: bool,
}
@ -3433,24 +3443,8 @@ impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
.push(self_view_id);
}
pub fn text_style(&self) -> Arc<TextStyle> {
self.text_style_stack
.last()
.cloned()
.unwrap_or(Arc::new(TextStyle::default(&self.font_cache)))
}
pub fn push_text_style<S: Into<Arc<TextStyle>>>(&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<S, F, T>(&mut self, style: S, f: F) -> T
pub fn with_text_style<F, T>(&mut self, style: TextStyle, f: F) -> T
where
S: Into<Arc<TextStyle>>,
F: FnOnce(&mut Self) -> T,
{
self.push_text_style(style);
@ -3460,6 +3454,23 @@ impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
}
}
impl<'a, 'b, 'c, V> RenderContext for LayoutContext<'a, 'b, 'c, V> {
fn text_style(&self) -> TextStyle {
self.text_style_stack
.last()
.cloned()
.unwrap_or(TextStyle::default(&self.font_cache))
}
fn push_text_style(&mut self, style: TextStyle) {
self.text_style_stack.push(style);
}
fn pop_text_style(&mut self) {
self.text_style_stack.pop();
}
}
impl<'a, 'b, 'c, V> Deref for LayoutContext<'a, 'b, 'c, V> {
type Target = ViewContext<'a, 'b, V>;
@ -3516,7 +3527,7 @@ impl<V> BorrowWindowContext for LayoutContext<'_, '_, '_, V> {
pub struct PaintContext<'a, 'b, 'c, V> {
view_context: &'c mut ViewContext<'a, 'b, V>,
text_style_stack: Vec<Arc<TextStyle>>,
text_style_stack: Vec<TextStyle>,
}
impl<'a, 'b, 'c, V> PaintContext<'a, 'b, 'c, V> {
@ -3526,23 +3537,22 @@ impl<'a, 'b, 'c, V> PaintContext<'a, 'b, 'c, V> {
text_style_stack: Vec::new(),
}
}
}
pub fn text_style(&self) -> Arc<TextStyle> {
impl<'a, 'b, 'c, V> RenderContext for PaintContext<'a, 'b, 'c, V> {
fn text_style(&self) -> TextStyle {
self.text_style_stack
.last()
.cloned()
.unwrap_or(Arc::new(TextStyle::default(&self.font_cache)))
.unwrap_or(TextStyle::default(&self.font_cache))
}
pub fn with_text_style<S, F, T>(&mut self, style: S, f: F) -> T
where
S: Into<Arc<TextStyle>>,
F: FnOnce(&mut Self) -> T,
{
self.text_style_stack.push(style.into());
let result = f(self);
fn push_text_style(&mut self, style: TextStyle) {
self.text_style_stack.push(style);
}
fn pop_text_style(&mut self) {
self.text_style_stack.pop();
result
}
}

View File

@ -1300,6 +1300,7 @@ where
}
}
#[derive(Debug, Clone)]
pub struct EngineLayout {
pub bounds: RectF,
pub order: u32,

View File

@ -212,7 +212,7 @@ pub struct Glyph {
}
impl Line {
fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
pub fn new(layout: Arc<LineLayout>, runs: &[(usize, RunStyle)]) -> Self {
let mut style_runs = SmallVec::new();
for (len, style) in runs {
style_runs.push(StyleRun {
@ -364,13 +364,13 @@ impl Line {
origin: glyph_origin,
});
} else {
scene.push_glyph(scene::Glyph {
scene.push_glyph(dbg!(scene::Glyph {
font_id: run.font_id,
font_size: self.layout.font_size,
id: glyph.id,
origin: glyph_origin,
color,
});
}));
}
}
}