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},
layout_context::LayoutContext,
paint_context::PaintContext,
style::{Style, StyleHelpers, StyleRefinement, Styleable},
style::{Style, StyleHelpers, Styleable},
};
use anyhow::Result;
use gpui::LayoutId;
use refineable::{Refineable, RefinementCascade};
use smallvec::SmallVec;
pub struct Div<V: 'static> {
style: StyleRefinement,
styles: RefinementCascade<Style>,
handlers: InteractionHandlers<V>,
children: SmallVec<[AnyElement<V>; 2]>,
}
pub fn div<V>() -> Div<V> {
Div {
style: Default::default(),
styles: Default::default(),
handlers: Default::default(),
children: Default::default(),
}
@ -36,16 +37,16 @@ impl<V: 'static> Element<V> for Div<V> {
.map(|child| child.layout(view, cx))
.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>)
where
Self: Sized,
{
let style = self.style();
style.paint_background::<V, Self>(layout, cx);
self.computed_style()
.paint_background(layout.bounds(cx), cx);
for child in &mut self.children {
child.paint(view, cx);
}
@ -55,8 +56,12 @@ impl<V: 'static> Element<V> for Div<V> {
impl<V> Styleable for Div<V> {
type Style = Style;
fn declared_style(&mut self) -> &mut StyleRefinement {
&mut self.style
fn style_cascade(&mut self) -> &mut RefinementCascade<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 derive_more::{Deref, DerefMut};
use anyhow::{anyhow, Result};
use gpui::{geometry::rect::RectF, EngineLayout};
use smallvec::SmallVec;
use std::marker::PhantomData;
@ -83,13 +82,10 @@ impl<V> AnyElement<V> {
}
}
#[derive(Deref, DerefMut)]
pub struct Layout<V, D> {
id: LayoutId,
engine_layout: Option<EngineLayout>,
#[deref]
#[deref_mut]
element_data: D,
element_data: Option<D>,
view_type: PhantomData<V>,
}
@ -98,7 +94,7 @@ impl<V: 'static, D> Layout<V, D> {
Self {
id,
engine_layout: None,
element_data: element_data,
element_data: Some(element_data),
view_type: PhantomData,
}
}
@ -111,20 +107,26 @@ impl<V: 'static, D> Layout<V, D> {
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 {
self.engine_layout
.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> {
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<V>; 2]>;

View File

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

View File

@ -22,6 +22,7 @@ mod hoverable;
mod interactive;
mod layout_context;
mod paint_context;
mod pressable;
mod style;
mod text;
mod themes;
@ -54,8 +55,10 @@ fn playground<V: 'static>(theme: &ThemeColors) -> impl Element<V> {
.h_full()
.w_1_2()
.fill(theme.success(0.5))
.hoverable()
.hovered()
.fill(theme.error(0.5))
.pressed()
.fill(theme.warning(0.5))
// .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::{
color::Hsla,
element::{Element, Layout},
hoverable::{hoverable, Hoverable},
paint_context::PaintContext,
pressable::{pressable, Pressable},
};
use gpui::{
fonts::TextStyleRefinement,
geometry::{
AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point, PointRefinement,
Size, SizeRefinement,
rect::RectF, AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point,
PointRefinement, Size, SizeRefinement,
},
};
use playground_macros::styleable_helpers;
use refineable::Refineable;
use refineable::{Refineable, RefinementCascade};
pub use taffy::style::{
AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent,
Overflow, Position,
@ -126,12 +126,7 @@ impl Style {
/// Paints the background of an element styled with this style.
/// Return the bounds in which to paint the content.
pub fn paint_background<V: 'static, E: Element<V>>(
&self,
layout: &mut Layout<V, E::Layout>,
cx: &mut PaintContext<V>,
) {
let bounds = layout.bounds(cx);
pub fn paint_background<V: 'static>(&self, bounds: RectF, cx: &mut PaintContext<V>) {
let rem_size = cx.rem_pixels();
if let Some(color) = self.fill.as_ref().and_then(Fill::color) {
cx.scene.push_quad(gpui::Quad {
@ -202,7 +197,7 @@ impl OptionalTextStyle {
}
}
#[derive(Clone)]
#[derive(Clone, Debug)]
pub enum Fill {
Color(Hsla),
}
@ -247,22 +242,28 @@ impl CornerRadii {
}
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 {
let mut style = playground::style::Style::default();
style.refine(self.declared_style());
style
fn computed_style(&mut self) -> Self::Style {
Self::Style::from_refinement(&self.style_cascade().merged())
}
fn hoverable(self) -> Hoverable<Self>
fn hovered(self) -> Hoverable<Self>
where
Self: Sized,
{
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.
@ -270,7 +271,6 @@ pub trait Styleable {
// Example:
// // Sets the padding to 0.5rem, just like class="p-2" in Tailwind.
// 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> {
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
#where_clause
{
type Layout = Option<playground::element::AnyElement<#view_type_name #lifetimes>>;
type Layout = playground::element::AnyElement<#view_type_name #lifetimes>;
fn layout(
&mut self,
view: &mut V,
cx: &mut playground::element::LayoutContext<V>,
) -> anyhow::Result<playground::element::Layout<V, Self::Layout>> {
let mut element = self.render(view, cx).into_any();
let layout_id = element.layout(view, cx)?;
Ok(playground::element::Layout::new(layout_id, Some(element)))
let mut rendered_element = self.render(view, cx).into_any();
let layout_id = rendered_element.layout(view, cx)?;
Ok(playground::element::Layout::new(layout_id, rendered_element))
}
fn paint(
@ -80,7 +80,7 @@ pub fn derive_element(input: TokenStream) -> TokenStream {
layout: &mut playground::element::Layout<V, Self::Layout>,
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! {
#(#methods)*
};
output.into()
}

View File

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

View File

@ -1,7 +1,7 @@
pub use derive_refineable::Refineable;
pub trait Refineable {
type Refinement: Default;
pub trait Refineable: Clone {
type Refinement: Refineable<Refinement = Self::Refinement> + Default;
fn refine(&mut self, refinement: &Self::Refinement);
fn refined(mut self, refinement: &Self::Refinement) -> Self
@ -11,4 +11,46 @@ pub trait Refineable {
self.refine(refinement);
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
}
}