feat: event handling on desktop
This commit is contained in:
1 parent
073fea9ed3
commit
30ef225812
29 files changed
+424
-205
No files matched your search
@@ -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
@@ -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..],
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -74,7 +74,7 @@ pub use crate::innerlude::{
|
||||
DynamicNode,
|
||||
Element,
|
||||
ElementId,
|
||||
ElementPath,
|
||||
ElementRef,
|
||||
EventPriority,
|
||||
Fragment,
|
||||
LazyNodes,
|
||||
|
||||
+26
-13
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user