Bladeren bron

Merge branch 'master' of https://github.com/DioxusLabs/dioxus into rusty-mousedata

 Conflicts:
	packages/tui/src/hooks.rs
Reinis Mazeiks 3 jaren geleden
bovenliggende
commit
cf26f41972

+ 1 - 1
.github/FUNDING.yml

@@ -1,4 +1,4 @@
 # These are supported funding model platforms
 
-github: jkelleyrtp # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+github: DioxusLabs # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
 open_collective: dioxus-labs # Replace with a single Open Collective username

+ 0 - 1
Cargo.toml

@@ -44,7 +44,6 @@ html = ["dioxus-html"]
 ssr = ["dioxus-ssr"]
 web = ["dioxus-web", "dioxus-router/web"]
 desktop = ["dioxus-desktop"]
-ayatana = ["dioxus-desktop/ayatana"]
 router = ["dioxus-router"]
 tui = ["dioxus-tui"]
 liveview = ["dioxus-liveview"]

+ 1 - 1
docs/guide/src/elements/lists.md

@@ -102,7 +102,7 @@ For keen Rustaceans: notice how we don't actually call `collect` on the name lis
 
 ## Keeping list items in order with `key`
 
-The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inneficient.
+The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inefficient.
 
 To solve this problem, each item in the list must be **uniquely identifiable**. You can achieve this by giving it a unique, fixed "key". In Dioxus, a key is a string that identifies an item among others in the list.
 

+ 232 - 0
packages/core/src/arbitrary_value.rs

