diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..6fddca0d6 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/examples/all_css.rs b/examples/all_css.rs index 467748198..5e646a284 100644 --- a/examples/all_css.rs +++ b/examples/all_css.rs @@ -406,6 +406,8 @@ fn app(cx: Scope) -> Element { wrap_through: "a", writing_mode: "a", z_index: "a", + + "This example isn't quite useful yet" } }) } diff --git a/examples/borrowed.rs b/examples/borrowed.rs index 7de2ff80d..95641bbbc 100644 --- a/examples/borrowed.rs +++ b/examples/borrowed.rs @@ -29,9 +29,7 @@ fn app(cx: Scope) -> Element { cx.render(rsx! { div { - Child1 { - text: first - } + Child1 { text: first } } }) } @@ -59,9 +57,7 @@ struct C2Props<'a> { fn Child2<'a>(cx: Scope<'a, C2Props<'a>>) -> Element { cx.render(rsx! { - Child3 { - text: cx.props.text - } + Child3 { text: cx.props.text } }) } diff --git a/examples/calculator.rs b/examples/calculator.rs index 4f5d7dedd..93e298010 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -25,6 +25,7 @@ fn app(cx: Scope) -> Element { if val.get() == "0" { val.set(String::new()); } + val.make_mut().push_str(num.to_string().as_str()); }; @@ -99,12 +100,8 @@ fn app(cx: Scope) -> Element { } } div { class: "digit-keys", - button { class: "calculator-key key-0", onclick: move |_| input_digit(0), - "0" - } - button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), - "●" - } + button { class: "calculator-key key-0", onclick: move |_| input_digit(0), "0" } + button { class: "calculator-key key-dot", onclick: move |_| val.make_mut().push('.'), "●" } (1..10).map(|k| rsx!{ button { class: "calculator-key {k}", @@ -116,22 +113,13 @@ fn app(cx: Scope) -> Element { } } div { class: "operator-keys", - button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"), - "÷" - } - button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"), - "×" - } - button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"), - "−" - } - button { class: "calculator-key key-add", onclick: move |_| input_operator("+"), - "+" - } - button { class: "calculator-key key-equals", - onclick: move |_| { - val.set(format!("{}", calc_val(val.as_str()))); - }, + button { class: "calculator-key key-divide", onclick: move |_| input_operator("/"), "÷" } + button { class: "calculator-key key-multiply", onclick: move |_| input_operator("*"), "×" } + button { class: "calculator-key key-subtract", onclick: move |_| input_operator("-"), "−" } + button { class: "calculator-key key-add", onclick: move |_| input_operator("+"), "+" } + button { + class: "calculator-key key-equals", + onclick: move |_| val.set(format!("{}", calc_val(val.as_str()))), "=" } } diff --git a/examples/callback.rs b/examples/callback.rs index e7930ef34..bf2e3e3a0 100644 --- a/examples/callback.rs +++ b/examples/callback.rs @@ -5,7 +5,7 @@ fn main() { } fn app(cx: Scope) -> Element { - let login = use_callback!(cx, || move |evt: MouseEvent| async move { + let login = use_callback!(cx, move |_| async move { let res = reqwest::get("https://dog.ceo/api/breeds/list/all") .await .unwrap() @@ -13,7 +13,7 @@ fn app(cx: Scope) -> Element { .await .unwrap(); - println!("{}, ", res); + println!("{:#?}, ", res); }); cx.render(rsx! { diff --git a/examples/custom_element.rs b/examples/custom_element.rs index 92c61ef86..19bfa0fd9 100644 --- a/examples/custom_element.rs +++ b/examples/custom_element.rs @@ -10,7 +10,7 @@ fn main() { let mut dom = VirtualDom::new(app); let _ = dom.rebuild(); - let output = dioxus_ssr::render_vdom(&dom); + let output = dioxus_ssr::render(&dom); println!("{}", output); } diff --git a/examples/nested_listeners.rs b/examples/nested_listeners.rs index 163a827a2..3938959a7 100644 --- a/examples/nested_listeners.rs +++ b/examples/nested_listeners.rs @@ -22,7 +22,7 @@ fn app(cx: Scope) -> Element { button { onclick: move |evt| { println!("clicked! bottom no bubbling"); - evt.cancel_bubble(); + evt.stop_propogation(); }, "Dont propogate" } diff --git a/examples/rsx_compile_fail.rs b/examples/rsx_compile_fail.rs index 75b740df9..375219609 100644 --- a/examples/rsx_compile_fail.rs +++ b/examples/rsx_compile_fail.rs @@ -2,14 +2,14 @@ //! It also proves that lifetimes work properly, especially when used with use_ref use dioxus::prelude::*; -use dioxus_ssr::config::SsrConfig; fn main() { let mut vdom = VirtualDom::new(example); - vdom.rebuild(); + _ = vdom.rebuild(); - let out = dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().newline(true).indent(true)); - println!("{}", out); + let mut renderer = dioxus_ssr::Renderer::new(); + renderer.pretty = true; + renderer.render(&vdom); } fn example(cx: Scope) -> Element { diff --git a/examples/simple_todo.rs b/examples/simple_todo.rs deleted file mode 100644 index 454e4b508..000000000 --- a/examples/simple_todo.rs +++ /dev/null @@ -1,32 +0,0 @@ -use dioxus::prelude::*; - -fn main() { - dioxus_desktop::launch(app); -} - -fn app(cx: Scope) -> Element { - let mut idx = use_state(cx, || 0); - let onhover = |h| println!("go!"); - - cx.render(rsx! { - div { - button { onclick: move |_| idx += 1, "+" } - button { onclick: move |_| idx -= 1, "-" } - ul { - (0..**idx).map(|i| rsx! { - Child { i: i, onhover: onhover } - }) - } - } - }) -} - -#[inline_props] -fn Child<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element { - cx.render(rsx! { - li { - onmouseover: move |e| onhover.call(e), - "{i}" - } - }) -} diff --git a/examples/ssr.rs b/examples/ssr.rs index e1e5a3ad8..0bdebc419 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -5,7 +5,7 @@ use std::fmt::Write; use dioxus::prelude::*; -use dioxus_ssr::config::SsrConfig; +use dioxus_ssr::config::Config; fn main() { // We can render VirtualDoms @@ -26,7 +26,7 @@ fn main() { // We can configure the SSR rendering to add ids for rehydration println!( "{}", - dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().pre_render(true)) + dioxus_ssr::render_vdom_cfg(&vdom, Config::default().pre_render(true)) ); // We can even render as a writer diff --git a/examples/suspense2.rs b/examples/suspense2.rs deleted file mode 100644 index b63867f0a..000000000 --- a/examples/suspense2.rs +++ /dev/null @@ -1,16 +0,0 @@ -use dioxus::prelude::*; -use dioxus_desktop::{Config, LogicalSize, WindowBuilder}; - -fn main() { - dioxus_desktop::launch(|cx| cx.render(rsx! { async_app {} })); -} - -async fn async_app(cx: Scope<'_>) -> Element { - tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; - - cx.render(rsx! { - div { - "hi!" - } - }) -} diff --git a/examples/todomvc.rs b/examples/todomvc.rs index f74f56063..b5277133e 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -22,10 +22,10 @@ pub struct TodoItem { } pub fn app(cx: Scope<()>) -> Element { - let todos = use_state(&cx, im_rc::HashMap::::default); - let filter = use_state(&cx, || FilterState::All); - let draft = use_state(&cx, || "".to_string()); - let todo_id = use_state(&cx, || 0); + let todos = use_state(cx, im_rc::HashMap::::default); + let filter = use_state(cx, || FilterState::All); + let draft = use_state(cx, || "".to_string()); + let todo_id = use_state(cx, || 0); // Filter the todos based on the filter state let mut filtered_todos = todos @@ -58,7 +58,6 @@ pub fn app(cx: Scope<()>) -> Element { value: "{draft}", autofocus: "true", oninput: move |evt| { - println!("calling oninput"); draft.set(evt.value.clone()); }, onkeydown: move |evt| { @@ -117,15 +116,13 @@ pub struct TodoEntryProps<'a> { } pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element { - let is_editing = use_state(&cx, || false); + let is_editing = use_state(cx, || false); let todos = cx.props.todos.get(); let todo = &todos[&cx.props.id]; let completed = if todo.checked { "completed" } else { "" }; let editing = if **is_editing { "editing" } else { "" }; - println!("rendering todo entry"); - cx.render(rsx!{ li { class: "{completed} {editing}", diff --git a/examples/window_event.rs b/examples/window_event.rs index 8805bcfdd..cd752fa17 100644 --- a/examples/window_event.rs +++ b/examples/window_event.rs @@ -35,13 +35,13 @@ fn app(cx: Scope) -> Element { nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" } button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| window.set_minimized(true), "Minimize" } button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| { window.set_fullscreen(!**fullscreen); @@ -52,7 +52,7 @@ fn app(cx: Scope) -> Element { } button { class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| window.close(), "Close" } @@ -66,7 +66,7 @@ fn app(cx: Scope) -> Element { div { button { class: "inline-flex items-center text-white bg-green-500 border-0 py-1 px-3 hover:bg-green-700 rounded", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| { window.set_always_on_top(!always_on_top); always_on_top.set(!always_on_top); @@ -77,7 +77,7 @@ fn app(cx: Scope) -> Element { div { button { class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| { window.set_decorations(!decorations); decorations.set(!decorations); @@ -88,7 +88,7 @@ fn app(cx: Scope) -> Element { div { button { class: "inline-flex items-center text-white bg-blue-500 border-0 py-1 px-3 hover:bg-green-700 rounded", - onmousedown: |evt| evt.cancel_bubble(), + onmousedown: |evt| evt.stop_propogation(), onclick: move |_| window.set_title("Dioxus Application"), "Change Title" } diff --git a/notes/README/ZH_CN.md b/notes/README/ZH_CN.md index 4da075c7d..07bcdee55 100644 --- a/notes/README/ZH_CN.md +++ b/notes/README/ZH_CN.md @@ -1,9 +1,6 @@ -
-

🌗🚀 Dioxus

-

- Frontend that scales. -

-
+

+ +

@@ -26,9 +23,7 @@ CI status -
-
Awesome Page @@ -39,29 +34,23 @@
-

- 官网 + 官方网站 | - 手册 + 代码示例 + | + 开发指南 | - 示例 -

-
- -
-

English | - 中文 + PT-BR

-
-Dioxus 是一个可移植、高性能的框架,用于在 Rust 中构建跨平台的用户界面。 +Dioxus 是一个可移植的、高性能的、符合人体工程学的框架,使用 Rust 语言构建跨平台的用户界面。 ```rust fn app(cx: Scope) -> Element { @@ -75,103 +64,111 @@ fn app(cx: Scope) -> Element { } ``` -Dioxus 可用于制作 网页程序、桌面应用、静态站点、移动端应用。 +Dioxus 可用于生成 网页前端、桌面应用、静态网站、移动端应用、TUI程序、等多类平台应用。 -Dioxus 为不同的平台都提供了很好的开发文档。 +如果你能够熟悉使用 React 框架,那 Dioxus 对你来说将非常简单。 -如果你会使用 React ,那 Dioxus 对你来说会很简单。 +## 独特的特性: +- 桌面程序完全基于本地环境运行(并非 Electron 的封装) +- 符合人体工程学的设计以及拥有强大的状态管理 +- 全面的内联文档 - 包含所有 HTML 元素、监听器 和 事件 指南。 +- 极快的运行效率和极高的内存效率 +- 智能项目热更新和高效的项目迭代 +- 一流的异步支持🔥 +- 更多内容请查看 [版本发布信息](https://dioxuslabs.com/blog/introducing-dioxus/). -### 项目特点: -- 对桌面应用的原生支持。 -- 强大的状态管理工具。 -- 支持所有 HTML 标签,监听器和事件。 -- 超高的内存使用率,稳定的组件分配器。 -- 多通道异步调度器,超强的异步支持。 -- 更多信息请查阅: [版本发布文档](https://dioxuslabs.com/blog/introducing-dioxus/). - -### 示例 - -本项目中的所有例子都是 `桌面应用` 程序,请使用 `cargo run --example XYZ` 运行这些例子。 - -``` -cargo run --example EXAMPLE -``` - -## 进入学习 - - - - - - - - - +## 已支持的平台 +
+
教程网页端桌面端SSR移动端状态管理
-
+ 网站项目 + + + + + + 桌面应用 + + + + + + 移动端应用 + + + + + + Liveview + + + + + + 终端程序 + + + + + + + +## Why Dioxus? + +目前有非常多的应用开发选择,为什么偏偏要选择 Dioxus 呢? + +首先,Dioxus将开发者的经验放在首位。这反映在 Dioxus 特有的各种功能上。 + +- 自动格式化 RSX 格式代码,并拥有 VSCode 插件作为支持。 +- 热加载基于 RSX 代码解析器,同时支持桌面程序和网页程序。 +- 强调文档的重要性,我们对所有 HTML 元素都提供文档支持。 + +Dioxus 也是一个可扩展化的平台。 + +- 通过实现一个非常简单的优化堆栈机,轻松构建新的渲染器。 +- 构建并分享开发者自定义的组件代码。 + +Dioxus 那么优秀,但什么时候它不适合我呢? +- 它还没有完全成熟。api仍在变化,可能会出现故障(尽管我们试图避免) +- 您需要运行在 no-std 的环境之中。 +- 你不喜欢使用 React-like 的方式构建 UI 项目。 -## Dioxus 项目 - -| 文件浏览器 (桌面应用) | WiFi 扫描器 (桌面应用) | Todo管理 (所有平台) | 商城系统 (SSR/liveview) | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [![File Explorer](https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) | [![Wifi Scanner Demo](https://github.com/DioxusLabs/example-projects/raw/master/wifi-scanner/demo_small.png)](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) | [![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) | [![E-commerce Example](https://github.com/DioxusLabs/example-projects/raw/master/ecommerce-site/demo.png)](https://github.com/DioxusLabs/example-projects/blob/master/ecommerce-site) | +## 贡献代码 +- 在我们的 [问题追踪](https://github.com/dioxuslabs/dioxus/issues) 中汇报你遇到的问题。 +- 加入我们的 Discord 与我们交流。 -查看 [awesome-dioxus](https://github.com/DioxusLabs/awesome-dioxus) 查看更多有趣(~~NiuBi~~)的项目! + + + -## 为什么使用 Dioxus 和 Rust ? +## 开源协议 -TypeScript 是一个不错的 JavaScript 拓展集,但它仍然算是 JavaScript。 +本项目使用 [MIT license]. -TS 代码运行效率不高,而且有大量的配置项。 +[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT -相比之下,Dioxus 使用 Rust 编写将大大的提高效能。 - -使用 Rust 开发,我们能获得: - -- 静态类型支持。 -- 变量默认不变性。 -- 简单直观的模块系统。 -- 内部集成的文档系统。 -- 先进的模式匹配系统。 -- 简洁、高效、强大的迭代器。 -- 内置的 单元测试 / 集成测试。 -- 优秀的异常处理系统。 -- 强大且健全的标准库。 -- 灵活的 `宏` 系统。 -- 使用 `crates.io` 管理包。 - -Dioxus 能为开发者提供的: - -- 安全使用数据结构。 -- 安全的错误处理结果。 -- 拥有原生移动端的性能。 -- 直接访问系统的IO层。 - -Dioxus 使 Rust 应用程序的编写速度和 React 应用程序一样快,但提供了更多的健壮性,让团队能在更短的时间内做出强大功能。 - -### 不建议使用 Dioxus 的情况? - -您不该在这些情况下使用 Dioxus : - -- 您不喜欢类似 React 的开发风格。 -- 您需要一个 `no-std` 的渲染器。 -- 您希望应用运行在 `不支持 Wasm 或 asm.js` 的浏览器。 -- 您需要一个 `Send + Sync` UI 解决方案(目前不支持)。 - -### 项目生态 - -想要加入我们一起为 Dioxus 生态努力吗?有很多项目都能在您的帮助下获得改变: - -- [TUI 渲染器](https://github.com/dioxusLabs/rink) -- [CLI 开发工具](https://github.com/dioxusLabs/cli) -- [官网及文档](https://github.com/dioxusLabs/docsite) -- 动态网站 及 Web 服务器 -- 资源系统 - -## 协议 - -这个项目使用 [MIT 协议]. - -[MIT 协议]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE +除非您另有明确声明,否则有意提交的任何贡献将被授权为 MIT 协议,没有任何附加条款或条件。 diff --git a/packages/core/README.md b/packages/core/README.md index cf3467030..d3ad1946f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -41,7 +41,7 @@ fn app(cx: Scope) -> Element { Then, we'll want to create a new VirtualDom using this app as the root component. -```rust, ingore +```rust, ignore let mut dom = VirtualDom::new(app); ``` @@ -65,9 +65,9 @@ dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16))); If an event occurs from outside the virtualdom while waiting for work, then we can cancel the wait using a `select!` block and inject the event. -```rust +```rust, ignore loop { - tokio::select! { + select! { evt = real_dom.event() => dom.handle_event("click", evt.data, evt.element, evt.bubbles), _ = dom.wait_for_work() => {} } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 024ad5375..98960fca7 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,7 +1,4 @@ -use crate::{ - nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode, - ScopeId, -}; +use crate::{nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode, ScopeId}; use bumpalo::boxed::Box as BumpBox; /// An Element's unique identifier. @@ -37,21 +34,20 @@ impl ElementRef { impl VirtualDom { pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId { - let entry = self.elements.vacant_entry(); - let id = entry.key(); - entry.insert(ElementRef { - template: template as *const _ as *mut _, - path: ElementPath::Deep(path), - }); - ElementId(id) + self.next(template, ElementPath::Deep(path)) } pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId { + self.next(template, ElementPath::Root(path)) + } + + fn next(&mut self, template: &VNode, path: ElementPath) -> ElementId { let entry = self.elements.vacant_entry(); let id = entry.key(); + entry.insert(ElementRef { template: template as *const _ as *mut _, - path: ElementPath::Root(path), + path, }); ElementId(id) } @@ -64,9 +60,9 @@ impl VirtualDom { pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option { if el.0 == 0 { panic!( - "Invalid element set to 0 - {:#?}", + "Cannot reclaim the root element - {:#?}", std::backtrace::Backtrace::force_capture() - ) + ); } self.elements.try_remove(el.0) @@ -79,17 +75,15 @@ impl VirtualDom { // Drop a scope and all its children pub(crate) fn drop_scope(&mut self, id: ScopeId) { - let scope = self.scopes.get(id.0).unwrap(); - - if let Some(root) = scope.as_ref().try_root_node() { - let root = unsafe { root.extend_lifetime_ref() }; - if let RenderReturn::Sync(Ok(node)) = root { + if let Some(root) = self.scopes[id.0].as_ref().try_root_node() { + if let RenderReturn::Sync(Ok(node)) = unsafe { root.extend_lifetime_ref() } { self.drop_scope_inner(node) } } - let scope = self.scopes.get_mut(id.0).unwrap(); - scope.props.take(); + self.scopes[id.0].props.take(); + + let scope = &mut self.scopes[id.0]; // Drop all the hooks once the children are dropped // this means we'll drop hooks bottom-up @@ -99,24 +93,55 @@ impl VirtualDom { } fn drop_scope_inner(&mut self, node: &VNode) { - for attr in node.dynamic_attrs { - if let AttributeValue::Listener(l) = &attr.value { - l.borrow_mut().take(); + node.clear_listeners(); + node.dynamic_nodes.iter().for_each(|node| match node { + DynamicNode::Component(c) => self.drop_scope(c.scope.get().unwrap()), + DynamicNode::Fragment(nodes) => { + nodes.iter().for_each(|node| self.drop_scope_inner(node)) } - } + DynamicNode::Placeholder(t) => { + self.try_reclaim(t.get()); + } + DynamicNode::Text(t) => { + self.try_reclaim(t.id.get()); + } + }); - for (idx, _) in node.template.roots.iter().enumerate() { - match node.dynamic_root(idx) { - Some(DynamicNode::Component(c)) => self.drop_scope(c.scope.get().unwrap()), - Some(DynamicNode::Fragment(nodes)) => { - for node in *nodes { - self.drop_scope_inner(node); - } - } - _ => {} + for root in node.root_ids { + let id = root.get(); + if id.0 != 0 { + self.try_reclaim(id); } } } + + /// Descend through the tree, removing any borrowed props and listeners + pub(crate) fn ensure_drop_safety(&self, scope: ScopeId) { + let node = unsafe { self.scopes[scope.0].previous_frame().try_load_node() }; + + // And now we want to make sure the previous frame has dropped anything that borrows self + if let Some(RenderReturn::Sync(Ok(node))) = node { + self.ensure_drop_safety_inner(node); + } + } + + fn ensure_drop_safety_inner(&self, node: &VNode) { + node.clear_listeners(); + + node.dynamic_nodes.iter().for_each(|child| match child { + // Only descend if the props are borrowed + DynamicNode::Component(c) if !c.static_props => { + self.ensure_drop_safety(c.scope.get().unwrap()); + c.props.set(None); + } + + DynamicNode::Fragment(f) => f + .iter() + .for_each(|node| self.ensure_drop_safety_inner(node)), + + _ => {} + }); + } } impl ElementPath { diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index c5e2c6b1c..35c2db411 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,4 +1,5 @@ use std::cell::Cell; +use std::rc::Rc; use crate::innerlude::{VComponent, VText}; use crate::mutations::Mutation; @@ -42,7 +43,7 @@ impl<'b> VirtualDom { for (root_idx, root) in template.template.roots.iter().enumerate() { // We might need to generate an ID for the root node on_stack += match root { - TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => { + TemplateNode::DynamicText { id } | TemplateNode::Dynamic { id } => { match &template.dynamic_nodes[*id] { // a dynamic text node doesn't replace a template node, instead we create it on the fly DynamicNode::Text(VText { id: slot, value }) => { @@ -72,7 +73,7 @@ impl<'b> VirtualDom { } } - TemplateNode::Element { .. } | TemplateNode::Text(_) => { + TemplateNode::Element { .. } | TemplateNode::Text { .. } => { let this_id = self.next_root(template, root_idx); template.root_ids[root_idx].set(this_id); @@ -213,7 +214,7 @@ impl<'b> VirtualDom { if template.template.roots.iter().all(|root| { matches!( root, - TemplateNode::Dynamic(_) | TemplateNode::DynamicText(_) + TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. } ) }) { return; @@ -302,7 +303,8 @@ impl<'b> VirtualDom { let unbounded_props = unsafe { std::mem::transmute(props) }; - let scope = self.new_scope(unbounded_props).id; + let scope = self.new_scope(unbounded_props, component.name); + let scope = scope.id; component.scope.set(Some(scope)); let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() }; @@ -336,8 +338,8 @@ impl<'b> VirtualDom { } // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense - let boundary = match self.scopes[scope.0].has_context::() { - Some(boundary) => unsafe { &*(boundary as *const SuspenseContext) }, + let boundary = match self.scopes[scope.0].has_context::>() { + Some(boundary) => boundary, _ => return created, }; diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 544b4bb16..8abf8b4ee 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -25,40 +25,35 @@ impl<'b> VirtualDom { .previous_frame() .try_load_node() .expect("Call rebuild before diffing"); + let new = scope_state .current_frame() .try_load_node() .expect("Call rebuild before diffing"); - self.diff_maybe_node(old, new); + + use RenderReturn::{Async, Sync}; + + match (old, new) { + (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r), + + // Err cases + (Sync(Ok(l)), Sync(Err(e))) => self.diff_ok_to_err(l, e), + (Sync(Err(e)), Sync(Ok(r))) => self.diff_err_to_ok(e, r), + (Sync(Err(_eo)), Sync(Err(_en))) => { /* nothing */ } + + // Async + (Sync(Ok(_l)), Async(_)) => todo!(), + (Sync(Err(_e)), Async(_)) => todo!(), + (Async(_), Sync(Ok(_r))) => todo!(), + (Async(_), Sync(Err(_e))) => { /* nothing */ } + (Async(_), Async(_)) => { /* nothing */ } + }; } self.scope_stack.pop(); } - fn diff_maybe_node(&mut self, old: &'b RenderReturn<'b>, new: &'b RenderReturn<'b>) { - use RenderReturn::{Async, Sync}; - match (old, new) { - (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r), - - // Err cases - (Sync(Ok(l)), Sync(Err(e))) => self.diff_ok_to_err(l, e), - (Sync(Err(e)), Sync(Ok(r))) => self.diff_err_to_ok(e, r), - (Sync(Err(_eo)), Sync(Err(_en))) => { /* nothing */ } - - // Async - (Sync(Ok(_l)), Async(_)) => todo!(), - (Sync(Err(_e)), Async(_)) => todo!(), - (Async(_), Sync(Ok(_r))) => todo!(), - (Async(_), Sync(Err(_e))) => { /* nothing */ } - (Async(_), Async(_)) => { /* nothing */ } - } - } - - fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) { - todo!("Not yet handling error rollover") - } - fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) { - todo!("Not yet handling error rollover") - } + fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) {} + fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {} fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) { if !std::ptr::eq(left_template.template.name, right_template.template.name) @@ -144,16 +139,18 @@ impl<'b> VirtualDom { } fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell) { + // Remove the old nodes, except for one + self.remove_nodes(&l[1..]); + + // Now create the new one + let first = self.replace_inner(&l[0]); + // Create the placeholder first, ensuring we get a dedicated ID for the placeholder let placeholder = self.next_element(&l[0], &[]); r.set(placeholder); self.mutations .push(Mutation::CreatePlaceholder { id: placeholder }); - // Remove the old nodes, except for onea - let first = self.replace_inner(&l[0]); - self.remove_nodes(&l[1..]); - self.mutations .push(Mutation::ReplaceWith { id: first, m: 1 }); @@ -746,7 +743,8 @@ impl<'b> VirtualDom { /// Remove these nodes from the dom /// Wont generate mutations for the inner nodes fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) { - nodes.iter().for_each(|node| self.remove_node(node)); + // note that we iterate in reverse to unlink lists of nodes in their rough index order + nodes.iter().rev().for_each(|node| self.remove_node(node)); } fn remove_node(&mut self, node: &'b VNode<'b>) { @@ -911,7 +909,7 @@ fn matching_components<'a>( .zip(right.template.roots.iter()) .map(|(l, r)| { let (l, r) = match (l, r) { - (TemplateNode::Dynamic(l), TemplateNode::Dynamic(r)) => (l, r), + (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r), _ => return None, }; diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index d5dfd3c50..776d3e37a 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -29,8 +29,8 @@ use crate::innerlude::*; pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { let children = cx.props.0.as_ref().map_err(|e| anyhow::anyhow!("{}", e))?; Ok(VNode { - key: children.key.clone(), - parent: children.parent.clone(), + key: children.key, + parent: children.parent, template: children.template, root_ids: children.root_ids, dynamic_nodes: children.dynamic_nodes, diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 3687cd956..2512ae9fa 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -13,6 +13,7 @@ use crate::{arena::ElementId, ScopeId, Template}; /// Templates, however, apply to all subtrees, not just target subtree. /// /// Mutations are the only link between the RealDOM and the VirtualDOM. +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[derive(Debug, Default)] #[must_use = "not handling edits can lead to visual inconsistencies in UI"] pub struct Mutations<'a> { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 27eed58d4..02c5bed99 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -78,12 +78,20 @@ impl<'a> VNode<'a> { /// Returns [`None`] if the root is actually a static node (Element/Text) pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> { match &self.template.roots[idx] { - TemplateNode::Element { .. } | TemplateNode::Text(_) => None, - TemplateNode::Dynamic(id) | TemplateNode::DynamicText(id) => { + TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None, + TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => { Some(&self.dynamic_nodes[*id]) } } } + + 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. @@ -124,7 +132,7 @@ pub struct Template<'a> { /// /// This can be created at compile time, saving the VirtualDom time when diffing the tree #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))] pub enum TemplateNode<'a> { /// An statically known element in the dom. /// @@ -151,15 +159,24 @@ pub enum TemplateNode<'a> { }, /// This template node is just a piece of static text - Text(&'a str), + Text { + /// The actual text + text: &'a str, + }, /// This template node is unknown, and needs to be created at runtime. - Dynamic(usize), + Dynamic { + /// The index of the dynamic node in the VNode's dynamic_nodes list + id: usize, + }, /// This template node is known to be some text, but needs to be created at runtime /// /// This is separate from the pure Dynamic variant for various optimizations - DynamicText(usize), + DynamicText { + /// The index of the dynamic node in the VNode's dynamic_nodes list + id: usize, + }, } /// A node created at runtime @@ -244,7 +261,11 @@ pub struct VText<'a> { /// An attribute of the TemplateNode, created at compile time #[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type") +)] pub enum TemplateAttribute<'a> { /// This attribute is entirely known at compile time, enabling Static { @@ -265,7 +286,10 @@ pub enum TemplateAttribute<'a> { /// The attribute in this position is actually determined dynamically at runtime /// /// This is the index into the dynamic_attributes field on the container VNode - Dynamic(usize), + Dynamic { + /// The index + id: usize, + }, } /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"` @@ -416,6 +440,8 @@ impl<'a> IntoDynNode<'a> for VNode<'a> { DynamicNode::Fragment(_cx.bump().alloc([self])) } } + +// An element that's an error is currently lost into the ether impl<'a> IntoDynNode<'a> for Element<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { match self { diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index f8fdfb595..2bf6cdfa1 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -1,5 +1,8 @@ use futures_util::FutureExt; -use std::task::{Context, Poll}; +use std::{ + rc::Rc, + task::{Context, Poll}, +}; use crate::{ innerlude::{Mutation, Mutations, SuspenseContext}, @@ -31,12 +34,10 @@ impl VirtualDom { } } - pub(crate) fn acquire_suspense_boundary<'a>(&self, id: ScopeId) -> &'a SuspenseContext { - let ct = self.scopes[id.0] - .consume_context::() - .unwrap(); - - unsafe { &*(ct as *const SuspenseContext) } + pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc { + self.scopes[id.0] + .consume_context::>() + .unwrap() } pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) { diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index d9e13baea..db328fd93 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -7,7 +7,6 @@ use crate::{ scheduler::RcWake, scopes::{ScopeId, ScopeState}, virtual_dom::VirtualDom, - AttributeValue, DynamicNode, VNode, }; use bumpalo::Bump; use futures_util::FutureExt; @@ -19,7 +18,11 @@ use std::{ }; impl VirtualDom { - pub(super) fn new_scope(&mut self, props: Box>) -> &ScopeState { + pub(super) fn new_scope( + &mut self, + props: Box>, + name: &'static str, + ) -> &ScopeState { let parent = self.acquire_current_scope_raw(); let entry = self.scopes.vacant_entry(); let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) }; @@ -30,6 +33,7 @@ impl VirtualDom { id, height, props: Some(props), + name, placeholder: Default::default(), node_arena_1: BumpFrame::new(0), node_arena_2: BumpFrame::new(0), @@ -50,42 +54,6 @@ impl VirtualDom { .and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _)) } - fn ensure_drop_safety(&self, scope: ScopeId) { - let scope = &self.scopes[scope.0]; - let node = unsafe { scope.previous_frame().try_load_node() }; - - // And now we want to make sure the previous frame has dropped anything that borrows self - if let Some(RenderReturn::Sync(Ok(node))) = node { - self.ensure_drop_safety_inner(node); - } - } - - fn ensure_drop_safety_inner(&self, node: &VNode) { - for attr in node.dynamic_attrs { - if let AttributeValue::Listener(l) = &attr.value { - l.borrow_mut().take(); - } - } - - for child in node.dynamic_nodes { - match child { - DynamicNode::Component(c) => { - // Only descend if the props are borrowed - if !c.static_props { - self.ensure_drop_safety(c.scope.get().unwrap()); - c.props.set(None); - } - } - DynamicNode::Fragment(f) => { - for node in *f { - self.ensure_drop_safety_inner(node); - } - } - _ => {} - } - } - } - pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn { // Cycle to the next frame and then reset it // This breaks any latent references, invalidating every pointer referencing into it. diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 06f5bf8c3..270704ca3 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -69,6 +69,7 @@ pub struct ScopeId(pub usize); /// This struct exists to provide a common interface for all scopes without relying on generics. pub struct ScopeState { pub(crate) render_cnt: Cell, + pub(crate) name: &'static str, pub(crate) node_arena_1: BumpFrame, pub(crate) node_arena_2: BumpFrame, @@ -116,6 +117,11 @@ impl<'src> ScopeState { } } + /// Get the name of this component + pub fn name(&self) -> &str { + self.name + } + /// Get the current render since the inception of this component /// /// This can be used as a helpful diagnostic when debugging hooks/renders, etc @@ -251,17 +257,18 @@ impl<'src> ScopeState { } /// Return any context of type T if it exists on this scope - pub fn has_context(&self) -> Option<&T> { - let contextex = self.shared_contexts.borrow(); - let val = contextex.get(&TypeId::of::())?; - let as_concrete = val.downcast_ref::()? as *const T; - Some(unsafe { &*as_concrete }) + pub fn has_context(&self) -> Option { + self.shared_contexts + .borrow() + .get(&TypeId::of::())? + .downcast_ref::() + .cloned() } /// Try to retrieve a shared state with type `T` from any parent scope. /// - /// To release the borrow, use `cloned` if the context is clone. - pub fn consume_context(&self) -> Option<&T> { + /// Clones the state if it exists. + pub fn consume_context(&self) -> Option { if let Some(this_ctx) = self.has_context() { return Some(this_ctx); } @@ -271,8 +278,7 @@ impl<'src> ScopeState { // safety: all parent pointers are valid thanks to the bump arena let parent = unsafe { &*parent_ptr }; if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { - let as_concrete = shared.downcast_ref::()? as *const T; - return Some(unsafe { &*as_concrete }); + return shared.downcast_ref::().cloned(); } search_parent = parent.parent; } @@ -284,7 +290,7 @@ impl<'src> ScopeState { /// /// This is a "fundamental" operation and should only be called during initialization of a hook. /// - /// For a hook that provides the same functionality, use `use_provide_context` and `use_consume_context` instead. + /// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead. /// /// If a state is provided that already exists, the new value will not be inserted. Instead, this method will /// return the existing value. This behavior is chosen so shared values do not need to be `Clone`. This particular @@ -305,20 +311,14 @@ impl<'src> ScopeState { /// render!(div { "hello {state.0}" }) /// } /// ``` - pub fn provide_context(&self, value: T) -> &T { - let mut contexts = self.shared_contexts.borrow_mut(); + pub fn provide_context(&self, value: T) -> T { + let value2 = value.clone(); - let any = match contexts.get(&TypeId::of::()) { - Some(item) => item.downcast_ref::().unwrap() as *const T, - None => { - let boxed = Box::new(value); - let boxed_ptr = boxed.as_ref() as *const T; - contexts.insert(TypeId::of::(), boxed); - boxed_ptr - } - }; + self.shared_contexts + .borrow_mut() + .insert(TypeId::of::(), Box::new(value)); - unsafe { &*any } + value2 } /// Pushes the future onto the poll queue to be polled after the component renders. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 643de2caf..471b8ccf7 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -240,20 +240,19 @@ impl VirtualDom { mutations: Mutations::default(), }; - let root = dom.new_scope(Box::new(VProps::new( - root, - |_, _| unreachable!(), - root_props, - ))); + let root = dom.new_scope( + Box::new(VProps::new(root, |_, _| unreachable!(), root_props)), + "app", + ); // The root component is always a suspense boundary for any async children // This could be unexpected, so we might rethink this behavior later // // We *could* just panic if the suspense boundary is not found - root.provide_context(SuspenseContext::new(ScopeId(0))); + root.provide_context(Rc::new(SuspenseContext::new(ScopeId(0)))); // Unlike react, we provide a default error boundary that just renders the error as a string - root.provide_context(ErrorBoundary::new(ScopeId(0))); + root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0)))); // the root element is always given element ID 0 since it's the container for the entire tree dom.elements.insert(ElementRef::null()); @@ -297,7 +296,7 @@ impl VirtualDom { /// currently suspended. pub fn is_scope_suspended(&self, id: ScopeId) -> bool { !self.scopes[id.0] - .consume_context::() + .consume_context::>() .unwrap() .waiting_on .borrow() @@ -528,7 +527,7 @@ impl VirtualDom { // first, unload any complete suspense trees for finished_fiber in self.finished_fibers.drain(..) { let scope = &mut self.scopes[finished_fiber.0]; - let context = scope.has_context::().unwrap(); + let context = scope.has_context::>().unwrap(); self.mutations .templates @@ -566,7 +565,7 @@ impl VirtualDom { // No placeholder necessary since this is a diff if !self.collected_leaves.is_empty() { let mut boundary = self.scopes[dirty.id.0] - .consume_context::() + .consume_context::>() .unwrap(); let boundary_mut = boundary.borrow_mut(); diff --git a/packages/core/tests/bubble_error.rs b/packages/core/tests/bubble_error.rs index 6dbd0f3f7..3dbfe80a9 100644 --- a/packages/core/tests/bubble_error.rs +++ b/packages/core/tests/bubble_error.rs @@ -17,12 +17,12 @@ fn app(cx: Scope) -> Element { } #[test] -fn it_goes() { +fn bubbles_error() { let mut dom = VirtualDom::new(app); - let edits = dom.rebuild().santize(); + let _edits = dom.rebuild().santize(); dom.mark_dirty(ScopeId(0)); - dom.render_immediate(); + _ = dom.render_immediate(); } diff --git a/packages/core/tests/diff_keyed_list.rs b/packages/core/tests/diff_keyed_list.rs index ba9fe2d62..81013849d 100644 --- a/packages/core/tests/diff_keyed_list.rs +++ b/packages/core/tests/diff_keyed_list.rs @@ -320,9 +320,9 @@ fn remove_list() { assert_eq!( dom.render_immediate().santize().edits, [ - Remove { id: ElementId(3) }, + Remove { id: ElementId(5) }, Remove { id: ElementId(4) }, - Remove { id: ElementId(5) } + Remove { id: ElementId(3) }, ] ); } @@ -345,10 +345,10 @@ fn no_common_keys() { assert_eq!( dom.render_immediate().santize().edits, [ - Remove { id: ElementId(2) }, Remove { id: ElementId(3) }, - LoadTemplate { name: "template", index: 0, id: ElementId(3) }, + Remove { id: ElementId(2) }, LoadTemplate { name: "template", index: 0, id: ElementId(2) }, + LoadTemplate { name: "template", index: 0, id: ElementId(3) }, LoadTemplate { name: "template", index: 0, id: ElementId(4) }, ReplaceWith { id: ElementId(1), m: 3 } ] diff --git a/packages/core/tests/diff_unkeyed_list.rs b/packages/core/tests/diff_unkeyed_list.rs index 0ddb71a42..415aa79c0 100644 --- a/packages/core/tests/diff_unkeyed_list.rs +++ b/packages/core/tests/diff_unkeyed_list.rs @@ -357,11 +357,11 @@ fn remove_many() { assert_eq!( edits.edits, [ - Remove { id: ElementId(1,) }, - Remove { id: ElementId(5,) }, - Remove { id: ElementId(7,) }, Remove { id: ElementId(9,) }, - CreatePlaceholder { id: ElementId(9,) }, + Remove { id: ElementId(7,) }, + Remove { id: ElementId(5,) }, + Remove { id: ElementId(1,) }, + CreatePlaceholder { id: ElementId(3,) }, ReplaceWith { id: ElementId(2,), m: 1 }, ] ); @@ -372,8 +372,8 @@ fn remove_many() { edits.edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, - HydrateText { path: &[0,], value: "hello 0", id: ElementId(10,) }, - ReplaceWith { id: ElementId(9,), m: 1 }, + HydrateText { path: &[0,], value: "hello 0", id: ElementId(1,) }, + ReplaceWith { id: ElementId(3,), m: 1 }, ] ) } diff --git a/packages/core/tests/miri_full_app.rs b/packages/core/tests/miri_full_app.rs index 6cd3d2bd8..c1c6d52cc 100644 --- a/packages/core/tests/miri_full_app.rs +++ b/packages/core/tests/miri_full_app.rs @@ -8,16 +8,16 @@ fn miri_rollover() { _ = dom.rebuild(); - for x in 0..3 { + for _ in 0..3 { dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true); dom.process_events(); - dom.render_immediate(); + _ = dom.render_immediate(); } } fn app(cx: Scope) -> Element { let mut idx = use_state(cx, || 0); - let onhover = |h| println!("go!"); + let onhover = |_| println!("go!"); cx.render(rsx! { div { @@ -31,7 +31,7 @@ fn app(cx: Scope) -> Element { button { onclick: move |_| idx -= 1, "-" } ul { (0..**idx).map(|i| rsx! { - Child { i: i, onhover: onhover } + child_example { i: i, onhover: onhover } }) } } @@ -39,7 +39,7 @@ fn app(cx: Scope) -> Element { } #[inline_props] -fn Child<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element { +fn child_example<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element { cx.render(rsx! { li { onmouseover: move |e| onhover.call(e), diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index 3ebd5c504..2d9760e5d 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -97,8 +97,8 @@ fn memo_works_properly() { let mut dom = VirtualDom::new(app); - dom.rebuild(); - todo!() + _ = dom.rebuild(); + // todo!() // dom.hard_diff(ScopeId(0)); // dom.hard_diff(ScopeId(0)); // dom.hard_diff(ScopeId(0)); diff --git a/packages/core/tests/safety.rs b/packages/core/tests/safety.rs index 564f21eb6..8c125f4c6 100644 --- a/packages/core/tests/safety.rs +++ b/packages/core/tests/safety.rs @@ -1,5 +1,7 @@ //! Tests related to safety of the library. +use std::rc::Rc; + use dioxus::prelude::*; use dioxus_core::SuspenseContext; @@ -18,5 +20,5 @@ fn root_node_isnt_null() { // There should be a default suspense context // todo: there should also be a default error boundary - assert!(scope.has_context::().is_some()); + assert!(scope.has_context::>().is_some()); } diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 2da343707..9fac13937 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -1,9 +1,9 @@ use dioxus::core::ElementId; use dioxus::core::{Mutation::*, SuspenseContext}; use dioxus::prelude::*; -use dioxus_core::SuspenseContext; use std::future::IntoFuture; -use std::{rc::Rc, time::Duration}; +use std::rc::Rc; +use std::time::Duration; #[tokio::test] async fn it_works() { @@ -51,10 +51,12 @@ fn app(cx: Scope) -> Element { } fn suspense_boundary(cx: Scope) -> Element { - cx.use_hook(|| cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id())))); + cx.use_hook(|| { + cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id()))); + }); // Ensure the right types are found - cx.has_context::().unwrap(); + cx.has_context::>().unwrap(); cx.render(rsx!(async_child {})) } @@ -72,7 +74,6 @@ async fn async_text(cx: Scope<'_>) -> Element { let age = use_future!(cx, || async { tokio::time::sleep(std::time::Duration::from_secs(2)).await; - println!("long future completed"); 1234 }); diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 724ec9b94..58e947574 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -33,7 +33,7 @@ webbrowser = "0.8.0" infer = "0.11.0" dunce = "1.0.2" -interprocess = { version = "1.1.1" } +interprocess = { version = "1.1.1", optional = true} futures-util = "0.3.25" [target.'cfg(target_os = "ios")'.dependencies] @@ -44,11 +44,12 @@ objc_id = "0.1.1" core-foundation = "0.9.3" [features] -default = ["tokio_runtime"] +default = ["tokio_runtime", "hot-reload"] tokio_runtime = ["tokio"] fullscreen = ["wry/fullscreen"] transparent = ["wry/transparent"] tray = ["wry/tray"] +hot-reload = ["interprocess"] [dev-dependencies] dioxus-core-macro = { path = "../core-macro" } diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index d01b6545f..7139c083f 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -1,7 +1,7 @@ use crate::desktop_context::{DesktopContext, UserWindowEvent}; use crate::events::{decode_event, EventMessage}; use dioxus_core::*; -use futures_channel::mpsc::UnboundedReceiver; +use futures_channel::mpsc::{unbounded, UnboundedSender}; use futures_util::StreamExt; #[cfg(target_os = "ios")] use objc::runtime::Object; @@ -22,6 +22,9 @@ pub(super) struct DesktopController { pub(super) pending_edits: Arc>>, pub(super) quit_app_on_close: bool, pub(super) is_ready: Arc, + pub(super) proxy: EventLoopProxy, + pub(super) event_tx: UnboundedSender, + #[cfg(target_os = "ios")] pub(super) views: Vec<*mut Object>, } @@ -33,9 +36,10 @@ impl DesktopController { root: Component

, props: P, proxy: EventLoopProxy, - mut event_rx: UnboundedReceiver, ) -> Self { let edit_queue = Arc::new(Mutex::new(Vec::new())); + let (event_tx, mut event_rx) = unbounded(); + let proxy2 = proxy.clone(); let pending_edits = edit_queue.clone(); let desktop_context_proxy = proxy.clone(); @@ -54,8 +58,7 @@ impl DesktopController { { let edits = dom.rebuild(); let mut queue = edit_queue.lock().unwrap(); - queue.push(serde_json::to_string(&edits.templates).unwrap()); - queue.push(serde_json::to_string(&edits.edits).unwrap()); + queue.push(serde_json::to_string(&edits).unwrap()); proxy.send_event(UserWindowEvent::EditsReady).unwrap(); } @@ -77,12 +80,8 @@ impl DesktopController { .render_with_deadline(tokio::time::sleep(Duration::from_millis(16))) .await; - { - let mut queue = edit_queue.lock().unwrap(); - queue.push(serde_json::to_string(&muts.templates).unwrap()); - queue.push(serde_json::to_string(&muts.edits).unwrap()); - let _ = proxy.send_event(UserWindowEvent::EditsReady); - } + edit_queue.lock().unwrap().push(serde_json::to_string(&muts).unwrap()); + let _ = proxy.send_event(UserWindowEvent::EditsReady); } }) }); @@ -92,6 +91,8 @@ impl DesktopController { webviews: HashMap::new(), is_ready: Arc::new(AtomicBool::new(false)), quit_app_on_close: true, + proxy: proxy2, + event_tx, #[cfg(target_os = "ios")] views: vec![], } @@ -116,12 +117,14 @@ impl DesktopController { let (_id, view) = self.webviews.iter_mut().next().unwrap(); - println!("processing pending edits {:?}", new_queue.len()); - for edit in new_queue.drain(..) { view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) .unwrap(); } } } + + pub(crate) fn set_template(&self, _serialized_template: String) { + todo!("hot reloading currently WIP") + } } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 71357e0ab..71d615a44 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -168,6 +168,11 @@ pub enum UserWindowEvent { DragWindow, FocusWindow, + /// Set a new Dioxus template for hot-reloading + /// + /// Is a no-op in release builds. Must fit the right format for templates + SetTemplate(String), + Visible(bool), Minimize(bool), Maximize(bool), @@ -195,99 +200,101 @@ pub enum UserWindowEvent { PopView, } -pub(super) fn handler( - user_event: UserWindowEvent, - desktop: &mut DesktopController, - control_flow: &mut ControlFlow, -) { - // currently dioxus-desktop supports a single window only, - // so we can grab the only webview from the map; - // on wayland it is possible that a user event is emitted - // before the webview is initialized. ignore the event. - let webview = if let Some(webview) = desktop.webviews.values().next() { - webview - } else { - return; - }; - let window = webview.window(); +impl DesktopController { + pub(super) fn handle_event( + &mut self, + user_event: UserWindowEvent, + control_flow: &mut ControlFlow, + ) { + // currently dioxus-desktop supports a single window only, + // so we can grab the only webview from the map; + // on wayland it is possible that a user event is emitted + // before the webview is initialized. ignore the event. + let webview = if let Some(webview) = self.webviews.values().next() { + webview + } else { + return; + }; - println!("user_event: {:?}", user_event); + let window = webview.window(); - match user_event { - Initialize | EditsReady => desktop.try_load_ready_webviews(), - CloseWindow => *control_flow = ControlFlow::Exit, - DragWindow => { - // if the drag_window has any errors, we don't do anything - window.fullscreen().is_none().then(|| window.drag_window()); - } - Visible(state) => window.set_visible(state), - Minimize(state) => window.set_minimized(state), - Maximize(state) => window.set_maximized(state), - MaximizeToggle => window.set_maximized(!window.is_maximized()), - Fullscreen(state) => { - if let Some(handle) = window.current_monitor() { - window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle)))); + match user_event { + Initialize | EditsReady => self.try_load_ready_webviews(), + SetTemplate(template) => self.set_template(template), + CloseWindow => *control_flow = ControlFlow::Exit, + DragWindow => { + // if the drag_window has any errors, we don't do anything + window.fullscreen().is_none().then(|| window.drag_window()); } - } - FocusWindow => window.set_focus(), - Resizable(state) => window.set_resizable(state), - AlwaysOnTop(state) => window.set_always_on_top(state), - - CursorVisible(state) => window.set_cursor_visible(state), - CursorGrab(state) => { - let _ = window.set_cursor_grab(state); - } - - SetTitle(content) => window.set_title(&content), - SetDecorations(state) => window.set_decorations(state), - - SetZoomLevel(scale_factor) => webview.zoom(scale_factor), - - Print => { - if let Err(e) = webview.print() { - // we can't panic this error. - log::warn!("Open print modal failed: {e}"); + Visible(state) => window.set_visible(state), + Minimize(state) => window.set_minimized(state), + Maximize(state) => window.set_maximized(state), + MaximizeToggle => window.set_maximized(!window.is_maximized()), + Fullscreen(state) => { + if let Some(handle) = window.current_monitor() { + window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle)))); + } } - } - DevTool => { - #[cfg(debug_assertions)] - webview.open_devtools(); - #[cfg(not(debug_assertions))] - log::warn!("Devtools are disabled in release builds"); - } + FocusWindow => window.set_focus(), + Resizable(state) => window.set_resizable(state), + AlwaysOnTop(state) => window.set_always_on_top(state), - Eval(code) => { - if let Err(e) = webview.evaluate_script(code.as_str()) { - // we can't panic this error. - log::warn!("Eval script error: {e}"); + CursorVisible(state) => window.set_cursor_visible(state), + CursorGrab(state) => { + let _ = window.set_cursor_grab(state); } - } - #[cfg(target_os = "ios")] - PushView(view) => unsafe { - use objc::runtime::Object; - use objc::*; - assert!(is_main_thread()); - let ui_view = window.ui_view() as *mut Object; - let ui_view_frame: *mut Object = msg_send![ui_view, frame]; - let _: () = msg_send![view, setFrame: ui_view_frame]; - let _: () = msg_send![view, setAutoresizingMask: 31]; + SetTitle(content) => window.set_title(&content), + SetDecorations(state) => window.set_decorations(state), - let ui_view_controller = window.ui_view_controller() as *mut Object; - let _: () = msg_send![ui_view_controller, setView: view]; - desktop.views.push(ui_view); - }, + SetZoomLevel(scale_factor) => webview.zoom(scale_factor), + + Print => { + if let Err(e) = webview.print() { + // we can't panic this error. + log::warn!("Open print modal failed: {e}"); + } + } + DevTool => { + #[cfg(debug_assertions)] + webview.open_devtools(); + #[cfg(not(debug_assertions))] + log::warn!("Devtools are disabled in release builds"); + } + + Eval(code) => { + if let Err(e) = webview.evaluate_script(code.as_str()) { + // we can't panic this error. + log::warn!("Eval script error: {e}"); + } + } + + #[cfg(target_os = "ios")] + PushView(view) => unsafe { + use objc::runtime::Object; + use objc::*; + assert!(is_main_thread()); + let ui_view = window.ui_view() as *mut Object; + let ui_view_frame: *mut Object = msg_send![ui_view, frame]; + let _: () = msg_send![view, setFrame: ui_view_frame]; + let _: () = msg_send![view, setAutoresizingMask: 31]; - #[cfg(target_os = "ios")] - PopView => unsafe { - use objc::runtime::Object; - use objc::*; - assert!(is_main_thread()); - if let Some(view) = desktop.views.pop() { let ui_view_controller = window.ui_view_controller() as *mut Object; let _: () = msg_send![ui_view_controller, setView: view]; - } - }, + self.views.push(ui_view); + }, + + #[cfg(target_os = "ios")] + PopView => unsafe { + use objc::runtime::Object; + use objc::*; + assert!(is_main_thread()); + if let Some(view) = self.views.pop() { + let ui_view_controller = window.ui_view_controller() as *mut Object; + let _: () = msg_send![ui_view_controller, setView: view]; + } + }, + } } } diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index c407013ea..b82d6f749 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -1,6 +1,5 @@ //! Convert a serialized event to an event trigger -use dioxus_core::ElementId; use dioxus_html::events::*; use serde::{Deserialize, Serialize}; use serde_json::from_value; @@ -43,7 +42,6 @@ macro_rules! match_data { ) => { match $name { $( $($mname)|* => { - println!("casting to type {:?}", std::any::TypeId::of::<$tip>()); let val: $tip = from_value::<$tip>($m).ok()?; Rc::new(val) as Rc })* diff --git a/packages/desktop/src/hot_reload.rs b/packages/desktop/src/hot_reload.rs index e3faf3ef1..e2628cdd4 100644 --- a/packages/desktop/src/hot_reload.rs +++ b/packages/desktop/src/hot_reload.rs @@ -1,4 +1,7 @@ +#![allow(dead_code)] + use dioxus_core::VirtualDom; + use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; use std::io::{BufRead, BufReader}; use std::time::Duration; @@ -10,18 +13,21 @@ fn handle_error(connection: std::io::Result) -> Option>>> = Arc::new(Mutex::new(None)); + let latest_in_connection_handle = latest_in_connection.clone(); // connect to processes for incoming data std::thread::spawn(move || { - // if let Ok(listener) = LocalSocketListener::bind("@dioxusin") { - // for conn in listener.incoming().filter_map(handle_error) { - // *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn)); - // } - // } + let temp_file = std::env::temp_dir().join("@dioxusin"); + + if let Ok(listener) = LocalSocketListener::bind(temp_file) { + for conn in listener.incoming().filter_map(handle_error) { + *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn)); + } + } }); std::thread::spawn(move || { diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index a729a2f6f..585a7ff51 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -8,13 +8,17 @@ mod controller; mod desktop_context; mod escape; mod events; -#[cfg(any(feature = "hot-reload", debug_assertions))] -mod hot_reload; mod protocol; +#[cfg(all(feature = "hot-reload", debug_assertions))] +mod hot_reload; + +use std::sync::atomic::AtomicBool; +use std::sync::Arc; + use desktop_context::UserWindowEvent; pub use desktop_context::{use_eval, use_window, DesktopContext}; -use futures_channel::mpsc::unbounded; +use futures_channel::mpsc::UnboundedSender; pub use wry; pub use wry::application as tao; @@ -100,15 +104,68 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// ``` pub fn launch_with_props(root: Component

, props: P, mut cfg: Config) { let event_loop = EventLoop::with_user_event(); + let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy()); - let (event_tx, event_rx) = unbounded(); - let mut desktop = - DesktopController::new_on_tokio(root, props, event_loop.create_proxy(), event_rx); - let proxy = event_loop.create_proxy(); + event_loop.run(move |window_event, event_loop, control_flow| { + *control_flow = ControlFlow::Wait; - // We assume that if the icon is None, then the user just didnt set it + match window_event { + Event::NewEvents(StartCause::Init) => desktop.start(&mut cfg, event_loop), + + Event::WindowEvent { + event, window_id, .. + } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow), + _ => {} + }, + + Event::UserEvent(user_event) => desktop.handle_event(user_event, control_flow), + Event::MainEventsCleared => {} + Event::Resumed => {} + Event::Suspended => {} + Event::LoopDestroyed => {} + Event::RedrawRequested(_id) => {} + _ => {} + } + }) +} + +impl DesktopController { + fn start( + &mut self, + cfg: &mut Config, + event_loop: &tao::event_loop::EventLoopWindowTarget, + ) { + let webview = build_webview( + cfg, + event_loop, + self.is_ready.clone(), + self.proxy.clone(), + self.event_tx.clone(), + ); + + self.webviews.insert(webview.window().id(), webview); + } +} + +fn build_webview( + cfg: &mut Config, + event_loop: &tao::event_loop::EventLoopWindowTarget, + is_ready: Arc, + proxy: tao::event_loop::EventLoopProxy, + event_tx: UnboundedSender, +) -> wry::webview::WebView { + let builder = cfg.window.clone(); + let window = builder.build(event_loop).unwrap(); + let file_handler = cfg.file_drop_handler.take(); + let custom_head = cfg.custom_head.clone(); + let resource_dir = cfg.resource_dir.clone(); + let index_file = cfg.custom_index.clone(); + + // We assume that if the icon is None in cfg, then the user just didnt set it if cfg.window.window.window_icon.is_none() { - cfg.window = cfg.window.with_window_icon(Some( + window.set_window_icon(Some( tao::window::Icon::from_rgba( include_bytes!("./assets/default_icon.bin").to_vec(), 460, @@ -118,85 +175,62 @@ pub fn launch_with_props(root: Component

, props: P, mut cf )); } - event_loop.run(move |window_event, event_loop, control_flow| { - *control_flow = ControlFlow::Wait; - - // println!("window event: {:?}", window_event); - match window_event { - Event::NewEvents(StartCause::Init) => { - let builder = cfg.window.clone(); - - let window = builder.build(event_loop).unwrap(); - let window_id = window.id(); - - let (is_ready, _) = (desktop.is_ready.clone(), ()); - // let (is_ready, sender) = (desktop.is_ready.clone(), desktop.sender.clone()); - - let proxy = proxy.clone(); - - let file_handler = cfg.file_drop_handler.take(); - let custom_head = cfg.custom_head.clone(); - let resource_dir = cfg.resource_dir.clone(); - let index_file = cfg.custom_index.clone(); - let event_tx = event_tx.clone(); - - let mut webview = WebViewBuilder::new(window) - .unwrap() - .with_transparent(cfg.window.window.transparent) - .with_url("dioxus://index.html/") - .unwrap() - .with_ipc_handler(move |_window: &Window, payload: String| { - parse_ipc_message(&payload) - .map(|message| match message.method() { - "user_event" => { - _ = event_tx.unbounded_send(message.params()); + let mut webview = WebViewBuilder::new(window) + .unwrap() + .with_transparent(cfg.window.window.transparent) + .with_url("dioxus://index.html/") + .unwrap() + .with_ipc_handler(move |_window: &Window, payload: String| { + parse_ipc_message(&payload) + .map(|message| match message.method() { + "user_event" => { + _ = event_tx.unbounded_send(message.params()); + } + "initialize" => { + is_ready.store(true, std::sync::atomic::Ordering::Relaxed); + let _ = proxy.send_event(UserWindowEvent::EditsReady); + } + "browser_open" => { + let data = message.params(); + log::trace!("Open browser: {:?}", data); + if let Some(temp) = data.as_object() { + if temp.contains_key("href") { + let url = temp.get("href").unwrap().as_str().unwrap(); + if let Err(e) = webbrowser::open(url) { + log::error!("Open Browser error: {:?}", e); } - "initialize" => { - is_ready.store(true, std::sync::atomic::Ordering::Relaxed); - println!("initializing..."); - let _ = proxy.send_event(UserWindowEvent::EditsReady); - } - "browser_open" => { - let data = message.params(); - log::trace!("Open browser: {:?}", data); - if let Some(temp) = data.as_object() { - if temp.contains_key("href") { - let url = temp.get("href").unwrap().as_str().unwrap(); - if let Err(e) = webbrowser::open(url) { - log::error!("Open Browser error: {:?}", e); - } - } - } - } - _ => (), - }) - .unwrap_or_else(|| { - log::warn!("invalid IPC message received"); - }); - }) - .with_custom_protocol(String::from("dioxus"), move |r| { - protocol::desktop_handler( - r, - resource_dir.clone(), - custom_head.clone(), - index_file.clone(), - ) - }) - .with_file_drop_handler(move |window, evet| { - file_handler - .as_ref() - .map(|handler| handler(window, evet)) - .unwrap_or_default() - }); + } + } + } + _ => (), + }) + .unwrap_or_else(|| { + log::warn!("invalid IPC message received"); + }); + }) + .with_custom_protocol(String::from("dioxus"), move |r| { + protocol::desktop_handler( + r, + resource_dir.clone(), + custom_head.clone(), + index_file.clone(), + ) + }) + .with_file_drop_handler(move |window, evet| { + file_handler + .as_ref() + .map(|handler| handler(window, evet)) + .unwrap_or_default() + }); - for (name, handler) in cfg.protocols.drain(..) { - webview = webview.with_custom_protocol(name, handler) - } + for (name, handler) in cfg.protocols.drain(..) { + webview = webview.with_custom_protocol(name, handler) + } - if cfg.disable_context_menu { - // in release mode, we don't want to show the dev tool or reload menus - webview = webview.with_initialization_script( - r#" + if cfg.disable_context_menu { + // in release mode, we don't want to show the dev tool or reload menus + webview = webview.with_initialization_script( + r#" if (document.addEventListener) { document.addEventListener('contextmenu', function(e) { e.preventDefault(); @@ -207,32 +241,11 @@ pub fn launch_with_props(root: Component

, props: P, mut cf }); } "#, - ) - } else { - // in debug, we are okay with the reload menu showing and dev tool - webview = webview.with_devtools(true); - } + ) + } else { + // in debug, we are okay with the reload menu showing and dev tool + webview = webview.with_devtools(true); + } - desktop.webviews.insert(window_id, webview.build().unwrap()); - } - - Event::WindowEvent { - event, window_id, .. - } => match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow), - _ => {} - }, - - Event::UserEvent(user_event) => { - desktop_context::handler(user_event, &mut desktop, control_flow) - } - Event::MainEventsCleared => {} - Event::Resumed => {} - Event::Suspended => {} - Event::LoopDestroyed => {} - Event::RedrawRequested(_id) => {} - _ => {} - } - }) + webview.build().unwrap() } diff --git a/packages/fermi/src/hooks/atom_root.rs b/packages/fermi/src/hooks/atom_root.rs index 267b71161..24fd70093 100644 --- a/packages/fermi/src/hooks/atom_root.rs +++ b/packages/fermi/src/hooks/atom_root.rs @@ -1,6 +1,7 @@ +use std::rc::Rc; + use crate::AtomRoot; use dioxus_core::ScopeState; -use std::rc::Rc; // Returns the atom root, initiaizing it at the root of the app if it does not exist. pub fn use_atom_root(cx: &ScopeState) -> &Rc { diff --git a/packages/hooks/src/usecallback.rs b/packages/hooks/src/usecallback.rs index aaa7432cf..3f223b581 100644 --- a/packages/hooks/src/usecallback.rs +++ b/packages/hooks/src/usecallback.rs @@ -1,6 +1,4 @@ -use std::rc::Rc; - -use dioxus_core::{prelude::EventHandler, ScopeState}; +use dioxus_core::ScopeState; use std::future::Future; #[macro_export] @@ -19,8 +17,14 @@ macro_rules! use_callback { move || $($rest)* ) }; + ($cx:ident, $($rest:tt)*) => { + use_callback( + $cx, + move || $($rest)* + ) + }; } -pub fn use_callback<'a, T, R, F>(cx: &'a ScopeState, make: impl FnOnce() -> R) -> impl FnMut(T) + 'a +pub fn use_callback(cx: &ScopeState, make: impl FnOnce() -> R) -> impl FnMut(T) + '_ where R: FnMut(T) -> F + 'static, F: Future + 'static, @@ -30,8 +34,8 @@ where move |evt| cx.spawn(hook(evt)) } -fn it_works(cx: &ScopeState) { - let p = use_callback(cx, || { +fn _it_works(cx: &ScopeState) { + let _p = use_callback(cx, || { |()| async { // } diff --git a/packages/hooks/src/usecontext.rs b/packages/hooks/src/usecontext.rs index 02902fa76..74f7cea93 100644 --- a/packages/hooks/src/usecontext.rs +++ b/packages/hooks/src/usecontext.rs @@ -1,17 +1,17 @@ use dioxus_core::ScopeState; -/// Consume some context in the tree -pub fn use_context(cx: &ScopeState) -> Option<&T> { - match *cx.use_hook(|| cx.consume_context::().map(|t| t as *const T)) { - Some(res) => Some(unsafe { &*res }), - None => None, - } +/// Consume some context in the tree, providing a sharable handle to the value +pub fn use_context(cx: &ScopeState) -> Option<&T> { + cx.use_hook(|| cx.consume_context::()).as_ref() } /// Provide some context via the tree and return a reference to it /// /// Once the context has been provided, it is immutable. Mutations should be done via interior mutability. -pub fn use_context_provider(cx: &ScopeState, f: impl FnOnce() -> T) -> &T { - let ptr = *cx.use_hook(|| cx.provide_context(f()) as *const T); - unsafe { &*ptr } +pub fn use_context_provider(cx: &ScopeState, f: impl FnOnce() -> T) -> &T { + cx.use_hook(|| { + let val = f(); + cx.provide_context(val.clone()); + val + }) } diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index 1eae035be..df34a6433 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -66,9 +66,7 @@ where // if there's a waker, we dont re-render the component. Instead we just progress that future match waker.borrow().as_ref() { Some(waker) => waker.wake_by_ref(), - None => { - // schedule_update() - } + None => schedule_update(), } }))); } @@ -122,7 +120,7 @@ impl UseFuture { } // Manually set the value in the future slot without starting the future over - pub fn set(&self, new_value: T) { + pub fn set(&self, _new_value: T) { // self.slot.set(Some(new_value)); // self.needs_regen.set(true); // (self.update)(); diff --git a/packages/html/src/render_template.rs b/packages/html/src/render_template.rs index 5fbff5ff0..e608f6aa1 100644 --- a/packages/html/src/render_template.rs +++ b/packages/html/src/render_template.rs @@ -33,9 +33,9 @@ fn render_template_node(node: &TemplateNode, out: &mut String) -> std::fmt::Resu } write!(out, "")?; } - TemplateNode::Text(t) => write!(out, "{t}")?, - TemplateNode::Dynamic(_) => write!(out, "