mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 01:34:02 +00:00
Use RefinementCascade to compose pressability and hoverability
Co-Authored-By: Conrad Irwin <conrad@zed.dev>
This commit is contained in:
parent
569d99a5a1
commit
5996b6b46b
10 changed files with 202 additions and 68 deletions
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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]>;
|
||||
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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!")))
|
||||
}
|
||||
|
||||
|
|
81
crates/gpui/playground/src/pressable.rs
Normal file
81
crates/gpui/playground/src/pressable.rs
Normal 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> {}
|
|
@ -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!();
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ pub fn styleable_helpers(input: TokenStream) -> TokenStream {
|
|||
let output = quote! {
|
||||
#(#methods)*
|
||||
};
|
||||
|
||||
output.into()
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue