浏览代码

Merge branch 'upstream' into simplify-native-core

Evan Almloff 2 年之前
父节点
当前提交
fb0c6c1b62

+ 37 - 0
.github/workflows/docs stable.yml

@@ -0,0 +1,37 @@
+name: docs stable
+
+on:
+  workflow_dispatch:
+
+jobs:
+  build-deploy:
+    runs-on: ubuntu-latest
+    environment: docs
+    steps:
+
+      # NOTE: Comment out when https://github.com/rust-lang/mdBook/pull/1306 is merged and released
+      # - name: Setup mdBook
+      #   uses: peaceiris/actions-mdbook@v1
+      #   with:
+      #     mdbook-version: "0.4.10"
+
+      # NOTE: Delete when the previous one is enabled
+      - name: Setup mdBook
+        run: |
+          cargo install mdbook --git https://github.com/Demonthos/mdBook.git --branch master
+      - uses: actions/checkout@v3
+
+      - name: Build
+        run: cd docs &&
+          cd guide && mdbook build -d ../nightly/guide && cd .. &&
+          cd router && mdbook build -d ../nightly/router  && cd ..
+
+      - name: Deploy 🚀
+        uses: JamesIves/github-pages-deploy-action@v4.4.1
+        with:
+          branch: gh-pages # The branch the action should deploy to.
+          folder: docs/nightly # The folder the action should deploy.
+          target-folder: docs
+          repository-name: dioxuslabs/docsite
+          clean: false
+          token: ${{ secrets.DEPLOY_KEY }} # let's pretend I don't need it for now

+ 1 - 33
examples/README.md

@@ -18,8 +18,6 @@ cargo run --example hello_world
 
 [custom_assets](./custom_assets.rs) - Include images
 
-[custom_element](./custom_element.rs) - Render webcomponents
-
 [custom_html](./custom_html.rs) - Customize wrapper HTML
 
 [eval](./eval.rs) - Evaluate JS expressions
@@ -132,36 +130,6 @@ cargo run --example hello_world
 
 [todomvc](./todomvc.rs) - Todo task list example
 
-## Terminal UI
-
-[tui_all_events](../packages/tui/examples/tui_all_events.rs) - All of the terminal events
-
-[tui_border](../packages/tui/examples/tui_border.rs) - Different styles of borders and corners
-
-[tui_buttons](../packages/tui/examples/tui_buttons.rs) - A grid of buttons that work demonstrate the focus system
-
-[tui_color_test](../packages/tui/examples/tui_color_test.rs) - Grid of colors to demonstrate compatablility with different terminal color rendering modes
-
-[tui_colorpicker](../packages/tui/examples/tui_colorpicker.rs) - A colorpicker
-
-[tui_components](../packages/tui/examples/tui_components.rs) - Simple component example
-
-[tui_flex](../packages/tui/examples/tui_flex.rs) - Flexbox support
-
-[tui_hover](../packages/tui/examples/tui_hover.rs) - Detect hover and mouse events
-
-[tui_list](../packages/tui/examples/tui_list.rs) - Renders a list of items
-
-[tui_margin](../packages/tui/examples/tui_margin.rs) - Margins around flexboxes
-
-[tui_quadrants](../packages/tui/examples/tui_quadrants.rs) - Four quadrants
-
-[tui_readme](../packages/tui/examples/tui_readme.rs) - The readme example
-
-[tui_task](../packages/tui/examples/tui_task.rs) - Background tasks
-
-[tui_text](../packages/tui/examples/tui_text.rs) - Simple text example
-
 # TODO
 Missing Features
 - Fine-grained reactivity
@@ -175,4 +143,4 @@ Missing examples
 - Custom elements
 - Component Children: Pass children into child components
 - Render To string: Render a mounted virtualdom to a string
-- Testing and Debugging
+- Testing and Debugging

+ 42 - 0
examples/window_focus.rs

@@ -0,0 +1,42 @@
+use dioxus::prelude::*;
+use dioxus_desktop::tao::event::WindowEvent;
+use dioxus_desktop::use_wry_event_handler;
+use dioxus_desktop::wry::application::event::Event as WryEvent;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let focused = use_state(cx, || false);
+
+    use_wry_event_handler(cx, {
+        to_owned![focused];
+        move |event, _| {
+            if let WryEvent::WindowEvent {
+                event: WindowEvent::Focused(new_focused),
+                ..
+            } = event
+            {
+                focused.set(*new_focused);
+            }
+        }
+    });
+
+    cx.render(rsx! {
+        div{
+            width: "100%",
+            height: "100%",
+            display: "flex",
+            flex_direction: "column",
+            align_items: "center",
+            {
+                if *focused.get() {
+                    "This window is focused!"
+                } else {
+                    "This window is not focused!"
+                }
+            }
+        }
+    })
+}

+ 2 - 4
packages/autofmt/src/element.rs

@@ -328,10 +328,8 @@ impl Writer<'_> {
                     None
                 }
             }