@@ -0,0 +1,232 @@
+use std::fmt::Formatter;
+
+// trying to keep values at 3 bytes
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Clone, Debug, PartialEq)]
+pub enum AttributeValue<'a> {
+    Text(&'a str),
+    Float32(f32),
+    Float64(f64),
+    Int32(i32),
+    Int64(i64),
+    Uint32(u32),
+    Uint64(u64),
+    Bool(bool),
+
+    Vec3Float(f32, f32, f32),
+    Vec3Int(i32, i32, i32),
+    Vec3Uint(u32, u32, u32),
+
+    Vec4Float(f32, f32, f32, f32),
+    Vec4Int(i32, i32, i32, i32),
+    Vec4Uint(u32, u32, u32, u32),
+
+    Bytes(&'a [u8]),
+    Any(ArbitraryAttributeValue<'a>),
+}
+
+impl<'a> AttributeValue<'a> {
+    pub fn is_truthy(&self) -> bool {
+        match self {
+            AttributeValue::Text(t) => *t == "true",
+            AttributeValue::Bool(t) => *t,
+            _ => false,
+        }
+    }
+
+    pub fn is_falsy(&self) -> bool {
+        match self {
+            AttributeValue::Text(t) => *t == "false",
+            AttributeValue::Bool(t) => !(*t),
+            _ => false,
+        }
+    }
+}
+
+impl<'a> std::fmt::Display for AttributeValue<'a> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        match self {
+            AttributeValue::Text(a) => write!(f, "{}", a),
+            AttributeValue::Float32(a) => write!(f, "{}", a),
+            AttributeValue::Float64(a) => write!(f, "{}", a),
+            AttributeValue::Int32(a) => write!(f, "{}", a),
+            AttributeValue::Int64(a) => write!(f, "{}", a),
+            AttributeValue::Uint32(a) => write!(f, "{}", a),
+            AttributeValue::Uint64(a) => write!(f, "{}", a),
+            AttributeValue::Bool(a) => write!(f, "{}", a),
+            AttributeValue::Vec3Float(_, _, _) => todo!(),
+            AttributeValue::Vec3Int(_, _, _) => todo!(),
+            AttributeValue::Vec3Uint(_, _, _) => todo!(),
+            AttributeValue::Vec4Float(_, _, _, _) => todo!(),
+            AttributeValue::Vec4Int(_, _, _, _) => todo!(),
+            AttributeValue::Vec4Uint(_, _, _, _) => todo!(),
+            AttributeValue::Bytes(_) => todo!(),
+            AttributeValue::Any(_) => todo!(),
+        }
+    }
+}
+
+#[derive(Clone, Copy)]
+pub struct ArbitraryAttributeValue<'a> {
+    pub value: &'a dyn std::any::Any,
+    pub cmp: fn(&'a dyn std::any::Any, &'a dyn std::any::Any) -> bool,
+}
+
+impl PartialEq for ArbitraryAttributeValue<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        (self.cmp)(self.value, other.value)
+    }
+}
+
+impl std::fmt::Debug for ArbitraryAttributeValue<'_> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("ArbitraryAttributeValue").finish()
+    }
+}
+
+#[cfg(feature = "serialize")]
+impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> {
+    fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: serde::Serializer,
+    {
+        panic!("ArbitraryAttributeValue should not be serialized")
+    }
+}
+#[cfg(feature = "serialize")]
+impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> {
+    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        panic!("ArbitraryAttributeValue is not deserializable!")
+    }
+}
+#[cfg(feature = "serialize")]
+impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> {
+    fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
+    where
+        D: serde::Deserializer<'de>,
+    {
+        panic!("ArbitraryAttributeValue is not deserializable!")
+    }
+}
+
+impl<'a> AttributeValue<'a> {
+    pub fn as_text(&self) -> Option<&'a str> {
+        match self {
+            AttributeValue::Text(s) => Some(s),
+            _ => None,
+        }
+    }
+
+    pub fn as_float32(&self) -> Option<f32> {
+        match self {
+            AttributeValue::Float32(f) => Some(*f),
+            _ => None,
+        }
+    }
+
+    pub fn as_float64(&self) -> Option<f64> {
+        match self {
+            AttributeValue::Float64(f) => Some(*f),
+            _ => None,
+        }
+    }
+
+    pub fn as_int32(&self) -> Option<i32> {
+        match self {
+            AttributeValue::Int32(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_int64(&self) -> Option<i64> {
+        match self {
+            AttributeValue::Int64(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_uint32(&self) -> Option<u32> {
+        match self {
+            AttributeValue::Uint32(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_uint64(&self) -> Option<u64> {
+        match self {
+            AttributeValue::Uint64(i) => Some(*i),
+            _ => None,
+        }
+    }
+
+    pub fn as_bool(&self) -> Option<bool> {
+        match self {
+            AttributeValue::Bool(b) => Some(*b),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
+        match self {
+            AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
+        match self {
+            AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
+        match self {
+            AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
+        match self {
+            AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
+        match self {
+            AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
+            _ => None,
+        }
+    }
+
+    pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
+        match self {
+            AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
+            _ => None,
+        }
+    }
+
+    pub fn as_bytes(&self) -> Option<&[u8]> {
+        match self {
+            AttributeValue::Bytes(b) => Some(b),
+            _ => None,
+        }
+    }
+
+    pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> {
+        match self {
+            AttributeValue::Any(a) => Some(a),
+            _ => None,
+        }
+    }
+}
+
+// #[test]
+// fn test_attribute_value_size() {
+//     assert_eq!(std::mem::size_of::<AttributeValue<'_>>(), 24);
+// }

+ 2 - 0
packages/core/src/lib.rs

@@ -2,6 +2,7 @@
 #![doc = include_str!("../README.md")]
 #![deny(missing_docs)]
 
+pub(crate) mod arbitrary_value;
 pub(crate) mod diff;
 pub(crate) mod events;
 pub(crate) mod lazynodes;
@@ -13,6 +14,7 @@ pub(crate) mod util;
 pub(crate) mod virtual_dom;
 
 pub(crate) mod innerlude {
+    pub use crate::arbitrary_value::*;
     pub use crate::events::*;
     pub use crate::lazynodes::*;
     pub use crate::mutations::*;

+ 4 - 3
packages/core/src/mutations.rs

@@ -167,8 +167,9 @@ pub enum DomEdit<'bump> {
         field: &'static str,
 
         /// The value of the attribute.
-        value: &'bump str,
+        value: AttributeValue<'bump>,
 
+        // value: &'bump str,
         /// The (optional) namespace of the attribute.
         /// For instance, "style" is in the "style" namespace.
         ns: Option<&'bump str>,
@@ -286,7 +287,7 @@ impl<'a> Mutations<'a> {
         self.edits.push(SetText { text, root });
     }
 
-    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute, root: u64) {
+    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: u64) {
         let Attribute {
             name,
             value,
@@ -296,7 +297,7 @@ impl<'a> Mutations<'a> {
 
         self.edits.push(SetAttribute {
             field: name,
-            value,
+            value: value.clone(),
             ns: *namespace,
             root,
         });

+ 20 - 2
packages/core/src/nodes.rs

@@ -4,7 +4,7 @@
 //! cheap and *very* fast to construct - building a full tree should be quick.
 
 use crate::{
-    innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
+    innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
     lazynodes::LazyNodes,
     AnyEvent, Component,
 };
@@ -339,7 +339,7 @@ pub struct Attribute<'a> {
     pub name: &'static str,
 
     /// The value of the attribute.
-    pub value: &'a str,
+    pub value: AttributeValue<'a>,
 
     /// An indication if this attribute can be ignored during diffing
     ///
@@ -610,6 +610,24 @@ impl<'a> NodeFactory<'a> {
         is_volatile: bool,
     ) -> Attribute<'a> {
         let (value, is_static) = self.raw_text(val);
+        Attribute {
+            name,
+            value: AttributeValue::Text(value),
+            is_static,
+            namespace,
+            is_volatile,
+        }
+    }
+
+    /// Create a new [`Attribute`] using non-arguments
+    pub fn custom_attr(
+        &self,
+        name: &'static str,
+        value: AttributeValue<'a>,
+        namespace: Option<&'static str>,
+        is_volatile: bool,
+        is_static: bool,
+    ) -> Attribute<'a> {
         Attribute {
             name,
             value,

+ 2 - 6
packages/desktop/Cargo.toml

@@ -20,7 +20,7 @@ serde = "1.0.136"
 serde_json = "1.0.79"
 thiserror = "1.0.30"
 log = "0.4.14"
-wry = { version = "0.13.1" }
+wry = { version = "0.16.0" }
 futures-channel = "0.3.21"
 tokio = { version = "1.16.1", features = [
     "sync",
@@ -28,7 +28,7 @@ tokio = { version = "1.16.1", features = [
     "rt",
     "time",
 ], optional = true, default-features = false }
-webbrowser = "0.6.0"
+webbrowser = "0.7.1"
 mime_guess = "2.0.3"
 dunce = "1.0.2"
 
@@ -38,13 +38,9 @@ core-foundation = "0.9.3"
 [features]
 default = ["tokio_runtime"]
 tokio_runtime = ["tokio"]
-
-devtool = ["wry/devtool"]
 fullscreen = ["wry/fullscreen"]
 transparent = ["wry/transparent"]
-
 tray = ["wry/tray"]
-ayatana = ["wry/ayatana"]
 
 
 [dev-dependencies]

+ 1 - 1
packages/desktop/src/desktop_context.rs

@@ -191,7 +191,7 @@ pub(super) fn handler(
         SetTitle(content) => window.set_title(&content),
         SetDecorations(state) => window.set_decorations(state),
 
-        DevTool => webview.devtool(),
+        DevTool => {}
 
         Eval(code) => webview
             .evaluate_script(code.as_str())

+ 1 - 1
packages/desktop/src/lib.rs

@@ -200,7 +200,7 @@ pub fn launch_with_props<P: 'static + Send>(
                     )
                 } else {
                     // in debug, we are okay with the reload menu showing and dev tool
-                    webview = webview.with_dev_tool(true);
+                    webview = webview.with_devtools(true);
                 }
 
                 desktop.webviews.insert(window_id, webview.build().unwrap());

+ 4 - 2
packages/ssr/src/lib.rs

@@ -186,7 +186,9 @@ impl<'a> TextRenderer<'a, '_> {
                 while let Some(attr) = attr_iter.next() {
                     match attr.namespace {
                         None => match attr.name {
-                            "dangerous_inner_html" => inner_html = Some(attr.value),
+                            "dangerous_inner_html" => {
+                                inner_html = Some(attr.value.as_text().unwrap())
+                            }
                             "allowfullscreen"
                             | "allowpaymentrequest"
                             | "async"
@@ -213,7 +215,7 @@ impl<'a> TextRenderer<'a, '_> {
                             | "reversed"
                             | "selected"
                             | "truespeed" => {
-                                if attr.value != "false" {
+                                if attr.value.is_truthy() {
                                     write!(f, " {}=\"{}\"", attr.name, attr.value)?
                                 }
                             }

+ 63 - 75
packages/tui/src/hooks.rs

@@ -192,16 +192,6 @@ impl InnerInputState {
         layout: &Stretch,
         dom: &mut Dom,
     ) {
-        struct Data<'b> {
-            new_pos: (i32, i32),
-            old_pos: Option<(i32, i32)>,
-            clicked: bool,
-            released: bool,
-            wheel_delta: f64,
-            mouse_data: &'b MouseData,
-            wheel_data: &'b Option<WheelData>,
-        }
-
         fn layout_contains_point(layout: &Layout, point: (i32, i32)) -> bool {
             layout.location.x as i32 <= point.0
                 && layout.location.x as i32 + layout.size.width as i32 >= point.0
@@ -234,48 +224,49 @@ impl InnerInputState {
             }
         }
 
+        fn prepare_mouse_data(mouse_data: &MouseData, layout: &Layout) -> MouseData {
+            let mut data = mouse_data.clone();
+            data.offset_x = data.client_x - layout.location.x as i32;
+            data.offset_y = data.client_y - layout.location.y as i32;
+            data
+        }
+
         if let Some(mouse) = &self.mouse {
             let new_pos = (mouse.0.screen_x, mouse.0.screen_y);
             let old_pos = previous_mouse
                 .as_ref()
                 .map(|m| (m.0.screen_x, m.0.screen_y));
-            let clicked =
-                (!mouse.0.buttons & previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0;
+            // the a mouse button is pressed if a button was not down and is now down
+            let pressed =
+                (mouse.0.buttons & !previous_mouse.as_ref().map(|m| m.0.buttons).unwrap_or(0)) > 0;
+            // the a mouse button is pressed if a button was down and is now not down
             let released =
-                (mouse.0.buttons & !previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0;
+                (!mouse.0.buttons & previous_mouse.map(|m| m.0.buttons).unwrap_or(0)) > 0;
             let wheel_delta = self.wheel.as_ref().map_or(0.0, |w| w.delta_y);
             let mouse_data = &mouse.0;
             let wheel_data = &self.wheel;
-            let data = Data {
-                new_pos,
-                old_pos,
-                clicked,
-                released,
-                wheel_delta,
-                mouse_data,
-                wheel_data,
-            };
 
             {
                 // mousemove
-                let mut will_bubble = FxHashSet::default();
-                for node in dom.get_listening_sorted("mousemove") {
-                    let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let previously_contained = data
-                        .old_pos
-                        .filter(|pos| layout_contains_point(node_layout, *pos))
-                        .is_some();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
-
-                    if currently_contains && previously_contained {
-                        try_create_event(
-                            "mousemove",
-                            Arc::new(data.mouse_data.clone()),
-                            &mut will_bubble,
-                            resolved_events,
-                            node,
-                            dom,
-                        );
+                if old_pos != Some(new_pos) {
+                    let mut will_bubble = FxHashSet::default();
+                    for node in dom.get_listening_sorted("mousemove") {
+                        let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
+                        let previously_contained = old_pos
+                            .filter(|pos| layout_contains_point(node_layout, *pos))
+                            .is_some();
+                        let currently_contains = layout_contains_point(node_layout, new_pos);
+
+                        if currently_contains && previously_contained {
+                            try_create_event(
+                                "mousemove",
+                                Arc::new(prepare_mouse_data(mouse_data, node_layout)),
+                                &mut will_bubble,
+                                resolved_events,
+                                node,
+                                dom,
+                            );
+                        }
                     }
                 }
             }
@@ -285,16 +276,15 @@ impl InnerInputState {
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseenter") {
                     let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let previously_contained = data
-                        .old_pos
+                    let previously_contained = old_pos
                         .filter(|pos| layout_contains_point(node_layout, *pos))
                         .is_some();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
                     if currently_contains && !previously_contained {
                         try_create_event(
                             "mouseenter",
-                            Arc::new(data.mouse_data.clone()),
+                            Arc::new(mouse_data.clone()),
                             &mut will_bubble,
                             resolved_events,
                             node,
@@ -309,16 +299,15 @@ impl InnerInputState {
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseover") {
                     let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let previously_contained = data
-                        .old_pos
+                    let previously_contained = old_pos
                         .filter(|pos| layout_contains_point(node_layout, *pos))
                         .is_some();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
                     if currently_contains && !previously_contained {
                         try_create_event(
                             "mouseover",
-                            Arc::new(data.mouse_data.clone()),
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
                             &mut will_bubble,
                             resolved_events,
                             node,
@@ -328,17 +317,17 @@ impl InnerInputState {
                 }
             }
 
-            {
-                // mousedown
+            // mousedown
+            if pressed {
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mousedown") {
                     let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
-                    if currently_contains && data.clicked {
+                    if currently_contains {
                         try_create_event(
                             "mousedown",
-                            Arc::new(data.mouse_data.clone()),
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
                             &mut will_bubble,
                             resolved_events,
                             node,
@@ -353,12 +342,12 @@ impl InnerInputState {
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseup") {
                     let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
-                    if currently_contains && data.released {
+                    if currently_contains && released {
                         try_create_event(
                             "mouseup",
-                            Arc::new(data.mouse_data.clone()),
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
                             &mut will_bubble,
                             resolved_events,
                             node,
@@ -373,12 +362,12 @@ impl InnerInputState {
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("click") {
                     let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
-                    if currently_contains && data.released && data.mouse_data.button == 0 {
+                    if currently_contains && released && mouse_data.button == 0 {
                         try_create_event(
                             "click",
-                            Arc::new(data.mouse_data.clone()),
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
                             &mut will_bubble,
                             resolved_events,
                             node,
@@ -393,12 +382,12 @@ impl InnerInputState {
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("contextmenu") {
                     let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
-                    if currently_contains && data.released && data.mouse_data.button == 2 {
+                    if currently_contains && released && mouse_data.button == 2 {
                         try_create_event(
                             "contextmenu",
-                            Arc::new(data.mouse_data.clone()),
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
                             &mut will_bubble,
                             resolved_events,
                             node,
@@ -413,10 +402,10 @@ impl InnerInputState {
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("wheel") {
                     let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
-                    if let Some(w) = data.wheel_data {
-                        if currently_contains && data.wheel_delta != 0.0 {
+                    if let Some(w) = wheel_data {
+                        if currently_contains && wheel_delta != 0.0 {
                             try_create_event(
                                 "wheel",
                                 Arc::new(w.clone()),
@@ -435,16 +424,15 @@ impl InnerInputState {
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseleave") {
                     let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let previously_contained = data
-                        .old_pos
+                    let previously_contained = old_pos
                         .filter(|pos| layout_contains_point(node_layout, *pos))
                         .is_some();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
                     if !currently_contains && previously_contained {
                         try_create_event(
                             "mouseleave",
-                            Arc::new(data.mouse_data.clone()),
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
                             &mut will_bubble,
                             resolved_events,
                             node,
@@ -459,16 +447,15 @@ impl InnerInputState {
                 let mut will_bubble = FxHashSet::default();
                 for node in dom.get_listening_sorted("mouseout") {
                     let node_layout = layout.layout(node.state.layout.node.unwrap()).unwrap();
-                    let previously_contained = data
-                        .old_pos
+                    let previously_contained = old_pos
                         .filter(|pos| layout_contains_point(node_layout, *pos))
                         .is_some();
-                    let currently_contains = layout_contains_point(node_layout, data.new_pos);
+                    let currently_contains = layout_contains_point(node_layout, new_pos);
 
                     if !currently_contains && previously_contained {
                         try_create_event(
                             "mouseout",
-                            Arc::new(data.mouse_data.clone()),
+                            Arc::new(prepare_mouse_data(mouse_data, node_layout)),
                             &mut will_bubble,
                             resolved_events,
                             node,
@@ -606,13 +593,14 @@ fn get_event(evt: TermEvent) -> Option<(&'static str, EventData)> {
 
                 // from https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
 
-                // The `offset`, `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively.
+                // The `page` and `screen` coordinates are inconsistent with the MDN definition, as they are relative to the viewport (client), not the target element/page/screen, respectively.
                 // todo?
                 // But then, MDN defines them in terms of pixels, yet crossterm provides only row/column, and it might not be possible to get pixels. So we can't get 100% consistency anyway.
                 let coordinates = Coordinates::new(
                     ScreenPoint::new(x, y),
                     ClientPoint::new(x, y),
-                    ElementPoint::new(x, y),
+                    // offset x/y are set when the origin of the event is assigned to an element
+                    ElementPoint::new(0., 0.),
                     PagePoint::new(x, y),
                 );
 

+ 5 - 3
packages/tui/src/layout.rs

@@ -79,9 +79,11 @@ impl ChildDepState for StretchLayout {
             }
         } else {
             // gather up all the styles from the attribute list
-            for &Attribute { name, value, .. } in node.attributes() {
-                assert!(SORTED_LAYOUT_ATTRS.binary_search(&name).is_ok());
-                apply_layout_attributes(name, value, &mut style);
+            for Attribute { name, value, .. } in node.attributes() {
+                assert!(SORTED_LAYOUT_ATTRS.binary_search(name).is_ok());
+                if let Some(text) = value.as_text() {
+                    apply_layout_attributes(name, text, &mut style);
+                }
             }
 
             // the root node fills the entire area

+ 4 - 2
packages/tui/src/style_attributes.rs

@@ -78,8 +78,10 @@ impl ParentDepState for StyleModifier {
         }
 
         // gather up all the styles from the attribute list
-        for &Attribute { name, value, .. } in node.attributes() {
-            apply_style_attributes(name, value, &mut new);
+        for Attribute { name, value, .. } in node.attributes() {
+            if let Some(text) = value.as_text() {
+                apply_style_attributes(name, text, &mut new);
+            }
         }
 
         // keep the text styling from the parent element

+ 1 - 1
packages/web/Cargo.toml

@@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 dioxus-core = { path = "../core", version = "^0.2.1" }
 dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
 dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
-    "web",
+    "web"
 ] }
 
 js-sys = "0.3.56"

+ 1 - 1
packages/web/src/dom.rs

@@ -146,7 +146,7 @@ impl WebsysDom {
                     value,
                     ns,
                 } => {
-                    let value = serde_wasm_bindgen::to_value(value).unwrap();
+                    let value = serde_wasm_bindgen::to_value(&value).unwrap();
                     self.interpreter.SetAttribute(root, field, value, ns)
                 }
             }