This commit is contained in:
Nathan Sobo 2023-10-11 17:18:33 -06:00
parent 93c233b1cf
commit f37b83a0ea
8 changed files with 168 additions and 46 deletions

View file

@ -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<TextStyleRefinement>,
pub(crate) state_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
pub(crate) global_stacks_by_type: HashMap<TypeId, Vec<Box<dyn Any + Send + Sync>>>,
pub(crate) unit_entity: Handle<()>,
pub(crate) entities: EntityMap,
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
@ -252,24 +252,49 @@ impl AppContext {
style
}
pub fn state<S: 'static>(&self) -> &S {
self.state_stacks_by_type
.get(&TypeId::of::<S>())
pub fn global<G: 'static>(&self) -> &G {
self.global_stacks_by_type
.get(&TypeId::of::<G>())
.and_then(|stack| stack.last())
.and_then(|any_state| any_state.downcast_ref::<S>())
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
.and_then(|any_state| any_state.downcast_ref::<G>())
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
.unwrap()
}
pub fn state_mut<S: 'static>(&mut self) -> &mut S {
self.state_stacks_by_type
.get_mut(&TypeId::of::<S>())
pub fn global_mut<G: 'static>(&mut self) -> &mut G {
self.global_stacks_by_type
.get_mut(&TypeId::of::<G>())
.and_then(|stack| stack.last_mut())
.and_then(|any_state| any_state.downcast_mut::<S>())
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<S>()))
.and_then(|any_state| any_state.downcast_mut::<G>())
.ok_or_else(|| anyhow!("no state of type {} exists", type_name::<G>()))
.unwrap()
}
pub fn default_global<G: 'static + Default + Sync + Send>(&mut self) -> &mut G {
let stack = self
.global_stacks_by_type
.entry(TypeId::of::<G>())
.or_default();
if stack.is_empty() {
stack.push(Box::new(G::default()));
}
stack.last_mut().unwrap().downcast_mut::<G>().unwrap()
}
pub(crate) fn push_global<T: Send + Sync + 'static>(&mut self, state: T) {
self.global_stacks_by_type
.entry(TypeId::of::<T>())
.or_default()
.push(Box::new(state));
}
pub(crate) fn pop_global<T: 'static>(&mut self) {
self.global_stacks_by_type
.get_mut(&TypeId::of::<T>())
.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<T: Send + Sync + 'static>(&mut self, state: T) {
self.state_stacks_by_type
.entry(TypeId::of::<T>())
.or_default()
.push(Box::new(state));
}
pub(crate) fn pop_state<T: 'static>(&mut self) {
self.state_stacks_by_type
.get_mut(&TypeId::of::<T>())
.and_then(|stack| stack.pop())
.expect("state stack underflow");
}
}
impl Context for AppContext {

View file

@ -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<Self::ViewState>,
);
fn group(self, name: impl Into<SharedString>) -> ElementGroup<Self>
where
Self: Sized,
{
ElementGroup::new(name.into(), self)
}
}
pub trait IdentifiedElement: Element {

View file

@ -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::*;

View file

@ -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<SharedString, SmallVec<[Bounds<Pixels>; 1]>>);
pub fn element_group_bounds(name: &SharedString, cx: &mut AppContext) -> Option<Bounds<Pixels>> {
cx.default_global::<GroupBounds>()
.0
.get(name)
.and_then(|bounds_stack| bounds_stack.last().cloned())
}
pub struct ElementGroup<E> {
name: SharedString,
child: E,
}
impl<E> ElementGroup<E> {
pub fn new(name: SharedString, child: E) -> Self {
ElementGroup { name, child }
}
}
impl<E: Element> Element for ElementGroup<E> {
type ViewState = E::ViewState;
type ElementState = E::ElementState;
fn element_id(&self) -> Option<ElementId> {
self.child.element_id()
}
fn layout(
&mut self,
state: &mut Self::ViewState,
element_state: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
) -> (crate::LayoutId, Self::ElementState) {
self.child.layout(state, element_state, cx)
}
fn paint(
&mut self,
bounds: Bounds<Pixels>,
state: &mut Self::ViewState,
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) {
cx.default_global::<GroupBounds>()
.0
.entry(self.name.clone())
.or_default()
.push(bounds);
self.child.paint(bounds, state, element_state, cx);
cx.default_global::<GroupBounds>()
.0
.get_mut(&self.name)
.unwrap()
.pop();
}
}
impl<E: ParentElement> ParentElement for ElementGroup<E> {
type State = E::State;
fn children_mut(&mut self) -> &mut SmallVec<[AnyElement<Self::State>; 2]> {
self.child.children_mut()
}
}
impl<E> IdentifiedElement for ElementGroup<E> where E: IdentifiedElement {}

View file

@ -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<E: Styled> {
hover_group: Option<SharedString>,
hovered: Arc<AtomicBool>,
cascade_slot: CascadeSlot,
hovered_style: <E::Style as Refineable>::Refinement,
@ -17,8 +19,9 @@ pub struct Hoverable<E: Styled> {
}
impl<E: Styled> Hoverable<E> {
pub fn new(mut child: E) -> Self {
pub fn new(mut child: E, hover_group: Option<SharedString>) -> 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<Self::ViewState>,
) {
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();
}
}
}
});

View file

@ -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<R>;
}
pub enum GlobalKey {
Numeric(usize),
View(EntityId),
Type(TypeId),
}
#[repr(transparent)]
pub struct MainThread<T>(T);
@ -143,13 +149,13 @@ pub trait BorrowAppContext {
result
}
fn with_state<T: Send + Sync + 'static, F, R>(&mut self, state: T, f: F) -> R
fn with_global<T: Send + Sync + 'static, F, R>(&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::<T>();
self.app_mut().pop_global::<T>();
result
}
}

View file

@ -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,
<Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
{
Hoverable::new(self)
Hoverable::new(self, None)
}
fn group_hover(self, group_name: impl Into<SharedString>) -> Hoverable<Self>
where
Self: 'static + Sized + Send + Sync,
Self::Style: 'static + Refineable + Default + Send + Sync,
<Self::Style as Refineable>::Refinement: 'static + Default + Send + Sync,
{
Hoverable::new(self, Some(group_name.into()))
}
fn active(self) -> Pressable<Self>

View file

@ -134,7 +134,7 @@ where
E: Element,
F: FnOnce(&mut ViewContext<E::ViewState>) -> 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<E: Element> Element for Themed<E> {
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<E: Element> Element for Themed<E> {
) 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()
}