-            [BodyNode::RawExpr(ref expr)] => {
-                // TODO: let rawexprs to be inlined
-                get_expr_length(expr)
-            }
+            // TODO: let rawexprs to be inlined
+            [BodyNode::RawExpr(ref expr)] => get_expr_length(expr),
             [BodyNode::Element(ref el)] => {
                 let attr_len = self.is_short_attrs(&el.attributes);
 

+ 6 - 6
packages/autofmt/src/expr.rs

@@ -3,7 +3,7 @@ use std::fmt::{Result, Write};
 
 use proc_macro2::Span;
 
-use crate::Writer;
+use crate::{collect_macros::byte_offset, Writer};
 
 impl Writer<'_> {
     pub fn write_raw_expr(&mut self, placement: Span) -> Result {
@@ -16,11 +16,11 @@ impl Writer<'_> {
 
         // if the expr is on one line, just write it directly
         if start.line == end.line {
-            write!(
-                self.out,
-                "{}",
-                &self.src[start.line - 1][start.column - 1..end.column].trim()
-            )?;
+            // split counting utf8 chars
+            let start = byte_offset(self.raw_src, start);
+            let end = byte_offset(self.raw_src, end);
+            let row = self.raw_src[start..end].trim();
+            write!(self.out, "{}", row)?;
             return Ok(());
         }
 

+ 31 - 42
packages/autofmt/src/lib.rs

@@ -1,6 +1,6 @@
 use crate::writer::*;
 use collect_macros::byte_offset;
-use dioxus_rsx::CallBody;
+use dioxus_rsx::{BodyNode, CallBody};
 use proc_macro2::LineColumn;
 use syn::{ExprMacro, MacroDelimiter};
 
@@ -53,10 +53,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
         return formatted_blocks;
     }
 
-    let mut writer = Writer {
-        src: contents.lines().collect::<Vec<_>>(),
-        ..Writer::default()
-    };
+    let mut writer = Writer::new(contents);
 
     // Dont parse nested macros
     let mut end_span = LineColumn { column: 0, line: 0 };
@@ -79,12 +76,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
             .count()
             / 4;
 
-        // Oneliner optimization
-        if writer.is_short_children(&body.roots).is_some() {
-            writer.write_ident(&body.roots[0]).unwrap();
-        } else {
-            writer.write_body_indented(&body.roots).unwrap();
-        }
+        write_body(&mut writer, &body);
 
         // writing idents leaves the final line ended at the end of the last ident
         if writer.out.buf.contains('\n') {
@@ -105,7 +97,11 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
         let start = byte_offset(contents, span.start()) + 1;
         let end = byte_offset(contents, span.end()) - 1;
 
-        if formatted.len() <= 80 && !formatted.contains('\n') {
+        // Rustfmt will remove the space between the macro and the opening paren if the macro is a single expression
+        let body_is_solo_expr = body.roots.len() == 1
+            && matches!(body.roots[0], BodyNode::RawExpr(_) | BodyNode::Text(_));
+
+        if formatted.len() <= 80 && !formatted.contains('\n') && !body_is_solo_expr {
             formatted = format!(" {} ", formatted);
         }
 
@@ -126,35 +122,38 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
 }
 
 pub fn write_block_out(body: CallBody) -> Option<String> {
-    let mut buf = Writer {
-        src: vec![""],
-        ..Writer::default()
-    };
+    let mut buf = Writer::new("");
+
+    write_body(&mut buf, &body);
+
+    buf.consume()
+}
+
+fn write_body(buf: &mut Writer, body: &CallBody) {
+    use std::fmt::Write;
 
-    // Oneliner optimization
     if buf.is_short_children(&body.roots).is_some() {
-        buf.write_ident(&body.roots[0]).unwrap();
+        // write all the indents with spaces and commas between
+        for idx in 0..body.roots.len() - 1 {
+            let ident = &body.roots[idx];
+            buf.write_ident(ident).unwrap();
+            write!(&mut buf.out.buf, ", ").unwrap();
+        }
+
+        // write the last ident without a comma
+        let ident = &body.roots[body.roots.len() - 1];
+        buf.write_ident(ident).unwrap();
     } else {
         buf.write_body_indented(&body.roots).unwrap();
     }
-
-    buf.consume()
 }
 
 pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
     let body = syn::parse2::<CallBody>(expr.mac.tokens).unwrap();
 
-    let mut buf = Writer {
-        src: raw.lines().collect(),
-        ..Writer::default()
-    };
+    let mut buf = Writer::new(raw);
 
-    // Oneliner optimization
-    if buf.is_short_children(&body.roots).is_some() {
-        buf.write_ident(&body.roots[0]).unwrap();
-    } else {
-        buf.write_body_indented(&body.roots).unwrap();
-    }
+    write_body(&mut buf, &body);
 
     buf.consume()
 }
@@ -162,19 +161,11 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
 pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
     let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
 
-    let mut buf = Writer {
-        src: block.lines().collect(),
-        ..Writer::default()
-    };
+    let mut buf = Writer::new(block);
 
     buf.out.indent = indent_level;
 
-    // Oneliner optimization
-    if buf.is_short_children(&body.roots).is_some() {
-        buf.write_ident(&body.roots[0]).unwrap();
-    } else {
-        buf.write_body_indented(&body.roots).unwrap();
-    }
+    write_body(&mut buf, &body);
 
     // writing idents leaves the final line ended at the end of the last ident
     if buf.out.buf.contains('\n') {
@@ -191,8 +182,6 @@ pub fn apply_format(input: &str, block: FormattedBlock) -> String {
     let (left, _) = input.split_at(start);
     let (_, right) = input.split_at(end);
 
-    // dbg!(&block.formatted);
-
     format!("{}{}{}", left, block.formatted, right)
 }
 

+ 14 - 2
packages/autofmt/src/writer.rs

@@ -9,8 +9,9 @@ use syn::{spanned::Spanned, Expr, ExprIf};
 
 use crate::buffer::Buffer;
 
-#[derive(Debug, Default)]
+#[derive(Debug)]
 pub struct Writer<'a> {
+    pub raw_src: &'a str,
     pub src: Vec<&'a str>,
     pub cached_formats: HashMap<Location, String>,
     pub comments: VecDeque<usize>,
@@ -31,7 +32,18 @@ impl Location {
     }
 }
 
-impl Writer<'_> {
+impl<'a> Writer<'a> {
+    pub fn new(raw_src: &'a str) -> Self {
+        let src = raw_src.lines().collect();
+        Self {
+            raw_src,
+            src,
+            cached_formats: HashMap::new(),
+            comments: VecDeque::new(),
+            out: Buffer::default(),
+        }
+    }
+
     // Expects to be written directly into place
     pub fn write_ident(&mut self, node: &BodyNode) -> Result {
         match node {

+ 4 - 1
packages/autofmt/tests/samples.rs

@@ -37,5 +37,8 @@ twoway![
     long_exprs,
     ifchain_forloop,
     t2,
-    reallylong
+    reallylong,
+    immediate_expr,
+    collapse_expr,
+    trailing_expr
 ];

+ 4 - 0
packages/autofmt/tests/samples/collapse_expr.rsx

@@ -0,0 +1,4 @@
+fn itworks() {
+    rsx!( "{name}", "{name}", "{name}" )
+}
+

+ 4 - 0
packages/autofmt/tests/samples/immediate_expr.rsx

@@ -0,0 +1,4 @@
+fn it_works() {
+    cx.render(rsx!(()))
+}
+

+ 7 - 0
packages/autofmt/tests/samples/trailing_expr.rsx

@@ -0,0 +1,7 @@
+fn it_works() {
+    cx.render(rsx! {
+        div {
+            span { "Description: ", package.description.as_deref().unwrap_or("❌❌❌❌ missing") }
+        }
+    })
+}

+ 1 - 1
packages/core-macro/src/inlineprops.rs

@@ -158,7 +158,7 @@ impl ToTokens for InlinePropsBody {
             #maybe_async #vis fn #ident #fn_generics (#cx_token: Scope<#scope_lifetime #struct_name #generics>) #output
             #where_clause
             {
-                let #struct_name { #(#field_names),* } = &cx.props;
+                let #struct_name { #(#field_names),* } = &#cx_token.props;
                 #block
             }
         });

+ 13 - 29
packages/core/src/arena.rs

@@ -88,7 +88,9 @@ impl VirtualDom {
     }
 
     // Drop a scope and all its children
-    pub(crate) fn drop_scope(&mut self, id: ScopeId) {
+    //
+    // Note: This will not remove any ids from the arena
+    pub(crate) fn drop_scope(&mut self, id: ScopeId, recursive: bool) {
         self.dirty_scopes.remove(&DirtyScope {
             height: self.scopes[id.0].height,
             id,
@@ -96,19 +98,14 @@ impl VirtualDom {
 
         self.ensure_drop_safety(id);
 
-        if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
-            if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
-                self.drop_scope_inner(node)
-            }
-        }
-        if let Some(root) = unsafe { self.scopes[id.0].as_ref().previous_frame().try_load_node() } {
-            if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
-                self.drop_scope_inner(node)
+        if recursive {
+            if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
+                if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
+                    self.drop_scope_inner(node)
+                }
             }
         }
 
-        self.scopes[id.0].props.take();
-
         let scope = &mut self.scopes[id.0];
 
         // Drop all the hooks once the children are dropped
@@ -121,37 +118,24 @@ impl VirtualDom {
         for task_id in scope.spawned_tasks.borrow_mut().drain() {
             scope.tasks.remove(task_id);
         }
+
+        self.scopes.remove(id.0);
     }
 
     fn drop_scope_inner(&mut self, node: &VNode) {
-        node.clear_listeners();
         node.dynamic_nodes.iter().for_each(|node| match node {
             DynamicNode::Component(c) => {
                 if let Some(f) = c.scope.get() {
-                    self.drop_scope(f);
+                    self.drop_scope(f, true);
                 }
                 c.props.take();
             }
             DynamicNode::Fragment(nodes) => {
                 nodes.iter().for_each(|node| self.drop_scope_inner(node))
             }
-            DynamicNode::Placeholder(t) => {
-                if let Some(id) = t.id.get() {
-                    self.try_reclaim(id);
-                }
-            }
-            DynamicNode::Text(t) => {
-                if let Some(id) = t.id.get() {
-                    self.try_reclaim(id);
-                }
-            }
+            DynamicNode::Placeholder(_) => {}
+            DynamicNode::Text(_) => {}
         });
-
-        for id in &node.root_ids {
-            if id.0 != 0 {
-                self.try_reclaim(id);
-            }
-        }
     }
 
     /// Descend through the tree, removing any borrowed props and listeners

+ 1 - 1
packages/core/src/diff.rs

@@ -934,7 +934,7 @@ impl<'b> VirtualDom {
         *comp.props.borrow_mut() = unsafe { std::mem::transmute(props) };
 
         // Now drop all the resouces
-        self.drop_scope(scope);
+        self.drop_scope(scope, false);
     }
 
     fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {

+ 0 - 8
packages/core/src/nodes.rs

@@ -195,14 +195,6 @@ impl<'a> VNode<'a> {
             }
         }
     }
-
-    pub(crate) fn clear_listeners(&self) {
-        for attr in self.dynamic_attrs {
-            if let AttributeValue::Listener(l) = &attr.value {
-                l.borrow_mut().take();
-            }
-        }
-    }
 }
 
 /// A static layout of a UI tree that describes a set of dynamic and static nodes.

+ 1 - 1
packages/core/src/virtual_dom.rs

@@ -682,6 +682,6 @@ impl VirtualDom {
 impl Drop for VirtualDom {
     fn drop(&mut self) {
         // Simply drop this scope which drops all of its children
-        self.drop_scope(ScopeId(0));
+        self.drop_scope(ScopeId(0), true);
     }
 }

+ 1 - 0
packages/desktop/Cargo.toml

@@ -32,6 +32,7 @@ tokio = { version = "1.16.1", features = [
 webbrowser = "0.8.0"
 infer = "0.11.0"
 dunce = "1.0.2"
+slab = "0.4"
 
 interprocess = { version = "1.1.1", optional = true }
 futures-util = "0.3.25"

+ 0 - 10
packages/desktop/src/cfg.rs

@@ -16,7 +16,6 @@ pub struct Config {
     pub(crate) file_drop_handler: Option<DropHandler>,
     pub(crate) protocols: Vec<WryProtocol>,
     pub(crate) pre_rendered: Option<String>,
-    // pub(crate) event_handler: Option<Box<DynEventHandlerFn>>,
     pub(crate) disable_context_menu: bool,
     pub(crate) resource_dir: Option<PathBuf>,
     pub(crate) custom_head: Option<String>,
@@ -77,15 +76,6 @@ impl Config {
         self
     }
 
-    // /// Set a custom event handler
-    // pub fn with_event_handler(
-    //     mut self,
-    //     handler: impl Fn(&mut EventLoop<()>, &mut WebView) + 'static,
-    // ) -> Self {
-    //     self.event_handler = Some(Box::new(handler));
-    //     self
-    // }
-
     /// Set a file drop handler
     pub fn with_file_drop_handler(
         mut self,

+ 134 - 0
packages/desktop/src/desktop_context.rs

@@ -10,6 +10,8 @@ use crate::WebviewHandler;
 use dioxus_core::ScopeState;
 use dioxus_core::VirtualDom;
 use serde_json::Value;
+use slab::Slab;
+use wry::application::event::Event;
 use wry::application::event_loop::EventLoopProxy;
 use wry::application::event_loop::EventLoopWindowTarget;
 #[cfg(target_os = "ios")]
@@ -57,6 +59,8 @@ pub struct DesktopContext {
 
     pub(crate) event_loop: EventLoopWindowTarget<UserWindowEvent>,
 
+    pub(crate) event_handlers: WindowEventHandlers,
+
     #[cfg(target_os = "ios")]
     pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
 }
@@ -76,6 +80,7 @@ impl DesktopContext {
         proxy: ProxyType,
         event_loop: EventLoopWindowTarget<UserWindowEvent>,
         webviews: WebviewQueue,
+        event_handlers: WindowEventHandlers,
     ) -> Self {
         Self {
             webview,
@@ -83,6 +88,7 @@ impl DesktopContext {
             event_loop,
             eval: tokio::sync::broadcast::channel(8).0,
             pending_windows: webviews,
+            event_handlers,
             #[cfg(target_os = "ios")]
             views: Default::default(),
         }
@@ -102,6 +108,7 @@ impl DesktopContext {
             &self.proxy,
             dom,
             &self.pending_windows,
+            &self.event_handlers,
         );
 
         let id = window.webview.window().id();
@@ -216,6 +223,22 @@ impl DesktopContext {
         EvalResult::new(self.eval.clone())
     }
 
+    /// Create a wry event handler that listens for wry events.
+    /// This event handler is scoped to the currently active window and will only recieve events that are either global or related to the current window.
+    ///
+    /// The id this function returns can be used to remove the event handler with [`DesktopContext::remove_wry_event_handler`]
+    pub fn create_wry_event_handler(
+        &self,
+        handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
+    ) -> WryEventHandlerId {
+        self.event_handlers.add(self.id(), handler)
+    }
+
+    /// Remove a wry event handler created with [`DesktopContext::create_wry_event_handler`]
+    pub fn remove_wry_event_handler(&self, id: WryEventHandlerId) {
+        self.event_handlers.remove(id)
+    }
+
     /// Push an objc view to the window
     #[cfg(target_os = "ios")]
     pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
@@ -276,3 +299,114 @@ fn is_main_thread() -> bool {
     let result: BOOL = unsafe { msg_send![cls, isMainThread] };
     result != NO
 }
+
+/// The unique identifier of a window event handler. This can be used to later remove the handler.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct WryEventHandlerId(usize);
+
+#[derive(Clone, Default)]
+pub(crate) struct WindowEventHandlers {
+    handlers: Rc<RefCell<Slab<WryWindowEventHandlerInner>>>,
+}
+
+impl WindowEventHandlers {
+    pub(crate) fn add(
+        &self,
+        window_id: WindowId,
+        handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
+    ) -> WryEventHandlerId {
+        WryEventHandlerId(
+            self.handlers
+                .borrow_mut()
+                .insert(WryWindowEventHandlerInner {
+                    window_id,
+                    handler: Box::new(handler),
+                }),
+        )
+    }
+
+    pub(crate) fn remove(&self, id: WryEventHandlerId) {
+        self.handlers.borrow_mut().try_remove(id.0);
+    }
+
+    pub(crate) fn apply_event(
+        &self,
+        event: &Event<UserWindowEvent>,
+        target: &EventLoopWindowTarget<UserWindowEvent>,
+    ) {
+        for (_, handler) in self.handlers.borrow_mut().iter_mut() {
+            handler.apply_event(event, target);
+        }
+    }
+}
+
+struct WryWindowEventHandlerInner {
+    window_id: WindowId,
+    handler: WryEventHandlerCallback,
+}
+
+type WryEventHandlerCallback =
+    Box<dyn FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static>;
+
+impl WryWindowEventHandlerInner {
+    fn apply_event(
+        &mut self,
+        event: &Event<UserWindowEvent>,
+        target: &EventLoopWindowTarget<UserWindowEvent>,
+    ) {
+        // if this event does not apply to the window this listener cares about, return
+        match event {
+            Event::WindowEvent { window_id, .. }
+            | Event::MenuEvent {
+                window_id: Some(window_id),
+                ..
+            } => {
+                if *window_id != self.window_id {
+                    return;
+                }
+            }
+            _ => (),
+        }
+        (self.handler)(event, target)
+    }
+}
+
+/// Get a closure that executes any JavaScript in the WebView context.
+pub fn use_wry_event_handler(
+    cx: &ScopeState,
+    handler: impl FnMut(&Event<UserWindowEvent>, &EventLoopWindowTarget<UserWindowEvent>) + 'static,
+) -> &WryEventHandler {
+    let desktop = use_window(cx);
+    cx.use_hook(move || {
+        let desktop = desktop.clone();
+
+        let id = desktop.create_wry_event_handler(handler);
+
+        WryEventHandler {
+            handlers: desktop.event_handlers,
+            id,
+        }
+    })
+}
+
+/// A wry event handler that is scoped to the current component and window. The event handler will only receive events for the window it was created for and global events.
+///
+/// This will automatically be removed when the component is unmounted.
+pub struct WryEventHandler {
+    handlers: WindowEventHandlers,
+    /// The unique identifier of the event handler.
+    pub id: WryEventHandlerId,
+}
+
+impl WryEventHandler {
+    /// Remove the event handler.
+    pub fn remove(&self) {
+        self.handlers.remove(self.id);
+    }
+}
+
+impl Drop for WryEventHandler {
+    fn drop(&mut self) {
+        self.handlers.remove(self.id);
+    }
+}

+ 13 - 3
packages/desktop/src/lib.rs

@@ -16,8 +16,10 @@ mod webview;
 mod hot_reload;
 
 pub use cfg::Config;
-pub use desktop_context::{use_window, DesktopContext};
-use desktop_context::{EventData, UserWindowEvent, WebviewQueue};
+pub use desktop_context::{
+    use_window, use_wry_event_handler, DesktopContext, WryEventHandler, WryEventHandlerId,
+};
+use desktop_context::{EventData, UserWindowEvent, WebviewQueue, WindowEventHandlers};
 use dioxus_core::*;
 use dioxus_html::HtmlEvent;
 pub use eval::{use_eval, EvalResult};
@@ -123,6 +125,9 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
     // Store them in a hashmap so we can remove them when they're closed
     let mut webviews = HashMap::<WindowId, WebviewHandler>::new();
 
+    // We use this to allow dynamically adding and removing window event handlers
+    let event_handlers = WindowEventHandlers::default();
+
     let queue = WebviewQueue::default();
 
     // By default, we'll create a new window when the app starts
@@ -132,11 +137,14 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
         &proxy,
         VirtualDom::new_with_props(root, props),
         &queue,
+        &event_handlers,
     ));
 
-    event_loop.run(move |window_event, _event_loop, control_flow| {
+    event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 
+        event_handlers.apply_event(&window_event, event_loop);
+
         match window_event {
             Event::WindowEvent {
                 event, window_id, ..
@@ -240,6 +248,7 @@ fn create_new_window(
     proxy: &EventLoopProxy<UserWindowEvent>,
     dom: VirtualDom,
     queue: &WebviewQueue,
+    event_handlers: &WindowEventHandlers,
 ) -> WebviewHandler {
     let webview = webview::build(&mut cfg, event_loop, proxy.clone());
 
@@ -248,6 +257,7 @@ fn create_new_window(
         proxy.clone(),
         event_loop.clone(),
         queue.clone(),
+        event_handlers.clone(),
     ));
 
     let id = webview.window().id();

+ 100 - 17
packages/native-core/src/layout_attributes.rs

@@ -29,6 +29,7 @@
 - [ ] pub aspect_ratio: Number, ----> parsing is done, but taffy doesnt support it
 */
 
+use lightningcss::properties::border::LineStyle;
 use lightningcss::properties::{align, display, flex, position, size};
 use lightningcss::{
     properties::{align::GapValue, border::BorderSideWidth, Property, PropertyId},
@@ -45,8 +46,43 @@ use taffy::{
     style::{FlexDirection, PositionType},
 };
 
+#[derive(Default)]
+pub struct LayoutConfigeration {
+    /// the default border widths to use
+    pub border_widths: BorderWidths,
+}
+
+pub struct BorderWidths {
+    /// the default border width to use for thin borders
+    pub thin: f32,
+    /// the default border width to use for medium borders
+    pub medium: f32,
+    /// the default border width to use for thick borders
+    pub thick: f32,
+}
+
+impl Default for BorderWidths {
+    fn default() -> Self {
+        Self {
+            thin: 1.0,
+            medium: 3.0,
+            thick: 5.0,
+        }
+    }
+}
+
 /// applies the entire html namespace defined in dioxus-html
 pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
+    apply_layout_attributes_cfg(name, value, style, &LayoutConfigeration::default())
+}
+
+/// applies the entire html namespace defined in dioxus-html with the specified configeration
+pub fn apply_layout_attributes_cfg(
+    name: &str,
+    value: &str,
+    style: &mut Style,
+    config: &LayoutConfigeration,
+) {
     if let Ok(property) =
         Property::parse_string(PropertyId::from(name), value, ParserOptions::default())
     {
@@ -84,41 +120,85 @@ pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
                 style.position.right = convert_length_percentage_or_auto(inset.right);
             }
             Property::BorderTopWidth(width) => {
-                style.border.top = convert_border_side_width(width);
+                style.border.top = convert_border_side_width(width, &config.border_widths);
             }
             Property::BorderBottomWidth(width) => {
-                style.border.bottom = convert_border_side_width(width);
+                style.border.bottom = convert_border_side_width(width, &config.border_widths);
             }
             Property::BorderLeftWidth(width) => {
-                style.border.left = convert_border_side_width(width);
+                style.border.left = convert_border_side_width(width, &config.border_widths);
             }
             Property::BorderRightWidth(width) => {
-                style.border.right = convert_border_side_width(width);
+                style.border.right = convert_border_side_width(width, &config.border_widths);
             }
             Property::BorderWidth(width) => {
-                style.border.top = convert_border_side_width(width.top);
-                style.border.bottom = convert_border_side_width(width.bottom);
-                style.border.left = convert_border_side_width(width.left);
-                style.border.right = convert_border_side_width(width.right);
+                style.border.top = convert_border_side_width(width.top, &config.border_widths);
+                style.border.bottom =
+                    convert_border_side_width(width.bottom, &config.border_widths);
+                style.border.left = convert_border_side_width(width.left, &config.border_widths);
+                style.border.right = convert_border_side_width(width.right, &config.border_widths);
             }
             Property::Border(border) => {
-                let width = convert_border_side_width(border.width);
+                let width = convert_border_side_width(border.width, &config.border_widths);
                 style.border.top = width;
                 style.border.bottom = width;
                 style.border.left = width;
                 style.border.right = width;
             }
             Property::BorderTop(top) => {
-                style.border.top = convert_border_side_width(top.width);
+                style.border.top = convert_border_side_width(top.width, &config.border_widths);
             }
             Property::BorderBottom(bottom) => {
-                style.border.bottom = convert_border_side_width(bottom.width);
+                style.border.bottom =
+                    convert_border_side_width(bottom.width, &config.border_widths);
             }
             Property::BorderLeft(left) => {
-                style.border.left = convert_border_side_width(left.width);
+                style.border.left = convert_border_side_width(left.width, &config.border_widths);
             }
             Property::BorderRight(right) => {
-                style.border.right = convert_border_side_width(right.width);
+                style.border.right = convert_border_side_width(right.width, &config.border_widths);
+            }
+            Property::BorderTopStyle(line_style) => {
+                if line_style != LineStyle::None {
+                    style.border.top =
+                        convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
+                }
+            }
+            Property::BorderBottomStyle(line_style) => {
+                if line_style != LineStyle::None {
+                    style.border.bottom =
+                        convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
+                }
+            }
+            Property::BorderLeftStyle(line_style) => {
+                if line_style != LineStyle::None {
+                    style.border.left =
+                        convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
+                }
+            }
+            Property::BorderRightStyle(line_style) => {
+                if line_style != LineStyle::None {
+                    style.border.right =
+                        convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
+                }
+            }
+            Property::BorderStyle(styles) => {
+                if styles.top != LineStyle::None {
+                    style.border.top =
+                        convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
+                }
+                if styles.bottom != LineStyle::None {
+                    style.border.bottom =
+                        convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
+                }
+                if styles.left != LineStyle::None {
+                    style.border.left =
+                        convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
+                }
+                if styles.right != LineStyle::None {
+                    style.border.right =
+                        convert_border_side_width(BorderSideWidth::Medium, &config.border_widths);
+                }
             }
             Property::FlexDirection(flex_direction, _) => {
                 use FlexDirection::*;
@@ -330,12 +410,15 @@ fn convert_length_percentage_or_auto(
     }
 }
 
-fn convert_border_side_width(border_side_width: BorderSideWidth) -> Dimension {
+fn convert_border_side_width(
+    border_side_width: BorderSideWidth,
+    border_width_config: &BorderWidths,
+) -> Dimension {
     match border_side_width {
         BorderSideWidth::Length(Length::Value(value)) => convert_length_value(value),
-        BorderSideWidth::Thick => Dimension::Points(5.0),
-        BorderSideWidth::Medium => Dimension::Points(3.0),
-        BorderSideWidth::Thin => Dimension::Points(1.0),
+        BorderSideWidth::Thick => Dimension::Points(border_width_config.thick),
+        BorderSideWidth::Medium => Dimension::Points(border_width_config.medium),
+        BorderSideWidth::Thin => Dimension::Points(border_width_config.thin),
         _ => todo!(),
     }
 }

+ 166 - 81
packages/native-core/src/utils/cursor.rs

@@ -1,87 +1,160 @@
-use std::cmp::Ordering;
+use std::{cmp::Ordering, ops::Range};
 
 use dioxus_html::input_data::keyboard_types::{Code, Key, Modifiers};
 
+/// This contains the information about the text that is used by the cursor to handle navigation.
+pub trait Text {
+    /// Returns the line at the given index.
+    fn line(&self, number: usize) -> Option<&Self>;
+    /// Returns the length of the text in characters.
+    fn length(&self) -> usize;
+    /// Returns the number of lines in the text.
+    fn line_count(&self) -> usize;
+    /// Returns the character at the given character index.
+    fn character(&self, idx: usize) -> Option<char>;
+    /// Returns the length of the text before the given line in characters.
+    fn len_before_line(&self, line: usize) -> usize;
+}
+
+impl Text for str {
+    fn line(&self, number: usize) -> Option<&str> {
+        self.lines().nth(number)
+    }
+
+    fn length(&self) -> usize {
+        self.chars().count()
+    }
+
+    fn line_count(&self) -> usize {
+        self.lines().count()
+    }
+
+    fn character(&self, idx: usize) -> Option<char> {
+        self.chars().nth(idx)
+    }
+
+    fn len_before_line(&self, line: usize) -> usize {
+        self.lines()
+            .take(line)
+            .map(|l| l.chars().count())
+            .sum::<usize>()
+    }
+}
+
+/// This contains the information about the text that is used by the cursor to handle editing text.
+pub trait TextEditable<T: Text + ?Sized>: AsRef<T> {
+    /// Inserts a character at the given character index.
+    fn insert_character(&mut self, idx: usize, text: char);
+    /// Deletes the given character range.
+    fn delete_range(&mut self, range: Range<usize>);
+}
+
+impl TextEditable<str> for String {
+    fn insert_character(&mut self, idx: usize, text: char) {
+        self.insert(idx, text);
+    }
+
+    fn delete_range(&mut self, range: Range<usize>) {
+        self.replace_range(range, "");
+    }
+}
+
+/// A cursor position
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Pos {
+    /// The virtual column of the cursor. This can be more than the line length. To get the realized column, use [`Pos::col()`].
     pub col: usize,
+    /// The row of the cursor.
     pub row: usize,
 }
 
 impl Pos {
+    /// Creates a new cursor position.
     pub fn new(col: usize, row: usize) -> Self {
         Self { row, col }
     }
 
-    pub fn up(&mut self, rope: &str) {
-        self.move_row(-1, rope);
+    /// Moves the position up by one line.
+    pub fn up(&mut self, text: &(impl Text + ?Sized)) {
+        self.move_row(-1, text);
     }
 
-    pub fn down(&mut self, rope: &str) {
-        self.move_row(1, rope);
+    /// Moves the position down by one line.
+    pub fn down(&mut self, text: &(impl Text + ?Sized)) {
+        self.move_row(1, text);
     }
 
-    pub fn right(&mut self, rope: &str) {
-        self.move_col(1, rope);
+    /// Moves the position right by one character.
+    pub fn right(&mut self, text: &(impl Text + ?Sized)) {
+        self.move_col(1, text);
     }
 
-    pub fn left(&mut self, rope: &str) {
-        self.move_col(-1, rope);
+    /// Moves the position left by one character.
+    pub fn left(&mut self, text: &(impl Text + ?Sized)) {
+        self.move_col(-1, text);
     }
 
-    pub fn move_row(&mut self, change: i32, rope: &str) {
+    /// Move the position's row by the given amount. (positive is down, negative is up)
+    pub fn move_row(&mut self, change: i32, text: &(impl Text + ?Sized)) {
         let new = self.row as i32 + change;
-        if new >= 0 && new < rope.lines().count() as i32 {
+        if new >= 0 && new < text.line_count() as i32 {
             self.row = new as usize;
         }
     }
 
-    pub fn move_col(&mut self, change: i32, rope: &str) {
-        self.realize_col(rope);
-        let idx = self.idx(rope) as i32;
-        if idx + change >= 0 && idx + change <= rope.len() as i32 {
-            let len_line = self.len_line(rope) as i32;
+    /// Move the position's column by the given amount. (positive is right, negative is left)
+    pub fn move_col(&mut self, change: i32, text: &(impl Text + ?Sized)) {
+        self.realize_col(text);
+        let idx = self.idx(text) as i32;
+        if idx + change >= 0 && idx + change <= text.length() as i32 {
+            let len_line = self.len_line(text) as i32;
             let new_col = self.col as i32 + change;
             let diff = new_col - len_line;
             if diff > 0 {
-                self.down(rope);
+                self.down(text);
                 self.col = 0;
-                self.move_col(diff - 1, rope);
+                self.move_col(diff - 1, text);
             } else if new_col < 0 {
-                self.up(rope);
-                self.col = self.len_line(rope);
-                self.move_col(new_col + 1, rope);
+                self.up(text);
+                self.col = self.len_line(text);
+                self.move_col(new_col + 1, text);
             } else {
                 self.col = new_col as usize;
             }
         }
     }
 
-    pub fn col(&self, rope: &str) -> usize {
-        self.col.min(self.len_line(rope))
+    /// Get the realized column of the position. This is the column, but capped at the line length.
+    pub fn col(&self, text: &(impl Text + ?Sized)) -> usize {
+        self.col.min(self.len_line(text))
     }
 
+    /// Get the row of the position.
     pub fn row(&self) -> usize {
         self.row
     }
 
-    fn len_line(&self, rope: &str) -> usize {
-        let line = rope.lines().nth(self.row).unwrap_or_default();
-        let len = line.len();
-        if len > 0 && line.chars().nth(len - 1) == Some('\n') {
-            len - 1
+    fn len_line(&self, text: &(impl Text + ?Sized)) -> usize {
+        if let Some(line) = text.line(self.row) {
+            let len = line.length();
+            if len > 0 && line.character(len - 1) == Some('\n') {
+                len - 1
+            } else {
+                len
+            }
         } else {
-            len
+            0
         }
     }
 
-    pub fn idx(&self, rope: &str) -> usize {
-        rope.lines().take(self.row).map(|l| l.len()).sum::<usize>() + self.col(rope)
+    /// Get the character index of the position.
+    pub fn idx(&self, text: &(impl Text + ?Sized)) -> usize {
+        text.len_before_line(self.row) + self.col(text)
     }
 
-    // the column can be more than the line length, cap it
-    pub fn realize_col(&mut self, rope: &str) {
-        self.col = self.col(rope);
+    /// If the column is more than the line length, cap it to the line length.
+    pub fn realize_col(&mut self, text: &(impl Text + ?Sized)) {
+        self.col = self.col(text);
     }
 }
 
@@ -97,13 +170,17 @@ impl PartialOrd for Pos {
     }
 }
 
+/// A cursor is a selection of text. It has a start and end position of the selection.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct Cursor {
+    /// The start position of the selection. The start position is the origin of the selection, not necessarily the first position.
     pub start: Pos,
+    /// The end position of the selection. If the end position is None, the cursor is a caret.
     pub end: Option<Pos>,
 }
 
 impl Cursor {
+    /// Create a new cursor with the given start position.
     pub fn from_start(pos: Pos) -> Self {
         Self {
             start: pos,
@@ -111,6 +188,7 @@ impl Cursor {
         }
     }
 
+    /// Create a new cursor with the given start and end position.
     pub fn new(start: Pos, end: Pos) -> Self {
         Self {
             start,
@@ -118,7 +196,8 @@ impl Cursor {
         }
     }
 
-    fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) {
+    /// Move the cursor position. If shift is true, the end position will be moved instead of the start position.
+    pub fn move_cursor(&mut self, f: impl FnOnce(&mut Pos), shift: bool) {
         if shift {
             self.with_end(f);
         } else {
@@ -127,38 +206,36 @@ impl Cursor {
         }
     }
 
-    fn delete_selection(&mut self, text: &mut String) -> [i32; 2] {
+    /// Delete the currently selected text and update the cursor position.
+    pub fn delete_selection<T: Text + ?Sized>(&mut self, text: &mut impl TextEditable<T>) {
         let first = self.first();
         let last = self.last();
-        let dr = first.row as i32 - last.row as i32;
-        let dc = if dr != 0 {
-            -(last.col as i32)
-        } else {
-            first.col as i32 - last.col as i32
-        };
-        text.replace_range(first.idx(text)..last.idx(text), "");
+        text.delete_range(first.idx(text.as_ref())..last.idx(text.as_ref()));
         if let Some(end) = self.end.take() {
             if self.start > end {
                 self.start = end;
             }
         }
-        [dc, dr]
     }
 
-    pub fn handle_input(
+    /// Handle moving the cursor with the given key.
+    pub fn handle_input<T: Text + ?Sized>(
         &mut self,
         data: &dioxus_html::KeyboardData,
-        text: &mut String,
-        max_width: usize,
+        text: &mut impl TextEditable<T>,
+        max_text_length: usize,
     ) {
         use Code::*;
         match data.code() {
             ArrowUp => {
-                self.move_cursor(|c| c.up(text), data.modifiers().contains(Modifiers::SHIFT));
+                self.move_cursor(
+                    |c| c.up(text.as_ref()),
+                    data.modifiers().contains(Modifiers::SHIFT),
+                );
             }
             ArrowDown => {
                 self.move_cursor(
-                    |c| c.down(text),
+                    |c| c.down(text.as_ref()),
                     data.modifiers().contains(Modifiers::SHIFT),
                 );
             }
@@ -167,22 +244,22 @@ impl Cursor {
                     self.move_cursor(
                         |c| {
                             let mut change = 1;
-                            let idx = c.idx(text);
-                            let length = text.len();
+                            let idx = c.idx(text.as_ref());
+                            let length = text.as_ref().length();
                             while idx + change < length {
-                                let chr = text.chars().nth(idx + change).unwrap();
+                                let chr = text.as_ref().character(idx + change).unwrap();
                                 if chr.is_whitespace() {
                                     break;
                                 }
                                 change += 1;
                             }
-                            c.move_col(change as i32, text);
+                            c.move_col(change as i32, text.as_ref());
                         },
                         data.modifiers().contains(Modifiers::SHIFT),
                     );
                 } else {
                     self.move_cursor(
-                        |c| c.right(text),
+                        |c| c.right(text.as_ref()),
                         data.modifiers().contains(Modifiers::SHIFT),
                     );
                 }
@@ -192,28 +269,28 @@ impl Cursor {
                     self.move_cursor(
                         |c| {
                             let mut change = -1;
-                            let idx = c.idx(text) as i32;
+                            let idx = c.idx(text.as_ref()) as i32;
                             while idx + change > 0 {
-                                let chr = text.chars().nth((idx + change) as usize).unwrap();
+                                let chr = text.as_ref().character((idx + change) as usize).unwrap();
                                 if chr == ' ' {
                                     break;
                                 }
                                 change -= 1;
                             }
-                            c.move_col(change, text);
+                            c.move_col(change, text.as_ref());
                         },
                         data.modifiers().contains(Modifiers::SHIFT),
                     );
                 } else {
                     self.move_cursor(
-                        |c| c.left(text),
+                        |c| c.left(text.as_ref()),
                         data.modifiers().contains(Modifiers::SHIFT),
                     );
                 }
             }
             End => {
                 self.move_cursor(
-                    |c| c.col = c.len_line(text),
+                    |c| c.col = c.len_line(text.as_ref()),
                     data.modifiers().contains(Modifiers::SHIFT),
                 );
             }
@@ -221,64 +298,70 @@ impl Cursor {
                 self.move_cursor(|c| c.col = 0, data.modifiers().contains(Modifiers::SHIFT));
             }
             Backspace => {
-                self.start.realize_col(text);
-                let mut start_idx = self.start.idx(text);
+                self.start.realize_col(text.as_ref());
+                let mut start_idx = self.start.idx(text.as_ref());
                 if self.end.is_some() {
                     self.delete_selection(text);
                 } else if start_idx > 0 {
-                    self.start.left(text);
-                    text.replace_range(start_idx - 1..start_idx, "");
+                    self.start.left(text.as_ref());
+                    text.delete_range(start_idx - 1..start_idx);
                     if data.modifiers().contains(Modifiers::CONTROL) {
-                        start_idx = self.start.idx(text);
+                        start_idx = self.start.idx(text.as_ref());
                         while start_idx > 0
                             && text
-                                .chars()
-                                .nth(start_idx - 1)
+                                .as_ref()
+                                .character(start_idx - 1)
                                 .filter(|c| *c != ' ')
                                 .is_some()
                         {
-                            self.start.left(text);
-                            text.replace_range(start_idx - 1..start_idx, "");
-                            start_idx = self.start.idx(text);
+                            self.start.left(text.as_ref());
+                            text.delete_range(start_idx - 1..start_idx);
+                            start_idx = self.start.idx(text.as_ref());
                         }
                     }
                 }
             }
             Enter => {
-                if text.len() + 1 - self.selection_len(text) <= max_width {
-                    text.insert(self.start.idx(text), '\n');
+                if text.as_ref().length() + 1 - self.selection_len(text.as_ref()) <= max_text_length
+                {
+                    text.insert_character(self.start.idx(text.as_ref()), '\n');
                     self.start.col = 0;
-                    self.start.down(text);
+                    self.start.down(text.as_ref());
                 }
             }
             Tab => {
-                if text.len() + 1 - self.selection_len(text) <= max_width {
-                    self.start.realize_col(text);
+                if text.as_ref().length() + 1 - self.selection_len(text.as_ref()) <= max_text_length
+                {
+                    self.start.realize_col(text.as_ref());
                     self.delete_selection(text);
-                    text.insert(self.start.idx(text), '\t');
-                    self.start.right(text);
+                    text.insert_character(self.start.idx(text.as_ref()), '\t');
+                    self.start.right(text.as_ref());
                 }
             }
             _ => {
-                self.start.realize_col(text);
+                self.start.realize_col(text.as_ref());
                 if let Key::Character(character) = data.key() {
-                    if text.len() + 1 - self.selection_len(text) <= max_width {
+                    if text.as_ref().length() + 1 - self.selection_len(text.as_ref())
+                        <= max_text_length
+                    {
                         self.delete_selection(text);
                         let character = character.chars().next().unwrap();
-                        text.insert(self.start.idx(text), character);
-                        self.start.right(text);
+                        text.insert_character(self.start.idx(text.as_ref()), character);
+                        self.start.right(text.as_ref());
                     }
                 }
             }
         }
     }
 
+    /// Modify the end selection position
     pub fn with_end(&mut self, f: impl FnOnce(&mut Pos)) {
         let mut new = self.end.take().unwrap_or_else(|| self.start.clone());
         f(&mut new);
         self.end.replace(new);
     }
 
+    /// Returns first position of the selection (this could be the start or the end depending on the position)
     pub fn first(&self) -> &Pos {
         if let Some(e) = &self.end {
             e.min(&self.start)
@@ -287,6 +370,7 @@ impl Cursor {
         }
     }
 
+    /// Returns last position of the selection (this could be the start or the end depending on the position)
     pub fn last(&self) -> &Pos {
         if let Some(e) = &self.end {
             e.max(&self.start)
@@ -295,7 +379,8 @@ impl Cursor {
         }
     }
 
-    pub fn selection_len(&self, text: &str) -> usize {
+    /// Returns the length of the selection
+    pub fn selection_len(&self, text: &(impl Text + ?Sized)) -> usize {
         self.last().idx(text) - self.first().idx(text)
     }
 }

+ 15 - 2
packages/tui/src/layout.rs

@@ -1,6 +1,8 @@
 use std::sync::{Arc, Mutex};
 
-use dioxus_native_core::layout_attributes::apply_layout_attributes;
+use dioxus_native_core::layout_attributes::{
+    apply_layout_attributes_cfg, BorderWidths, LayoutConfigeration,
+};
 use dioxus_native_core::node::OwnedAttributeView;
 use dioxus_native_core::node_ref::{AttributeMask, NodeMask, NodeView};
 use dioxus_native_core::{Pass, SendAnyMap};
@@ -101,7 +103,18 @@ impl Pass for TaffyLayout {
                         .binary_search(&attribute.name.as_ref())
                         .is_ok());
                     if let Some(text) = value.as_text() {
-                        apply_layout_attributes(&attribute.name, text, &mut style);
+                        apply_layout_attributes_cfg(
+                            &attribute.name,
+                            text,
+                            &mut style,
+                            &LayoutConfigeration {
+                                border_widths: BorderWidths {
+                                    thin: 1.0,
+                                    medium: 1.0,
+                                    thick: 1.0,
+                                },
+                            },
+                        );
                     }
                 }
             }

+ 4 - 4
packages/tui/src/widgets/number.rs

@@ -40,8 +40,8 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
     let dragging = use_state(cx, || false);
 
     let text = text_ref.read().clone();
-    let start_highlight = cursor.read().first().idx(&text);
-    let end_highlight = cursor.read().last().idx(&text);
+    let start_highlight = cursor.read().first().idx(&*text);
+    let end_highlight = cursor.read().last().idx(&*text);
     let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
     let (text_highlighted, text_after_second_cursor) =
         text_after_first_cursor.split_at(end_highlight - start_highlight);
@@ -113,7 +113,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
                 };
                 if is_text{
                     let mut text = text_ref.write();
-                    cursor.write().handle_input(&k, &mut text, max_len);
+                    cursor.write().handle_input(&k, &mut *text, max_len);
                     update(text.clone());
 
                     let node = tui_query.get(get_root_id(cx).unwrap());
@@ -165,7 +165,7 @@ pub(crate) fn NumbericInput<'a>(cx: Scope<'a, NumbericInputProps>) -> Element<'a
                 }
                 new.row = 0;
 
-                new.realize_col(&text_ref.read());
+                new.realize_col(text_ref.read().as_str());
                 cursor.set(Cursor::from_start(new));
                 dragging.set(true);
                 let node = tui_query_clone.get(get_root_id(cx).unwrap());

+ 4 - 4
packages/tui/src/widgets/password.rs

@@ -40,8 +40,8 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
     let dragging = use_state(cx, || false);
 
     let text = text_ref.read().clone();
-    let start_highlight = cursor.read().first().idx(&text);
-    let end_highlight = cursor.read().last().idx(&text);
+    let start_highlight = cursor.read().first().idx(&*text);
+    let end_highlight = cursor.read().last().idx(&*text);
     let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
     let (text_highlighted, text_after_second_cursor) =
         text_after_first_cursor.split_at(end_highlight - start_highlight);
@@ -88,7 +88,7 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
             return;
         }
         let mut text = text_ref.write();
-        cursor.write().handle_input(&k, &mut text, max_len);
+        cursor.write().handle_input(&k, &mut *text, max_len);
         if let Some(input_handler) = &cx.props.raw_oninput {
             input_handler.call(FormData {
                 value: text.clone(),
@@ -147,7 +147,7 @@ pub(crate) fn Password<'a>(cx: Scope<'a, PasswordProps>) -> Element<'a> {
                 // textboxs are only one line tall
                 new.row = 0;
 
-                new.realize_col(&text_ref.read());
+                new.realize_col(text_ref.read().as_str());
                 cursor.set(Cursor::from_start(new));
                 dragging.set(true);
                 let node = tui_query_clone.get(get_root_id(cx).unwrap());

+ 4 - 4
packages/tui/src/widgets/textbox.rs

@@ -40,8 +40,8 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
     let dragging = use_state(cx, || false);
 
     let text = text_ref.read().clone();
-    let start_highlight = cursor.read().first().idx(&text);
-    let end_highlight = cursor.read().last().idx(&text);
+    let start_highlight = cursor.read().first().idx(&*text);
+    let end_highlight = cursor.read().last().idx(&*text);
     let (text_before_first_cursor, text_after_first_cursor) = text.split_at(start_highlight);
     let (text_highlighted, text_after_second_cursor) =
         text_after_first_cursor.split_at(end_highlight - start_highlight);
@@ -90,7 +90,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
                     return;
                 }
                 let mut text = text_ref.write();
-                cursor.write().handle_input(&k, &mut text, max_len);
+                cursor.write().handle_input(&k, &mut *text, max_len);
                 if let Some(input_handler) = &cx.props.raw_oninput{
                     input_handler.call(FormData{
                         value: text.clone(),
@@ -138,7 +138,7 @@ pub(crate) fn TextBox<'a>(cx: Scope<'a, TextBoxProps>) -> Element<'a> {
                 // textboxs are only one line tall
                 new.row = 0;
 
-                new.realize_col(&text_ref.read());
+                new.realize_col(text_ref.read().as_str());
                 cursor.set(Cursor::from_start(new));
                 dragging.set(true);
                 let node = tui_query_clone.get(get_root_id(cx).unwrap());