feat: event handling on desktop

This commit is contained in:
Jonathan Kelley committed 2022-11-17 20:00:39 -08:00
1 parent 073fea9ed3
commit 30ef225812
29 files changed
+424 -205

No files matched your search

+17 -7
View File
@@ -4,28 +4,31 @@ use crate::{nodes::VNode, virtual_dom::VirtualDom};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ElementId(pub usize);
pub struct ElementPath {
pub struct ElementRef {
// the pathway of the real element inside the template
pub path: &'static [u8],
// The actual template
pub template: *mut VNode<'static>,
pub element: usize,
}
impl ElementPath {
impl ElementRef {
pub fn null() -> Self {
Self {
template: std::ptr::null_mut(),
element: 0,
path: &[],
}
}
}
impl VirtualDom {
pub fn next_element(&mut self, template: &VNode) -> ElementId {
pub fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
let entry = self.elements.vacant_entry();
let id = entry.key();
entry.insert(ElementPath {
entry.insert(ElementRef {
template: template as *const _ as *mut _,
element: id,
path,
});
ElementId(id)
@@ -35,3 +38,10 @@ impl VirtualDom {
self.elements.remove(id.0);
}
}
/*
now......
an ID is mostly a pointer to a node in the real dom.
We need to
*/
+14 -14
View File
@@ -1,7 +1,5 @@
use std::ops::Bound;
use crate::factory::RenderReturn;
use crate::innerlude::{Mutations, SuspenseContext, SuspenseId};
use crate::innerlude::{Mutations, SuspenseContext};
use crate::mutations::Mutation;
use crate::mutations::Mutation::*;
use crate::nodes::VNode;
@@ -75,13 +73,13 @@ impl VirtualDom {
DynamicNode::Text {
id: slot, value, ..
} => {
let id = self.next_element(template);
let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id);
mutations.push(CreateTextNode { value, id });
1
}
DynamicNode::Placeholder(slot) => {
let id = self.next_element(template);
let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id);
mutations.push(CreatePlaceholder { id });
1
@@ -95,7 +93,7 @@ impl VirtualDom {
while let Some((mut attr_id, path)) =
dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
{
let id = self.next_element(template);
let id = self.next_element(template, template.template.attr_paths[attr_id]);
mutations.push(AssignId {
path: &path[1..],
id,
@@ -118,7 +116,8 @@ impl VirtualDom {
id,
}),
AttributeValue::Listener(_) => mutations.push(NewEventListener {
event_name: attribute.name,
// all listeners start with "on"
event_name: &attribute.name[2..],
scope: cur_scope,
id,
}),
@@ -178,8 +177,8 @@ impl VirtualDom {
) {
match *node {
// Todo: create the children's template
TemplateNode::Dynamic(_) => {
let id = self.next_element(template);
TemplateNode::Dynamic(idx) => {
let id = self.next_element(template, template.template.node_paths[idx]);
mutations.push(CreatePlaceholder { id })
}
TemplateNode::Text(value) => mutations.push(CreateStaticText { value }),
@@ -194,7 +193,7 @@ impl VirtualDom {
tag,
inner_opt,
} => {
let id = self.next_element(template);
let id = self.next_element(template, &[]); // never gets referenced, empty path is fine, I think?
mutations.push(CreateElement {
name: tag,
@@ -239,7 +238,7 @@ impl VirtualDom {
) -> usize {
match &node {
DynamicNode::Text { id, value, .. } => {
let new_id = self.next_element(template);
let new_id = self.next_element(template, template.template.node_paths[idx]);
id.set(new_id);
mutations.push(HydrateText {
id: new_id,
@@ -269,7 +268,7 @@ impl VirtualDom {
}
RenderReturn::Async(_) => {
let new_id = self.next_element(template);
let new_id = self.next_element(template, template.template.node_paths[idx]);
placeholder.set(Some(new_id));
self.scopes[scope.0].placeholder.set(Some(new_id));
@@ -305,7 +304,8 @@ impl VirtualDom {
self.scopes[scope.0].has_context::<SuspenseContext>()
{
// Since this is a boundary, use it as a placeholder
let new_id = self.next_element(template);
let new_id =
self.next_element(template, template.template.node_paths[idx]);
placeholder.set(Some(new_id));
self.scopes[scope.0].placeholder.set(Some(new_id));
mutations.push(AssignId {
@@ -350,7 +350,7 @@ impl VirtualDom {
}
DynamicNode::Placeholder(slot) => {
let id = self.next_element(template);
let id = self.next_element(template, template.template.node_paths[idx]);
slot.set(id);
mutations.push(AssignId {
path: &template.template.node_paths[idx][1..],
+1 -1
View File
@@ -82,7 +82,7 @@ impl<'b> VirtualDom {
// remove old with placeholder
(Sync(Some(l)), Sync(None)) | (Sync(Some(l)), Async(_)) => {
//
let id = self.next_element(l); // todo!
let id = self.next_element(l, &[]); // todo!
m.push(Mutation::CreatePlaceholder { id });
self.drop_template(m, l, true);
}
+15 -18
View File
@@ -6,21 +6,30 @@ use std::{
rc::Rc,
};
pub struct UiEvent<T: 'static> {
pub(crate) bubble_state: Cell<bool>,
pub struct UiEvent<T: 'static + ?Sized> {
pub(crate) bubbles: Rc<Cell<bool>>,
pub(crate) data: Rc<T>,
}
impl<T> Clone for UiEvent<T> {
impl UiEvent<dyn Any> {
pub fn downcast<T: 'static + Sized>(self) -> Option<UiEvent<T>> {
Some(UiEvent {
bubbles: self.bubbles,
data: self.data.downcast().ok()?,
})
}
}
impl<T: ?Sized> Clone for UiEvent<T> {
fn clone(&self) -> Self {
Self {
bubble_state: self.bubble_state.clone(),
bubbles: self.bubbles.clone(),
data: self.data.clone(),
}
}
}
impl<T> UiEvent<T> {
pub fn cancel_bubble(&self) {
self.bubble_state.set(false);
self.bubbles.set(false);
}
}
@@ -35,7 +44,7 @@ impl<T> std::ops::Deref for UiEvent<T> {
impl<T: Debug> std::fmt::Debug for UiEvent<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UiEvent")
.field("bubble_state", &self.bubble_state)
.field("bubble_state", &self.bubbles)
.field("data", &self.data)
.finish()
}
@@ -93,18 +102,6 @@ pub enum EventPriority {
Low = 0,
}
// ensures a strict descendant relationship
// returns false if the paths are equal
pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
if small.len() >= big.len() {
return false;
}
small == &big[..small.len()]
}
pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(&'bump dyn Any) + 'bump>;
type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
+1 -1
View File
@@ -74,7 +74,7 @@ pub use crate::innerlude::{
DynamicNode,
Element,
ElementId,
ElementPath,
ElementRef,
EventPriority,
Fragment,
LazyNodes,
+26 -13
View File
@@ -17,7 +17,7 @@ pub struct VNode<'a> {
pub key: Option<&'a str>,
// When rendered, this template will be linked to its parent manually
pub parent: Option<*const DynamicNode<'a>>,
pub parent: Option<ElementId>,
pub template: Template<'static>,
@@ -153,7 +153,7 @@ pub enum AttributeValue<'a> {
Float(f32),
Int(i32),
Bool(bool),
Listener(RefCell<&'a mut dyn FnMut(&dyn Any)>),
Listener(RefCell<&'a mut dyn FnMut(UiEvent<dyn Any>)>),
Any(&'a dyn AnyValue),
None,
}
@@ -163,17 +163,30 @@ impl<'a> AttributeValue<'a> {
cx: &'a ScopeState,
mut f: impl FnMut(UiEvent<T>) + 'a,
) -> AttributeValue<'a> {
let f = cx.bump().alloc(move |a: &dyn Any| {
a.downcast_ref::<UiEvent<T>>()
.map(|a| f(a.clone()))
.unwrap_or_else(|| {
panic!(
"Expected UiEvent<{}>, got {:?}",
std::any::type_name::<T>(),
a
)
})
}) as &mut dyn FnMut(&dyn Any);
println!("creating listener with type od id {:?}", TypeId::of::<T>());
let f = cx.bump().alloc(move |a: UiEvent<dyn Any>| {
println!(
"incoming: {:?}, desired {:?}",
a.data.type_id(),
std::any::TypeId::of::<T>()
);
let data = a.data.downcast::<T>().unwrap();
// println!("casting to type {:?}", TypeId::of::<T>());
f(UiEvent {
bubbles: a.bubbles,
data,
})
// println!("listener called");
// if let Some(event) = a.downcast::<T>() {
// println!("downcast success");
// f(event)
// }
}) as &mut dyn FnMut(UiEvent<dyn Any>);
AttributeValue::Listener(RefCell::new(f))
}
+96 -65
View File
@@ -1,7 +1,7 @@
use crate::{
any_props::VComponentProps,
arena::ElementId,
arena::ElementPath,
arena::ElementRef,
diff::DirtyScope,
factory::RenderReturn,
innerlude::{Mutations, Scheduler, SchedulerMsg},
@@ -13,12 +13,12 @@ use crate::{
};
use futures_util::{pin_mut, FutureExt, StreamExt};
use slab::Slab;
use std::future::Future;
use std::rc::Rc;
use std::{
any::Any,
collections::{BTreeSet, HashMap},
};
use std::{cell::Cell, future::Future};
/// A virtual node system that progresses user events and diffs UI trees.
///
@@ -145,12 +145,14 @@ use std::{
/// ```
pub struct VirtualDom {
pub(crate) templates: HashMap<TemplateId, Template<'static>>,
pub(crate) elements: Slab<ElementPath>,
pub(crate) scopes: Slab<ScopeState>,
pub(crate) element_stack: Vec<ElementId>,
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
pub(crate) scheduler: Rc<Scheduler>,
// Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
pub(crate) elements: Slab<ElementRef>,
// While diffing we need some sort of way of breaking off a stream of suspended mutations.
pub(crate) scope_stack: Vec<ScopeId>,
pub(crate) collected_leaves: Vec<SuspenseId>,
@@ -246,7 +248,7 @@ impl VirtualDom {
root.provide_context(SuspenseBoundary::new(ScopeId(0)));
// the root element is always given element 0
dom.elements.insert(ElementPath::null());
dom.elements.insert(ElementRef::null());
dom
}
@@ -259,6 +261,12 @@ impl VirtualDom {
self.scopes.get(0).unwrap()
}
/// Build the virtualdom with a global context inserted into the base scope
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
self.base_scope().provide_context(context);
self
}
fn mark_dirty_scope(&mut self, id: ScopeId) {
let height = self.scopes[id.0].height;
self.dirty_scopes.insert(DirtyScope { height, id });
@@ -288,67 +296,90 @@ impl VirtualDom {
///
///
///
pub fn handle_event<T: 'static>(
pub fn handle_event(
&mut self,
name: &str,
data: Rc<T>,
data: Rc<dyn Any>,
element: ElementId,
bubbles: bool,
priority: EventPriority,
) -> Option<()> {
/*
- click registers
- walk upwards until first element with onclick listener
- get template from ElementID
- break out of wait loop
- send event to virtualdom
*/
let event = UiEvent {
bubble_state: std::cell::Cell::new(true),
// todo: priority is helpful when scheduling work around suspense, but we don't currently use it
_priority: EventPriority,
) {
let uievent = UiEvent {
bubbles: Rc::new(Cell::new(bubbles)),
data,
};
let path = &self.elements[element.0];
let template = unsafe { &*path.template };
let dynamic = &template.dynamic_nodes[path.element];
/*
------------------------
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
we find the target path.
let location = template
.dynamic_attrs
.iter()
.position(|attr| attr.mounted_element.get() == element)?;
With the target path, we try and move up to the parent until there is no parent.
Due to how bubbling works, we call the listeners before walking to the parent.
// let mut index = Some((path.template, location));
If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
----------------------
| <-- yes (is ascendant)
| | | <-- no (is not ascendant)
| | <-- yes (is ascendant)
| | | | | <--- target element, break early
| | | <-- no, broke early
| <-- no, broke early
*/
let mut parent_path = self.elements.get(element.0);
let mut listeners = vec![];
let mut listeners = Vec::<&Attribute>::new();
while let Some(el_ref) = parent_path {
let template = unsafe { &*el_ref.template };
let target_path = el_ref.path;
// while let Some((raw_parent, dyn_index)) = index {
// let parent = unsafe { &mut *raw_parent };
// let path = parent.template.node_paths[dyn_index];
let mut attrs = template.dynamic_attrs.iter().enumerate();
// listeners.extend(
// parent
// .dynamic_attrs
// .iter()
// .enumerate()
// .filter_map(|(idx, attr)| {
// match is_path_ascendant(parent.template.node_paths[idx], path) {
// true if attr.name == event.name => Some(attr),
// _ => None,
// }
// }),
// );
while let Some((idx, attr)) = attrs.next() {
pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
small.len() >= big.len() && small == &big[..small.len()]
}
// index = parent.parent;
// }
let this_path = template.template.attr_paths[idx];
for listener in listeners {
if let AttributeValue::Listener(listener) = &listener.value {
(listener.borrow_mut())(&event.clone())
println!(
"is {:?} ascendant of {:?} ? {}",
this_path,
target_path,
is_path_ascendant(this_path, target_path)
);
println!("{ } - {name}, - {}", attr.name, &attr.name[2..]);
if &attr.name[2..] == name && is_path_ascendant(&target_path, &this_path) {
listeners.push(&attr.value);
// Break if the event doesn't bubble anyways
if !bubbles {
break;
}
// Break if this is the exact target element
if this_path == target_path {
break;
}
}
}
}
Some(())
for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener {
listener.borrow_mut()(uievent.clone());
// Break if the event doesn't bubble
if !uievent.bubbles.get() {
return;
}
}
}
parent_path = template.parent.and_then(|id| self.elements.get(id.0));
}
}
/// Wait for the scheduler to have any work.
@@ -508,22 +539,22 @@ impl VirtualDom {
}
}
fn mark_dirty_scope(&mut self, scope_id: ScopeId) {
let scopes = &self.scopes;
if let Some(scope) = scopes.get_scope(scope_id) {
let height = scope.height;
let id = scope_id.0;
if let Err(index) = self.dirty_scopes.binary_search_by(|new| {
let scope = scopes.get_scope(*new).unwrap();
let new_height = scope.height;
let new_id = &scope.scope_id();
height.cmp(&new_height).then(new_id.0.cmp(&id))
}) {
self.dirty_scopes.insert(index, scope_id);
log::info!("mark_dirty_scope: {:?}", self.dirty_scopes);
}
}
}
// fn mark_dirty_scope(&mut self, scope_id: ScopeId) {
// let scopes = &self.scopes;
// if let Some(scope) = scopes.get_scope(scope_id) {
// let height = scope.height;
// let id = scope_id.0;
// if let Err(index) = self.dirty_scopes.binary_search_by(|new| {
// let scope = scopes.get_scope(*new).unwrap();
// let new_height = scope.height;
// let new_id = &scope.scope_id();
// height.cmp(&new_height).then(new_id.0.cmp(&id))
// }) {
// self.dirty_scopes.insert(index, scope_id);
// log::info!("mark_dirty_scope: {:?}", self.dirty_scopes);
// }
// }
// }
}
impl Drop for VirtualDom {