From f37b83a0eab7178a02b3c48840f41e6f20723677 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 11 Oct 2023 17:18:33 -0600 Subject: [PATCH] WIP --- crates/gpui3/src/app.rs | 63 +++++++++++++--------- crates/gpui3/src/element.rs | 11 +++- crates/gpui3/src/elements.rs | 2 + crates/gpui3/src/elements/group.rs | 75 ++++++++++++++++++++++++++ crates/gpui3/src/elements/hoverable.rs | 28 +++++++--- crates/gpui3/src/gpui3.rs | 14 +++-- crates/gpui3/src/styled.rs | 13 ++++- crates/storybook2/src/theme.rs | 8 +-- 8 files changed, 168 insertions(+), 46 deletions(-) create mode 100644 crates/gpui3/src/elements/group.rs diff --git a/crates/gpui3/src/app.rs b/crates/gpui3/src/app.rs index 421ea0f346..17676ca645 100644 --- a/crates/gpui3/src/app.rs +++ b/crates/gpui3/src/app.rs @@ -60,7 +60,7 @@ impl App { svg_renderer: SvgRenderer::new(asset_source), image_cache: ImageCache::new(http_client), text_style_stack: Vec::new(), - state_stacks_by_type: HashMap::default(), + global_stacks_by_type: HashMap::default(), unit_entity, entities, windows: SlotMap::with_key(), @@ -100,7 +100,7 @@ pub struct AppContext { pub(crate) svg_renderer: SvgRenderer, pub(crate) image_cache: ImageCache, pub(crate) text_style_stack: Vec, - pub(crate) state_stacks_by_type: HashMap>>, + pub(crate) global_stacks_by_type: HashMap>>, pub(crate) unit_entity: Handle<()>, pub(crate) entities: EntityMap, pub(crate) windows: SlotMap>, @@ -252,24 +252,49 @@ impl AppContext { style } - pub fn state(&self) -> &S { - self.state_stacks_by_type - .get(&TypeId::of::()) + pub fn global(&self) -> &G { + self.global_stacks_by_type + .get(&TypeId::of::()) .and_then(|stack| stack.last()) - .and_then(|any_state| any_state.downcast_ref::()) - .ok_or_else(|| anyhow!("no state of type {} exists", type_name::())) + .and_then(|any_state| any_state.downcast_ref::()) + .ok_or_else(|| anyhow!("no state of type {} exists", type_name::())) .unwrap() } - pub fn state_mut(&mut self) -> &mut S { - self.state_stacks_by_type - .get_mut(&TypeId::of::()) + pub fn global_mut(&mut self) -> &mut G { + self.global_stacks_by_type + .get_mut(&TypeId::of::()) .and_then(|stack| stack.last_mut()) - .and_then(|any_state| any_state.downcast_mut::()) - .ok_or_else(|| anyhow!("no state of type {} exists", type_name::())) + .and_then(|any_state| any_state.downcast_mut::()) + .ok_or_else(|| anyhow!("no state of type {} exists", type_name::())) .unwrap() } + pub fn default_global(&mut self) -> &mut G { + let stack = self + .global_stacks_by_type + .entry(TypeId::of::()) + .or_default(); + if stack.is_empty() { + stack.push(Box::new(G::default())); + } + stack.last_mut().unwrap().downcast_mut::().unwrap() + } + + pub(crate) fn push_global(&mut self, state: T) { + self.global_stacks_by_type + .entry(TypeId::of::()) + .or_default() + .push(Box::new(state)); + } + + pub(crate) fn pop_global(&mut self) { + self.global_stacks_by_type + .get_mut(&TypeId::of::()) + .and_then(|stack| stack.pop()) + .expect("state stack underflow"); + } + pub(crate) fn push_text_style(&mut self, text_style: TextStyleRefinement) { self.text_style_stack.push(text_style); } @@ -277,20 +302,6 @@ impl AppContext { pub(crate) fn pop_text_style(&mut self) { self.text_style_stack.pop(); } - - pub(crate) fn push_state(&mut self, state: T) { - self.state_stacks_by_type - .entry(TypeId::of::()) - .or_default() - .push(Box::new(state)); - } - - pub(crate) fn pop_state(&mut self) { - self.state_stacks_by_type - .get_mut(&TypeId::of::()) - .and_then(|stack| stack.pop()) - .expect("state stack underflow"); - } } impl Context for AppContext { diff --git a/crates/gpui3/src/element.rs b/crates/gpui3/src/element.rs index 6abce18c56..c5df1d3fc8 100644 --- a/crates/gpui3/src/element.rs +++ b/crates/gpui3/src/element.rs @@ -1,8 +1,8 @@ use std::sync::Arc; use crate::{ - BorrowWindow, Bounds, Clickable, ElementId, LayoutId, MouseDownEvent, MouseUpEvent, Pixels, - Point, ViewContext, + BorrowWindow, Bounds, Clickable, ElementGroup, ElementId, LayoutId, MouseDownEvent, + MouseUpEvent, Pixels, Point, SharedString, ViewContext, }; use derive_more::{Deref, DerefMut}; pub(crate) use smallvec::SmallVec; @@ -27,6 +27,13 @@ pub trait Element: 'static + Send + Sync { element_state: &mut Self::ElementState, cx: &mut ViewContext, ); + + fn group(self, name: impl Into) -> ElementGroup + where + Self: Sized, + { + ElementGroup::new(name.into(), self) + } } pub trait IdentifiedElement: Element { diff --git a/crates/gpui3/src/elements.rs b/crates/gpui3/src/elements.rs index 01c5553dd4..931fa51308 100644 --- a/crates/gpui3/src/elements.rs +++ b/crates/gpui3/src/elements.rs @@ -1,5 +1,6 @@ mod clickable; mod div; +mod group; mod hoverable; mod identified; mod img; @@ -9,6 +10,7 @@ mod text; pub use clickable::*; pub use div::*; +pub use group::*; pub use hoverable::*; pub use identified::*; pub use img::*; diff --git a/crates/gpui3/src/elements/group.rs b/crates/gpui3/src/elements/group.rs new file mode 100644 index 0000000000..cee17ce50a --- /dev/null +++ b/crates/gpui3/src/elements/group.rs @@ -0,0 +1,75 @@ +use crate::{ + AnyElement, AppContext, Bounds, Element, ElementId, IdentifiedElement, ParentElement, Pixels, + SharedString, ViewContext, +}; +use collections::HashMap; +use smallvec::SmallVec; + +#[derive(Default)] +struct GroupBounds(HashMap; 1]>>); + +pub fn element_group_bounds(name: &SharedString, cx: &mut AppContext) -> Option> { + cx.default_global::() + .0 + .get(name) + .and_then(|bounds_stack| bounds_stack.last().cloned()) +} + +pub struct ElementGroup { + name: SharedString, + child: E, +} + +impl ElementGroup { + pub fn new(name: SharedString, child: E) -> Self { + ElementGroup { name, child } + } +} + +impl Element for ElementGroup { + type ViewState = E::ViewState; + type ElementState = E::ElementState; + + fn element_id(&self) -> Option { + self.child.element_id() + } + + fn layout( + &mut self, + state: &mut Self::ViewState, + element_state: Option, + cx: &mut ViewContext, + ) -> (crate::LayoutId, Self::ElementState) { + self.child.layout(state, element_state, cx) + } + + fn paint( + &mut self, + bounds: Bounds, + state: &mut Self::ViewState, + element_state: &mut Self::ElementState, + cx: &mut ViewContext, + ) { + cx.default_global::() + .0 + .entry(self.name.clone()) + .or_default() + .push(bounds); + self.child.paint(bounds, state, element_state, cx); + cx.default_global::() + .0 + .get_mut(&self.name) + .unwrap() + .pop(); + } +} + +impl ParentElement for ElementGroup { + type State = E::State; + + fn children_mut(&mut self) -> &mut SmallVec<[AnyElement; 2]> { + self.child.children_mut() + } +} + +impl IdentifiedElement for ElementGroup where E: IdentifiedElement {} diff --git a/crates/gpui3/src/elements/hoverable.rs b/crates/gpui3/src/elements/hoverable.rs index 7d57ff1f40..6519750224 100644 --- a/crates/gpui3/src/elements/hoverable.rs +++ b/crates/gpui3/src/elements/hoverable.rs @@ -1,6 +1,7 @@ use crate::{ - AnyElement, Bounds, DispatchPhase, Element, ElementId, IdentifiedElement, Interactive, - MouseEventListeners, MouseMoveEvent, ParentElement, Pixels, Styled, ViewContext, + element_group_bounds, AnyElement, Bounds, DispatchPhase, Element, ElementId, IdentifiedElement, + Interactive, MouseEventListeners, MouseMoveEvent, ParentElement, Pixels, SharedString, Styled, + ViewContext, }; use refineable::{CascadeSlot, Refineable, RefinementCascade}; use smallvec::SmallVec; @@ -10,6 +11,7 @@ use std::sync::{ }; pub struct Hoverable { + hover_group: Option, hovered: Arc, cascade_slot: CascadeSlot, hovered_style: ::Refinement, @@ -17,8 +19,9 @@ pub struct Hoverable { } impl Hoverable { - pub fn new(mut child: E) -> Self { + pub fn new(mut child: E, hover_group: Option) -> Self { Self { + hover_group, hovered: Arc::new(AtomicBool::new(false)), cascade_slot: child.style_cascade().reserve(), hovered_style: Default::default(), @@ -77,17 +80,26 @@ where element_state: &mut Self::ElementState, cx: &mut ViewContext, ) { + let bounds = self + .hover_group + .as_ref() + .and_then(|group| element_group_bounds(group, cx)) + .unwrap_or(bounds); let hovered = bounds.contains_point(cx.mouse_position()); + let slot = self.cascade_slot; let style = hovered.then_some(self.hovered_style.clone()); self.style_cascade().set(slot, style); self.hovered.store(hovered, SeqCst); - let hovered = self.hovered.clone(); - cx.on_mouse_event(move |_, event: &MouseMoveEvent, phase, cx| { - if phase == DispatchPhase::Capture { - if bounds.contains_point(event.position) != hovered.load(SeqCst) { - cx.notify(); + cx.on_mouse_event({ + let hovered = self.hovered.clone(); + + move |_, event: &MouseMoveEvent, phase, cx| { + if phase == DispatchPhase::Capture { + if bounds.contains_point(event.position) != hovered.load(SeqCst) { + cx.notify(); + } } } }); diff --git a/crates/gpui3/src/gpui3.rs b/crates/gpui3/src/gpui3.rs index ea0ad219a8..dbd14a9675 100644 --- a/crates/gpui3/src/gpui3.rs +++ b/crates/gpui3/src/gpui3.rs @@ -52,7 +52,7 @@ pub use view::*; pub use window::*; use std::{ - any::Any, + any::{Any, TypeId}, mem, ops::{Deref, DerefMut}, sync::Arc, @@ -77,6 +77,12 @@ pub trait Context { ) -> Self::Result; } +pub enum GlobalKey { + Numeric(usize), + View(EntityId), + Type(TypeId), +} + #[repr(transparent)] pub struct MainThread(T); @@ -143,13 +149,13 @@ pub trait BorrowAppContext { result } - fn with_state(&mut self, state: T, f: F) -> R + fn with_global(&mut self, state: T, f: F) -> R where F: FnOnce(&mut Self) -> R, { - self.app_mut().push_state(state); + self.app_mut().push_global(state); let result = f(self); - self.app_mut().pop_state::(); + self.app_mut().pop_global::(); result } } diff --git a/crates/gpui3/src/styled.rs b/crates/gpui3/src/styled.rs index c14b7068f5..8cab424862 100644 --- a/crates/gpui3/src/styled.rs +++ b/crates/gpui3/src/styled.rs @@ -1,4 +1,4 @@ -use crate::{Hoverable, Pressable, Refineable, RefinementCascade}; +use crate::{Hoverable, Pressable, Refineable, RefinementCascade, SharedString}; pub trait Styled { type Style: 'static + Refineable + Send + Sync + Default; @@ -16,7 +16,16 @@ pub trait Styled { Self::Style: 'static + Refineable + Default + Send + Sync, ::Refinement: 'static + Default + Send + Sync, { - Hoverable::new(self) + Hoverable::new(self, None) + } + + fn group_hover(self, group_name: impl Into) -> Hoverable + where + Self: 'static + Sized + Send + Sync, + Self::Style: 'static + Refineable + Default + Send + Sync, + ::Refinement: 'static + Default + Send + Sync, + { + Hoverable::new(self, Some(group_name.into())) } fn active(self) -> Pressable diff --git a/crates/storybook2/src/theme.rs b/crates/storybook2/src/theme.rs index 8e8ee7a120..da9916debc 100644 --- a/crates/storybook2/src/theme.rs +++ b/crates/storybook2/src/theme.rs @@ -134,7 +134,7 @@ where E: Element, F: FnOnce(&mut ViewContext) -> E, { - let child = cx.with_state(theme.clone(), |cx| build_child(cx)); + let child = cx.with_global(theme.clone(), |cx| build_child(cx)); Themed { theme, child } } @@ -160,7 +160,7 @@ impl Element for Themed { where Self: Sized, { - cx.with_state(self.theme.clone(), |cx| { + cx.with_global(self.theme.clone(), |cx| { self.child.layout(state, element_state, cx) }) } @@ -174,12 +174,12 @@ impl Element for Themed { ) where Self: Sized, { - cx.with_state(self.theme.clone(), |cx| { + cx.with_global(self.theme.clone(), |cx| { self.child.paint(bounds, state, frame_state, cx); }); } } pub fn theme<'a>(cx: &'a WindowContext) -> &'a Theme { - cx.state() + cx.global() }