Use RefinementCascade to compose pressability and hoverability

Co-Authored-By: Conrad Irwin <conrad@zed.dev>
This commit is contained in:
Nathan Sobo 2023-08-23 12:18:12 -06:00
parent 569d99a5a1
commit 5996b6b46b
10 changed files with 202 additions and 68 deletions

View file

@ -3,21 +3,22 @@ use crate::{
interactive::{InteractionHandlers, Interactive}, interactive::{InteractionHandlers, Interactive},
layout_context::LayoutContext, layout_context::LayoutContext,
paint_context::PaintContext, paint_context::PaintContext,
style::{Style, StyleHelpers, StyleRefinement, Styleable}, style::{Style, StyleHelpers, Styleable},
}; };
use anyhow::Result; use anyhow::Result;
use gpui::LayoutId; use gpui::LayoutId;
use refineable::{Refineable, RefinementCascade};
use smallvec::SmallVec; use smallvec::SmallVec;
pub struct Div<V: 'static> { pub struct Div<V: 'static> {
style: StyleRefinement, styles: RefinementCascade<Style>,
handlers: InteractionHandlers<V>, handlers: InteractionHandlers<V>,
children: SmallVec<[AnyElement<V>; 2]>, children: SmallVec<[AnyElement<V>; 2]>,
} }
pub fn div<V>() -> Div<V> { pub fn div<V>() -> Div<V> {
Div { Div {
style: Default::default(), styles: Default::default(),
handlers: Default::default(), handlers: Default::default(),
children: Default::default(), children: Default::default(),
} }
@ -36,16 +37,16 @@ impl<V: 'static> Element<V> for Div<V> {
.map(|child| child.layout(view, cx)) .map(|child| child.layout(view, cx))
.collect::<Result<Vec<LayoutId>>>()?; .collect::<Result<Vec<LayoutId>>>()?;
cx.add_layout_node(self.style(), (), children) let style = Style::from_refinement(&self.style_cascade().merged());
cx.add_layout_node(style.clone(), (), children)
} }
fn paint(&mut self, view: &mut V, layout: &mut Layout<V, ()>, cx: &mut PaintContext<V>) fn paint(&mut self, view: &mut V, layout: &mut Layout<V, ()>, cx: &mut PaintContext<V>)
where where
Self: Sized, Self: Sized,
{ {
let style = self.style(); self.computed_style()
.paint_background(layout.bounds(cx), cx);
style.paint_background::<V, Self>(layout, cx);
for child in &mut self.children { for child in &mut self.children {
child.paint(view, cx); child.paint(view, cx);
} }
@ -55,8 +56,12 @@ impl<V: 'static> Element<V> for Div<V> {
impl<V> Styleable for Div<V> { impl<V> Styleable for Div<V> {
type Style = Style; type Style = Style;
fn declared_style(&mut self) -> &mut StyleRefinement { fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
&mut self.style &mut self.styles
}
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
self.styles.base()
} }
} }

View file

@ -1,5 +1,4 @@
use anyhow::Result; use anyhow::{anyhow, Result};
use derive_more::{Deref, DerefMut};
use gpui::{geometry::rect::RectF, EngineLayout}; use gpui::{geometry::rect::RectF, EngineLayout};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::marker::PhantomData; use std::marker::PhantomData;
@ -83,13 +82,10 @@ impl<V> AnyElement<V> {
} }
} }
#[derive(Deref, DerefMut)]
pub struct Layout<V, D> { pub struct Layout<V, D> {
id: LayoutId, id: LayoutId,
engine_layout: Option<EngineLayout>, engine_layout: Option<EngineLayout>,
#[deref] element_data: Option<D>,
#[deref_mut]
element_data: D,
view_type: PhantomData<V>, view_type: PhantomData<V>,
} }
@ -98,7 +94,7 @@ impl<V: 'static, D> Layout<V, D> {
Self { Self {
id, id,
engine_layout: None, engine_layout: None,
element_data: element_data, element_data: Some(element_data),
view_type: PhantomData, view_type: PhantomData,
} }
} }
@ -111,20 +107,26 @@ impl<V: 'static, D> Layout<V, D> {
self.engine_layout(cx).order self.engine_layout(cx).order
} }
pub fn update<F, T>(&mut self, update: F) -> Result<T>
where
F: FnOnce(&mut Self, &mut D) -> T,
{
self.element_data
.take()
.map(|mut element_data| {
let result = update(self, &mut element_data);
self.element_data = Some(element_data);
result
})
.ok_or_else(|| anyhow!("reentrant calls to Layout::update are not allowed"))
}
fn engine_layout(&mut self, cx: &mut PaintContext<'_, '_, '_, '_, V>) -> &mut EngineLayout { fn engine_layout(&mut self, cx: &mut PaintContext<'_, '_, '_, '_, V>) -> &mut EngineLayout {
self.engine_layout self.engine_layout
.get_or_insert_with(|| cx.computed_layout(self.id).log_err().unwrap_or_default()) .get_or_insert_with(|| cx.computed_layout(self.id).log_err().unwrap_or_default())
} }
} }
impl<V: 'static> Layout<V, Option<AnyElement<V>>> {
pub fn paint(&mut self, view: &mut V, cx: &mut PaintContext<V>) {
let mut element = self.element_data.take().unwrap();
element.paint(view, cx);
self.element_data = Some(element);
}
}
pub trait ParentElement<V: 'static> { pub trait ParentElement<V: 'static> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>; fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;

View file

@ -2,24 +2,24 @@ use crate::{
element::{Element, Layout}, element::{Element, Layout},
layout_context::LayoutContext, layout_context::LayoutContext,
paint_context::PaintContext, paint_context::PaintContext,
style::{Style, StyleHelpers, StyleRefinement, Styleable}, style::{Style, StyleHelpers, Styleable},
}; };
use anyhow::Result; use anyhow::Result;
use gpui::platform::MouseMovedEvent; use gpui::platform::MouseMovedEvent;
use refineable::Refineable; use refineable::{CascadeSlot, Refineable, RefinementCascade};
use std::cell::Cell; use std::{cell::Cell, rc::Rc};
pub struct Hoverable<E: Styleable> { pub struct Hoverable<E: Styleable> {
hovered: Cell<bool>, hovered: Rc<Cell<bool>>,
child_style: StyleRefinement, cascade_slot: CascadeSlot,
hovered_style: StyleRefinement, hovered_style: <E::Style as Refineable>::Refinement,
child: E, child: E,
} }
pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> { pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
Hoverable { Hoverable {
hovered: Cell::new(false), hovered: Rc::new(Cell::new(false)),
child_style: child.declared_style().clone(), cascade_slot: child.style_cascade().reserve(),
hovered_style: Default::default(), hovered_style: Default::default(),
child, child,
} }
@ -28,7 +28,11 @@ pub fn hoverable<E: Styleable>(mut child: E) -> Hoverable<E> {
impl<E: Styleable> Styleable for Hoverable<E> { impl<E: Styleable> Styleable for Hoverable<E> {
type Style = E::Style; type Style = E::Style;
fn declared_style(&mut self) -> &mut crate::style::StyleRefinement { fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style> {
self.child.style_cascade()
}
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
&mut self.hovered_style &mut self.hovered_style
} }
} }
@ -55,13 +59,10 @@ impl<V: 'static, E: Element<V> + Styleable> Element<V> for Hoverable<E> {
let order = layout.order(cx); let order = layout.order(cx);
self.hovered.set(bounds.contains_point(cx.mouse_position())); self.hovered.set(bounds.contains_point(cx.mouse_position()));
if self.hovered.get() {
// If hovered, refine the child's style with this element's style. let slot = self.cascade_slot;
self.child.declared_style().refine(&self.hovered_style); let style = self.hovered.get().then_some(self.hovered_style.clone());
} else { self.style_cascade().set(slot, style);
// Otherwise, set the child's style back to its original style.
*self.child.declared_style() = self.child_style.clone();
}
let hovered = self.hovered.clone(); let hovered = self.hovered.clone();
cx.on_event(order, move |view, event: &MouseMovedEvent, cx| { cx.on_event(order, move |view, event: &MouseMovedEvent, cx| {

View file

@ -22,6 +22,7 @@ mod hoverable;
mod interactive; mod interactive;
mod layout_context; mod layout_context;
mod paint_context; mod paint_context;
mod pressable;
mod style; mod style;
mod text; mod text;
mod themes; mod themes;
@ -54,8 +55,10 @@ fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
.h_full() .h_full()
.w_1_2() .w_1_2()
.fill(theme.success(0.5)) .fill(theme.success(0.5))
.hoverable() .hovered()
.fill(theme.error(0.5)) .fill(theme.error(0.5))
.pressed()
.fill(theme.warning(0.5))
// .child(button().label("Hello").click(|_, _, _| println!("click!"))) // .child(button().label("Hello").click(|_, _, _| println!("click!")))
} }

View file

@ -0,0 +1,81 @@
use crate::{
element::{Element, Layout},
layout_context::LayoutContext,
paint_context::PaintContext,
style::{Style, StyleHelpers, Styleable},
};
use anyhow::Result;
use gpui::platform::MouseButtonEvent;
use refineable::{CascadeSlot, Refineable, RefinementCascade};
use std::{cell::Cell, rc::Rc};
pub struct Pressable<E: Styleable> {
pressed: Rc<Cell<bool>>,
pressed_style: <E::Style as Refineable>::Refinement,
cascade_slot: CascadeSlot,
child: E,
}
pub fn pressable<E: Styleable>(mut child: E) -> Pressable<E> {
Pressable {
pressed: Rc::new(Cell::new(false)),
pressed_style: Default::default(),
cascade_slot: child.style_cascade().reserve(),
child,
}
}
impl<E: Styleable> Styleable for Pressable<E> {
type Style = E::Style;
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement {
&mut self.pressed_style
}
fn style_cascade(&mut self) -> &mut RefinementCascade<E::Style> {
self.child.style_cascade()
}
}
impl<V: 'static, E: Element<V> + Styleable> Element<V> for Pressable<E> {
type Layout = E::Layout;
fn layout(&mut self, view: &mut V, cx: &mut LayoutContext<V>) -> Result<Layout<V, Self::Layout>>
where
Self: Sized,
{
self.child.layout(view, cx)
}
fn paint(
&mut self,
view: &mut V,
layout: &mut Layout<V, Self::Layout>,
cx: &mut PaintContext<V>,
) where
Self: Sized,
{
let slot = self.cascade_slot;
let style = self.pressed.get().then_some(self.pressed_style.clone());
self.style_cascade().set(slot, style);
let bounds = layout.bounds(cx);
let order = layout.order(cx);
let pressed = self.pressed.clone();
cx.on_event(order, move |view, event: &MouseButtonEvent, cx| {
if event.is_down {
if bounds.contains_point(event.position) {
pressed.set(true);
cx.repaint();
}
} else if pressed.get() {
pressed.set(false);
cx.repaint();
}
});
self.child.paint(view, layout, cx);
}
}
impl<E: Styleable<Style = Style>> StyleHelpers for Pressable<E> {}

View file

@ -1,18 +1,18 @@
use crate::{ use crate::{
color::Hsla, color::Hsla,
element::{Element, Layout},
hoverable::{hoverable, Hoverable}, hoverable::{hoverable, Hoverable},
paint_context::PaintContext, paint_context::PaintContext,
pressable::{pressable, Pressable},
}; };
use gpui::{ use gpui::{
fonts::TextStyleRefinement, fonts::TextStyleRefinement,
geometry::{ geometry::{
AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point, PointRefinement, rect::RectF, AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point,
Size, SizeRefinement, PointRefinement, Size, SizeRefinement,
}, },
}; };
use playground_macros::styleable_helpers; use playground_macros::styleable_helpers;
use refineable::Refineable; use refineable::{Refineable, RefinementCascade};
pub use taffy::style::{ pub use taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent, AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position, Overflow, Position,
@ -126,12 +126,7 @@ impl Style {
/// Paints the background of an element styled with this style. /// Paints the background of an element styled with this style.
/// Return the bounds in which to paint the content. /// Return the bounds in which to paint the content.
pub fn paint_background<V: 'static, E: Element<V>>( pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut PaintContext<V>) {
&self,
layout: &mut Layout<V, E::Layout>,
cx: &mut PaintContext<V>,
) {
let bounds = layout.bounds(cx);
let rem_size = cx.rem_pixels(); let rem_size = cx.rem_pixels();
if let Some(color) = self.fill.as_ref().and_then(Fill::color) { if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
cx.scene.push_quad(gpui::Quad { cx.scene.push_quad(gpui::Quad {
@ -202,7 +197,7 @@ impl OptionalTextStyle {
} }
} }
#[derive(Clone)] #[derive(Clone, Debug)]
pub enum Fill { pub enum Fill {
Color(Hsla), Color(Hsla),
} }
@ -247,22 +242,28 @@ impl CornerRadii {
} }
pub trait Styleable { pub trait Styleable {
type Style: refineable::Refineable; type Style: Refineable + Default;
fn declared_style(&mut self) -> &mut playground::style::StyleRefinement; fn style_cascade(&mut self) -> &mut RefinementCascade<Self::Style>;
fn declared_style(&mut self) -> &mut <Self::Style as Refineable>::Refinement;
fn style(&mut self) -> playground::style::Style { fn computed_style(&mut self) -> Self::Style {
let mut style = playground::style::Style::default(); Self::Style::from_refinement(&self.style_cascade().merged())
style.refine(self.declared_style());
style
} }
fn hoverable(self) -> Hoverable<Self> fn hovered(self) -> Hoverable<Self>
where where
Self: Sized, Self: Sized,
{ {
hoverable(self) hoverable(self)
} }
fn pressed(self) -> Pressable<Self>
where
Self: Sized,
{
pressable(self)
}
} }
// Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc. // Helpers methods that take and return mut self. This includes tailwind style methods for standard sizes etc.
@ -270,7 +271,6 @@ pub trait Styleable {
// Example: // Example:
// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind. // // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
// fn p_2(mut self) -> Self where Self: Sized; // fn p_2(mut self) -> Self where Self: Sized;
use crate as playground; // Macro invocation references this crate as playground.
pub trait StyleHelpers: Styleable<Style = Style> { pub trait StyleHelpers: Styleable<Style = Style> {
styleable_helpers!(); styleable_helpers!();

View file

@ -62,16 +62,16 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
impl #impl_generics playground::element::Element<#view_type_name> for #type_name #type_generics impl #impl_generics playground::element::Element<#view_type_name> for #type_name #type_generics
#where_clause #where_clause
{ {
type Layout = Option<playground::element::AnyElement<#view_type_name #lifetimes>>; type Layout = playground::element::AnyElement<#view_type_name #lifetimes>;
fn layout( fn layout(
&mut self, &mut self,
view: &mut V, view: &mut V,
cx: &mut playground::element::LayoutContext<V>, cx: &mut playground::element::LayoutContext<V>,
) -> anyhow::Result<playground::element::Layout<V, Self::Layout>> { ) -> anyhow::Result<playground::element::Layout<V, Self::Layout>> {
let mut element = self.render(view, cx).into_any(); let mut rendered_element = self.render(view, cx).into_any();
let layout_id = element.layout(view, cx)?; let layout_id = rendered_element.layout(view, cx)?;
Ok(playground::element::Layout::new(layout_id, Some(element))) Ok(playground::element::Layout::new(layout_id, rendered_element))
} }
fn paint( fn paint(
@ -80,7 +80,7 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
layout: &mut playground::element::Layout<V, Self::Layout>, layout: &mut playground::element::Layout<V, Self::Layout>,
cx: &mut playground::element::PaintContext<V>, cx: &mut playground::element::PaintContext<V>,
) { ) {
layout.paint(view, cx); layout.update(|_, rendered_element| rendered_element.paint(view, cx)).ok();
} }
} }

View file

@ -20,6 +20,7 @@ pub fn styleable_helpers(input: TokenStream) -> TokenStream {
let output = quote! { let output = quote! {
#(#methods)* #(#methods)*
}; };
output.into() output.into()
} }

View file

@ -1905,7 +1905,6 @@ impl AppContext {
fn handle_repaint_window_effect(&mut self, window: AnyWindowHandle) { fn handle_repaint_window_effect(&mut self, window: AnyWindowHandle) {
self.update_window(window, |cx| { self.update_window(window, |cx| {
cx.layout(false).log_err();
if let Some(scene) = cx.paint().log_err() { if let Some(scene) = cx.paint().log_err() {
cx.window.platform_window.present_scene(scene); cx.window.platform_window.present_scene(scene);
} }

View file

@ -1,7 +1,7 @@
pub use derive_refineable::Refineable; pub use derive_refineable::Refineable;
pub trait Refineable { pub trait Refineable: Clone {
type Refinement: Default; type Refinement: Refineable<Refinement = Self::Refinement> + Default;
fn refine(&mut self, refinement: &Self::Refinement); fn refine(&mut self, refinement: &Self::Refinement);
fn refined(mut self, refinement: &Self::Refinement) -> Self fn refined(mut self, refinement: &Self::Refinement) -> Self
@ -11,4 +11,46 @@ pub trait Refineable {
self.refine(refinement); self.refine(refinement);
self self
} }
fn from_refinement(refinement: &Self::Refinement) -> Self
where
Self: Default + Sized,
{
Self::default().refined(refinement)
}
}
pub struct RefinementCascade<S: Refineable>(Vec<Option<S::Refinement>>);
impl<S: Refineable + Default> Default for RefinementCascade<S> {
fn default() -> Self {
Self(vec![Some(Default::default())])
}
}
#[derive(Copy, Clone)]
pub struct CascadeSlot(usize);
impl<S: Refineable + Default> RefinementCascade<S> {
pub fn reserve(&mut self) -> CascadeSlot {
self.0.push(None);
return CascadeSlot(self.0.len() - 1);
}
pub fn base(&mut self) -> &mut S::Refinement {
self.0[0].as_mut().unwrap()
}
pub fn set(&mut self, slot: CascadeSlot, refinement: Option<S::Refinement>) {
self.0[slot.0] = refinement
}
pub fn merged(&self) -> S::Refinement {
let mut merged = self.0[0].clone().unwrap();
for refinement in self.0.iter().skip(1) {
if let Some(refinement) = refinement {
merged.refine(refinement);
}
}
merged
}
} }