130 files changed
+2250
-901
No files matched your search
+2
-2
@@ -9,6 +9,7 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "./packages/core", version = "0.1.3" }
|
||||
dioxus-router = { path = "./packages/router", optional = true }
|
||||
dioxus-core-macro = { path = "./packages/core-macro", optional = true }
|
||||
dioxus-html = { path = "./packages/html", optional = true }
|
||||
dioxus-web = { path = "./packages/web", optional = true }
|
||||
@@ -24,10 +25,10 @@ core = ["macro", "hooks", "html"]
|
||||
macro = ["dioxus-core-macro"]
|
||||
hooks = ["dioxus-hooks"]
|
||||
html = ["dioxus-html"]
|
||||
router = ["dioxus-router"]
|
||||
|
||||
# utilities
|
||||
atoms = []
|
||||
router = []
|
||||
|
||||
# targets
|
||||
ssr = ["dioxus-ssr"]
|
||||
@@ -78,7 +79,6 @@ members = [
|
||||
"packages/ssr",
|
||||
"packages/desktop",
|
||||
"packages/mobile",
|
||||
"packages/webview-client",
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
|
||||
|
||||
```rust
|
||||
fn App((cx, props): Component<()>) -> Element {
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
let mut count = use_state(cx, || 0);
|
||||
|
||||
cx.render(rsx!(
|
||||
@@ -147,8 +147,11 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
|
||||
| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees |
|
||||
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
|
||||
| Heuristic Engine | ✅ | ❓ | track component memory usage to minimize future allocations |
|
||||
| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates |
|
||||
| Effects | 🛠 | ✅ | Run effects after a component has been committed to render |
|
||||
| Server Components | 🛠 | ✅ | Hybrid components for SPA and Server |
|
||||
| Bundle Splitting | 👀 | ✅ | Hybrid components for SPA and Server |
|
||||
| Lazy Components | 👀 | ✅ | Dynamically load the new components as the page is loaded |
|
||||
| Fine-grained reactivity | 👀 | ❓ | Skip diffing for fine-grain updates |
|
||||
|
||||
- ✅ = implemented and working
|
||||
- 🛠 = actively being worked on
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
- [Lists](concepts/lists.md)
|
||||
- [Adding Interactivity](concepts/interactivity.md)
|
||||
- [Event handlers](concepts/event_handlers.md)
|
||||
- [Hooks and Internal State](concepts/hooks.md)
|
||||
- [Fundamental Hooks and `use_hook`](concepts/usestate.md)
|
||||
- [User Input and Controlled Components](concepts/errorhandling.md)
|
||||
- [Lifecycle, updates, and effects](concepts/lifecycles.md)
|
||||
|
||||
@@ -30,7 +32,6 @@ Extracting State Logic into a Reducer
|
||||
Passing Data Deeply with Context
|
||||
Scaling Up with Reducer and Context -->
|
||||
- [Managing State](concepts/managing_state.md)
|
||||
- [Hooks and Internal State](concepts/hooks.md)
|
||||
- [Global State](concepts/sharedstate.md)
|
||||
- [Error handling](concepts/errorhandling.md)
|
||||
- [Effects](concepts/effects.md)
|
||||
|
||||
@@ -84,7 +84,7 @@ struct PostProps {
|
||||
|
||||
And our render function:
|
||||
```rust
|
||||
fn Post((cx, props): Component<PostProps>) -> Element {
|
||||
fn Post((cx, props): Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "post-container"
|
||||
VoteButton {
|
||||
@@ -120,7 +120,7 @@ struct VoteButtonProps {
|
||||
score: i32
|
||||
}
|
||||
|
||||
fn VoteButton((cx, props): Component<VoteButtonProps>) -> Element {
|
||||
fn VoteButton((cx, props): Scope<VoteButtonProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
div { class: "votebutton"
|
||||
div { class: "arrow up" }
|
||||
@@ -145,7 +145,7 @@ struct TitleCardProps<'a> {
|
||||
title: &'a str,
|
||||
}
|
||||
|
||||
fn TitleCard<'a>((cx, props): Component<'a, TitleCardProps>) -> Element<'a> {
|
||||
fn TitleCard<'a>((cx, props): Scope<'a, TitleCardProps>) -> Element<'a> {
|
||||
cx.render(rsx!{
|
||||
h1 { "{props.title}" }
|
||||
})
|
||||
@@ -174,7 +174,7 @@ function Component(props) {
|
||||
Because Dioxus needs to work with the rules of Rust it uses the `Context` object to maintain some internal bookkeeping. That's what the `Context` object is: a place for the component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Context` object to build robust and performant extensions for Dioxus.
|
||||
|
||||
```rust
|
||||
fn Post((cx /* <-- our Context object*/, props): Component<PostProps>) -> Element {
|
||||
fn Post((cx /* <-- our Context object*/, props): Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!{ })
|
||||
}
|
||||
```
|
||||
|
||||
@@ -26,7 +26,7 @@ struct AppProps {
|
||||
Now that we have a "logged_in" flag accessible in our props, we can render two different screens:
|
||||
|
||||
```rust
|
||||
fn App((cx, props): Component<AppProps>) -> Element {
|
||||
fn App((cx, props): Scope<AppProps>) -> Element {
|
||||
if props.logged_in {
|
||||
cx.render(rsx!{
|
||||
DashboardScreen {}
|
||||
@@ -48,7 +48,7 @@ Rust provides us algebraic datatypes: enums that can contain values. Using the `
|
||||
For instance, we could run a function that returns a Result:
|
||||
|
||||
```rust
|
||||
fn App((cx, props): Component<()>) -> Element {
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
match get_name() {
|
||||
Ok(name) => cx.render(rsx!( "Hello, {name}!" )),
|
||||
Err(err) => cx.render(rsx!( "Sorry, I don't know your name, because an error occurred: {err}" )),
|
||||
@@ -58,7 +58,7 @@ fn App((cx, props): Component<()>) -> Element {
|
||||
|
||||
We can even match against values:
|
||||
```rust
|
||||
fn App((cx, props): Component<()>) -> Element {
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
match get_name() {
|
||||
"jack" => cx.render(rsx!( "Hey Jack, how's Diane?" )),
|
||||
"diane" => cx.render(rsx!( "Hey Diana, how's Jack?" )),
|
||||
@@ -67,12 +67,12 @@ fn App((cx, props): Component<()>) -> Element {
|
||||
}
|
||||
```
|
||||
|
||||
Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. Because every closure is unique, we cannot return *just* `rsx!` from a match statement. Every `rsx!` call must be rendered first when used in match statements.
|
||||
Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type.
|
||||
|
||||
To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax:
|
||||
|
||||
```rust
|
||||
fn App((cx, props): Component<()>) -> Element {
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
match get_name() {
|
||||
"jack" => rsx!(cx, "Hey Jack, how's Diane?" ),
|
||||
"diane" => rsx!(cx, "Hey Diana, how's Jack?" ),
|
||||
@@ -86,6 +86,19 @@ This syntax even enables us to write a one-line component:
|
||||
static App: Fc<()> = |(cx, props)| rsx!(cx, "hello world!");
|
||||
```
|
||||
|
||||
Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`:
|
||||
|
||||
```rust
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
let greeting = match get_name() {
|
||||
"jack" => rsx!("Hey Jack, how's Diane?" ),
|
||||
"diane" => rsx!("Hey Diana, how's Jack?" ),
|
||||
name => rsx!("Hello, {name}!" ),
|
||||
};
|
||||
cx.render(greeting)
|
||||
}
|
||||
```
|
||||
|
||||
## Nesting RSX
|
||||
|
||||
By looking at other examples, you might have noticed that it's possible to include `rsx!` calls inside other `rsx!` calls. With the curly-brace syntax, we can include anything in our `rsx!` that implements `IntoVnodeList`: a marker trait for iterators that produce Elements. `rsx!` itself implements this trait, so we can include it directly:
|
||||
@@ -117,8 +130,8 @@ In the case of a log-in screen, we might want to display the same NavBar and Foo
|
||||
|
||||
```rust
|
||||
let screen = match logged_in {
|
||||
true => rsx!(cx, DashboardScreen {}),
|
||||
false => rsx!(cx, LoginScreen {})
|
||||
true => rsx!(DashboardScreen {}),
|
||||
false => rsx!(LoginScreen {})
|
||||
};
|
||||
|
||||
cx.render(rsx!{
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
# Event handlers
|
||||
|
||||
In the overview for this section, we mentioned how we can modify the state of our component by responding to user-generated events inside of event listeners.
|
||||
|
||||
In this section, we'll talk more about event listeners:
|
||||
- What events are available
|
||||
- Handling event data
|
||||
- Mutability in event listeners
|
||||
|
||||
## Event Listeners
|
||||
|
||||
@@ -1 +1,183 @@
|
||||
# Adding Interactivity
|
||||
|
||||
So far, we've learned how to describe the structure and properties of our user interfaces. Unfortunately, they're static and quite a bit uninteresting. In this chapter, we're going to learn how to add interactivity through events, state, and tasks.
|
||||
|
||||
## Primer on interactivity
|
||||
|
||||
Before we get too deep into the mechanics of interactivity, we should first understand how Dioxus exactly chooses to handle user interaction and updates to your app.
|
||||
|
||||
### What is state?
|
||||
|
||||
Every app you'll ever build has some sort of information that needs to be rendered to the screen. Dioxus is responsible for translating your desired user interface to what is rendered to the screen. *You* are responsible for providing the content.
|
||||
|
||||
The dynamic data in your user interface is called `State`.
|
||||
|
||||
When you first launch your app with `dioxus::web::launch_with_props` you'll be providing the initial state. You need to declare the initial state *before* starting the app.
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
// declare our initial state
|
||||
let props = PostProps {
|
||||
id: Uuid::new_v4(),
|
||||
score: 10,
|
||||
comment_count: 0,
|
||||
post_time: std::time::Instant::now(),
|
||||
url: String::from("dioxuslabs.com"),
|
||||
title: String::from("Hello, world"),
|
||||
original_poster: String::from("dioxus")
|
||||
};
|
||||
|
||||
// start the render loop
|
||||
dioxus::desktop::launch_with_props(Post, props);
|
||||
}
|
||||
```
|
||||
|
||||
When Dioxus renders your app, it will pass an immutable reference of `PostProps` to your `Post` component. Here, you can pass the state down into children.
|
||||
|
||||
```rust
|
||||
fn App((cx, props): Scope<PostProps>) -> Element {
|
||||
cx.render(rsx!{
|
||||
Title { title: &props.title }
|
||||
Score { score: &props.score }
|
||||
// etc
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
State in Dioxus follows a pattern called "one-way-data-flow." As your components create new components as their children, your app's structure will eventually grow into a tree where state gets passed down from the root component into "leaves" of the tree.
|
||||
|
||||
You've probably seen the tree of UI components represented using an directed-acyclic-graph:
|
||||
|
||||

|
||||
|
||||
With Dioxus, your state will always flow down from parent components into child components.
|
||||
|
||||
### How do I change my app's state?
|
||||
|
||||
We've talked about the data flow of state, but we haven't yet talked about how to change that state dynamically. Dioxus provides a variety of ways to change the state of your app while it's running.
|
||||
|
||||
For starters, we _could_ use the `update_root_props` method on the VirtualDom to provide an entirely new root state of your App. However, for most applications, you probably don't want to regenerate your entire app just to update some text or a flag.
|
||||
|
||||
Instead, you'll want to store state internally in your components and let *that* flow down the tree. To store state in your components, you'll use something called a `hook`. Hooks are special functions that reserve a slot of state in your component's memory and provide some functionality to update that state.
|
||||
|
||||
The most common hook you'll use for storing state is `use_state`. `use_state` provides a slot for some data that allows you to read and update the value without accidentally mutating it.
|
||||
|
||||
```rust
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
let post = use_state(|| {
|
||||
PostData {
|
||||
id: Uuid::new_v4(),
|
||||
score: 10,
|
||||
comment_count: 0,
|
||||
post_time: std::time::Instant::now(),
|
||||
url: String::from("dioxuslabs.com"),
|
||||
title: String::from("Hello, world"),
|
||||
original_poster: String::from("dioxus")
|
||||
}
|
||||
});
|
||||
|
||||
cx.render(rsx!{
|
||||
Title { title: &post.title }
|
||||
Score { score: &post.score }
|
||||
// etc
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Whenever we have a new post that we want to render, we can call `set` on `post` and provide a new value:
|
||||
|
||||
```rust
|
||||
post.set(PostData {
|
||||
id: Uuid::new_v4(),
|
||||
score: 20,
|
||||
comment_count: 0,
|
||||
post_time: std::time::Instant::now(),
|
||||
url: String::from("google.com"),
|
||||
title: String::from("goodbye, world"),
|
||||
original_poster: String::from("google")
|
||||
})
|
||||
```
|
||||
|
||||
We'll dive deeper into how exactly these hooks work later.
|
||||
|
||||
### When do I update my state?
|
||||
|
||||
There are a few different approaches to choosing when to update your state. You can update your state in response to user-triggered events or asynchronously in some background task.
|
||||
|
||||
### Updating state in listeners
|
||||
|
||||
When responding to user-triggered events, we'll want to "listen" for an event on some element in our component.
|
||||
|
||||
For example, let's say we provide a button to generate a new post. Whenever the user clicks the button, they get a new post. To achieve this functionality, we'll want to attach a function to the `on_click` method of `button`. Whenever the button is clicked, our function will run, and we'll get new Post data to work with.
|
||||
|
||||
```rust
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
let post = use_state(|| PostData::new());
|
||||
|
||||
cx.render(rsx!{
|
||||
button {
|
||||
on_click: move |_| post.set(PostData::random())
|
||||
"Generate a random post"
|
||||
}
|
||||
Post { props: &post }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
We'll dive much deeper into event listeners later.
|
||||
|
||||
### Updating state asynchronously
|
||||
|
||||
We can also update our state outside of event listeners with `tasks`. `Tasks` are asynchronous blocks of our component that have the ability to cleanly interact with values, hooks, and other data in the component. `Tasks` are one-shot - if they don't complete before the component is updated by another `.set` call, then they won't finish.
|
||||
|
||||
We can use tasks in our components to build a tiny stopwatch that ticks every second.
|
||||
|
||||
```rust
|
||||
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
let mut sec_elapsed = use_state(|| 0);
|
||||
|
||||
cx.spawn_task(async move {
|
||||
TimeoutFuture::from_ms(1000).await;
|
||||
sec_elapsed += 1;
|
||||
});
|
||||
|
||||
cx.render(rsx!{
|
||||
div { "Current stopwatch time: {sec_elapsed}" }
|
||||
})
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Using asynchronous code can be difficult! This is just scratching the surface of what's possible. We have an entire chapter on using async properly in your Dioxus Apps.
|
||||
|
||||
### How do I tell Dioxus that my state changed?
|
||||
|
||||
So far, we've only updated our state with `.set`. However, you might've noticed that we used `AddAssign` to increment the `sec_elapsed` value in our stopwatch example *without* calling set. This is because the `AddAssign` trait is implemented for `UseState<T>` (the wrapper around our value returned from `use_state`). Under the hood, whenever you try to mutate our value through `UseState`, you're actually calling `.set` which informs Dioxus that _this_ component needs to be updated on the screen.
|
||||
|
||||
Whenever you inform Dioxus that the component needs to be updated, it will "render" your component again, storing the previous and current Elements in memory. Dioxus will automatically figure out the differences between the old and the new and generate a list of edits that the renderer needs to apply to change what's on the screen. This process is called "diffing":
|
||||
|
||||

|
||||
|
||||
In React, the specifics of when a component gets re-rendered is somewhat blurry. With Dioxus, any component can mark itself as "dirty" through a method on `Context`: `needs_update`. In addition, any component can mark any _other_ component as dirty provided it knows the other component's ID with `needs_update_any`.
|
||||
|
||||
With these building blocks, we can craft new hooks similar to `use_state` that let us easily tell Dioxus that new information is ready to be sent to the screen.
|
||||
|
||||
### How do I update my state efficiently?
|
||||
|
||||
In general, Dioxus should be plenty fast for most use cases. However, there are some rules you should consider following to ensure your apps are quick.
|
||||
|
||||
- 1) **Don't call set—state _while rendering_**. This will cause Dioxus to unnecessarily re-check the component for updates.
|
||||
- 2) **Break your state apart into smaller sections.** Hooks are explicitly designed to "unshackle" your state from the typical model-view-controller paradigm, making it easy to reuse useful bits of code with a single function.
|
||||
- 3) **Move local state down**. Dioxus will need to re-check child components of your app if the root component is constantly being updated. You'll get best results if rapidly-changing state does not cause major re-renders.
|
||||
|
||||
Don't worry - Dioxus is fast. But, if your app needs *extreme performance*, then take a look at the `Performance Tuning` in the `Advanced Guides` book.
|
||||
|
||||
## Moving On
|
||||
|
||||
This overview was a lot of information - but it doesn't tell you everything!
|
||||
|
||||
In the next sections we'll go over:
|
||||
- `use_state` in depth
|
||||
- `use_ref` and other hooks
|
||||
- Handling user input
|
||||
@@ -0,0 +1 @@
|
||||
# Fundamental Hooks and use_hook
|
||||
@@ -129,7 +129,7 @@ rsx!(
|
||||
)
|
||||
```
|
||||
|
||||
Note: the name of the custom attribute must match exactly what you want the renderer to output. All attributes defined as methods in `dioxus-html` follow the snake_case naming convention. However, they internally translate their snake_case convention to HTML's camelCase convention.
|
||||
Note: the name of the custom attribute must match exactly what you want the renderer to output. All attributes defined as methods in `dioxus-html` follow the snake_case naming convention. However, they internally translate their snake_case convention to HTML's camelCase convention. When using custom attributes, make sure the name of the attribute exactly matches what the renderer is expecting.
|
||||
|
||||
## Listeners
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
@@ -20,7 +20,7 @@ fn main() {
|
||||
dioxus::desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
fn App((cx, props): Component<()>) -> DomTree {
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f, |_| {});
|
||||
|
||||
let first = text.get_mut(0).unwrap();
|
||||
@@ -43,7 +43,7 @@ impl<'a> Drop for C1Props<'a> {
|
||||
fn drop(&mut self) {}
|
||||
}
|
||||
|
||||
fn Child1<'a>((cx, props): Component<'a, C1Props>) -> DomTree<'a> {
|
||||
fn Child1<'a>((cx, props): Scope<'a, C1Props>) -> Element<'a> {
|
||||
let (left, right) = props.text.split_once("=").unwrap();
|
||||
|
||||
cx.render(rsx! {
|
||||
@@ -59,7 +59,7 @@ struct C2Props<'a> {
|
||||
text: &'a str,
|
||||
}
|
||||
|
||||
fn Child2<'a>((cx, props): Component<'a, C2Props>) -> DomTree<'a> {
|
||||
fn Child2<'a>((cx, props): Scope<'a, C2Props>) -> Element<'a> {
|
||||
cx.render(rsx! {
|
||||
Child3 {
|
||||
text: props.text
|
||||
@@ -72,7 +72,7 @@ struct C3Props<'a> {
|
||||
text: &'a str,
|
||||
}
|
||||
|
||||
fn Child3<'a>((cx, props): Component<'a, C3Props>) -> DomTree<'a> {
|
||||
fn Child3<'a>((cx, props): Scope<'a, C3Props>) -> Element<'a> {
|
||||
cx.render(rsx! {
|
||||
div { "{props.text}"}
|
||||
})
|
||||
|
||||
@@ -114,12 +114,13 @@ const APP: FC<()> = |(cx, _)| {
|
||||
struct CalculatorKeyProps<'a> {
|
||||
name: &'static str,
|
||||
onclick: &'a dyn Fn(MouseEvent),
|
||||
children: ScopeChildren<'a>,
|
||||
}
|
||||
|
||||
fn CalculatorKey<'a>((cx, props): Component<'a, CalculatorKeyProps>) -> DomTree<'a> {
|
||||
fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps<'a>>) -> Element {
|
||||
rsx!(cx, button {
|
||||
class: "calculator-key {props.name}"
|
||||
onclick: {props.onclick}
|
||||
{cx.children()}
|
||||
{&props.children}
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_core_macro::format_args_f;
|
||||
use dioxus_core_macro::rsx;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
fn main() {
|
||||
let mut dom = VirtualDom::new(EXAMPLE);
|
||||
@@ -7,9 +10,14 @@ fn main() {
|
||||
}
|
||||
|
||||
pub static EXAMPLE: FC<()> = |(cx, _)| {
|
||||
let list = (0..10).map(|_f| LazyNodes::new(move |_f| todo!()));
|
||||
let list = (0..10).map(|_f| {
|
||||
rsx! {
|
||||
"{_f}"
|
||||
}
|
||||
});
|
||||
// let list = (0..10).map(|_f| Some(Box::new(move |_f| todo!())));
|
||||
|
||||
cx.render(LazyNodes::new(move |cx| {
|
||||
cx.render(Some(LazyNodes::new(move |cx| {
|
||||
cx.raw_element(
|
||||
"div",
|
||||
None,
|
||||
@@ -22,5 +30,5 @@ pub static EXAMPLE: FC<()> = |(cx, _)| {
|
||||
],
|
||||
None,
|
||||
)
|
||||
}))
|
||||
})))
|
||||
};
|
||||
File renamed without changes.
@@ -20,31 +20,32 @@ struct ListItem {
|
||||
age: u32,
|
||||
}
|
||||
|
||||
fn app<'a>(cx: Context<'a>, props: &AppProps) -> DomTree<'a> {
|
||||
fn app<'a>(cx: Context<'a>, props: &AppProps) -> Element<'a> {
|
||||
// let (val, set_val) = use_state_classic(cx, || 0);
|
||||
|
||||
cx.render(LazyNodes::new(move |_nodecx| {
|
||||
todo!()
|
||||
// builder::ElementBuilder::new(_nodecx, "div")
|
||||
// .iter_child({
|
||||
// cx.items.iter().map(|child| {
|
||||
// builder::virtual_child(
|
||||
// _nodecx,
|
||||
// ChildItem,
|
||||
// ChildProps {
|
||||
// item: child.clone(),
|
||||
// item_handler: set_val.clone(),
|
||||
// },
|
||||
// None,
|
||||
// &[],
|
||||
// )
|
||||
// })
|
||||
// })
|
||||
// .iter_child([builder::ElementBuilder::new(_nodecx, "div")
|
||||
// .iter_child([builder::text3(_nodecx.bump(), format_args!("{}", val))])
|
||||
// .finish()])
|
||||
// .finish()
|
||||
}))
|
||||
todo!()
|
||||
// cx.render(LazyNodes::new(move |_nodecx| {
|
||||
// todo!()
|
||||
// // builder::ElementBuilder::new(_nodecx, "div")
|
||||
// // .iter_child({
|
||||
// // cx.items.iter().map(|child| {
|
||||
// // builder::virtual_child(
|
||||
// // _nodecx,
|
||||
// // ChildItem,
|
||||
// // ChildProps {
|
||||
// // item: child.clone(),
|
||||
// // item_handler: set_val.clone(),
|
||||
// // },
|
||||
// // None,
|
||||
// // &[],
|
||||
// // )
|
||||
// // })
|
||||
// // })
|
||||
// // .iter_child([builder::ElementBuilder::new(_nodecx, "div")
|
||||
// // .iter_child([builder::text3(_nodecx.bump(), format_args!("{}", val))])
|
||||
// // .finish()])
|
||||
// // .finish()
|
||||
// }))
|
||||
}
|
||||
|
||||
// props should derive a partialeq implementation automatically, but implement ptr compare for & fields
|
||||
@@ -56,8 +57,8 @@ struct ChildProps {
|
||||
item_handler: Rc<dyn Fn(i32)>,
|
||||
}
|
||||
|
||||
fn ChildItem<'a>(cx: Context<'a>, props: &'a ChildProps) -> DomTree<'a> {
|
||||
cx.render(LazyNodes::new(move |__cx| todo!()))
|
||||
fn ChildItem<'a>(cx: Context<'a>, props: &'a ChildProps) -> Element<'a> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
impl PartialEq for ChildProps {
|
||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,46 @@
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
fn main() {}
|
||||
|
||||
fn app<'a>(cx: Context<'a>, props: &()) -> Element<'a> {
|
||||
// let vak = use_suspense(
|
||||
// cx,
|
||||
// || async {},
|
||||
// |c, _res| c.render(LazyNodes::new(move |f| f.text(format_args!("")))),
|
||||
// );
|
||||
|
||||
// let d1 = cx.render(to_lazy_nodes(move |f| {
|
||||
// f.raw_element(
|
||||
// "div",
|
||||
// None,
|
||||
// [],
|
||||
// [],
|
||||
// [
|
||||
// // f.fragment_from_iter(vak),
|
||||
// f.text(format_args!("")),
|
||||
// f.text(format_args!("")),
|
||||
// f.text(format_args!("")),
|
||||
// f.text(format_args!("")),
|
||||
// ],
|
||||
// None,
|
||||
// )
|
||||
// }));
|
||||
|
||||
todo!()
|
||||
// cx.render(LazyNodes::new(move |f| {
|
||||
// f.raw_element(
|
||||
// "div",
|
||||
// None,
|
||||
// [],
|
||||
// [],
|
||||
// [
|
||||
// f.text(format_args!("")),
|
||||
// f.text(format_args!("")),
|
||||
// f.text(format_args!("")),
|
||||
// f.text(format_args!("")),
|
||||
// d1.unwrap(),
|
||||
// ],
|
||||
// None,
|
||||
// )
|
||||
// }))
|
||||
}
|
||||
File renamed without changes.
@@ -1,6 +1,6 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::component::Component;
|
||||
use dioxus::component::Scope;
|
||||
use dioxus::events::on::MouseEvent;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
@@ -15,7 +15,7 @@ fn main() {
|
||||
assert!(g.edits.len() > 1);
|
||||
}
|
||||
|
||||
fn App((cx, props): Component<()>) -> DomTree {
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let rows = (0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
@@ -41,22 +41,22 @@ struct RowProps {
|
||||
label: Label,
|
||||
}
|
||||
|
||||
fn Row((cx, props): Component<RowProps>) -> DomTree {
|
||||
fn Row<'a>((cx, props): Scope<'a, RowProps>) -> Element<'a> {
|
||||
let handler = move |evt: MouseEvent| {
|
||||
let g = evt.button;
|
||||
};
|
||||
cx.render(rsx! {
|
||||
tr {
|
||||
td { class:"col-md-1", "{props.row_id}" }
|
||||
td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
|
||||
a { class: "lbl", "{props.label}" }
|
||||
}
|
||||
td { class: "col-md-1"
|
||||
a { class: "remove", onclick: {handler}
|
||||
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
|
||||
}
|
||||
}
|
||||
td { class: "col-md-6" }
|
||||
// td { class:"col-md-1", "{props.row_id}" }
|
||||
// td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
|
||||
// a { class: "lbl", "{props.label}" }
|
||||
// }
|
||||
// td { class: "col-md-1"
|
||||
// a { class: "remove", onclick: {handler}
|
||||
// span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
|
||||
// }
|
||||
// }
|
||||
// td { class: "col-md-6" }
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use dioxus::component::Scope;
|
||||
use dioxus::events::on::MouseEvent;
|
||||
use dioxus::nodes::IntoVNode;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
fn main() {}
|
||||
|
||||
fn html_usage() {
|
||||
let mo = move |_| {};
|
||||
let r = rsx! {
|
||||
div {
|
||||
onclick: move |_| {}
|
||||
onmouseover: {mo}
|
||||
"type": "bar",
|
||||
"hello world"
|
||||
}
|
||||
};
|
||||
|
||||
let items = ["bob", "bill", "jack"];
|
||||
|
||||
let f = items
|
||||
.iter()
|
||||
.filter(|f| f.starts_with('b'))
|
||||
.map(|f| rsx!("hello {f}"));
|
||||
|
||||
// let p = rsx!(div { {f} });
|
||||
}
|
||||
|
||||
static App2: FC<()> = |(cx, _)| cx.render(rsx!("hello world!"));
|
||||
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
let name = cx.use_state(|| 0);
|
||||
|
||||
cx.render(rsx!(div {
|
||||
h1 {}
|
||||
h2 {}
|
||||
}))
|
||||
};
|
||||
|
||||
pub trait UseState<'a, T: 'static> {
|
||||
fn use_state(self, f: impl FnOnce() -> T) -> &'a T;
|
||||
}
|
||||
impl<'a, T: 'static> UseState<'a, T> for Context<'a> {
|
||||
fn use_state(self, f: impl FnOnce() -> T) -> &'a T {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn App3((cx, props): Scope<()>) -> Element {
|
||||
let p = rsx! {
|
||||
Child {
|
||||
bame: 10,
|
||||
}
|
||||
};
|
||||
todo!()
|
||||
// cx.render(rsx!(Child {
|
||||
// bame: 102,
|
||||
// ..ChildProps { bame: 10 }
|
||||
// }))
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq, Debug)]
|
||||
struct ChildProps {
|
||||
bame: i32, // children: Children<'a>,
|
||||
}
|
||||
|
||||
fn Child<'a>((cx, props): Scope<'a, ChildProps>) -> Element<'a> {
|
||||
cx.render(rsx!(div {
|
||||
// {props.children}
|
||||
}))
|
||||
}
|
||||
|
||||
// Some(LazyNodes::new(|f| {
|
||||
// //
|
||||
// // let r = f.fragment_from_iter(&props.children);
|
||||
// r
|
||||
// // todo!()
|
||||
// }))
|
||||
// todo!()
|
||||
// rsx!({ Some(p) })
|
||||
// todo!()
|
||||
|
||||
pub struct Children<'a> {
|
||||
children: VNode<'static>,
|
||||
_p: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> Children<'a> {
|
||||
pub fn new(children: VNode<'a>) -> Self {
|
||||
Self {
|
||||
children: unsafe { std::mem::transmute(children) },
|
||||
_p: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Bapp: FC<()> = |(cx, props)| {
|
||||
let name = cx.use_state(|| 0);
|
||||
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
div {
|
||||
|
||||
}
|
||||
div {
|
||||
|
||||
}
|
||||
}
|
||||
))
|
||||
};
|
||||
|
||||
static Match: FC<()> = |(cx, props)| {
|
||||
//
|
||||
let b: Box<dyn Fn(NodeFactory) -> VNode> = Box::new(|f| todo!());
|
||||
|
||||
let b = match "ag" {
|
||||
"a" => {
|
||||
let __b: Box<dyn FnOnce(NodeFactory) -> VNode> = Box::new(|f: NodeFactory| todo!());
|
||||
__b
|
||||
}
|
||||
_ => {
|
||||
let __b: Box<dyn FnOnce(NodeFactory) -> VNode> = Box::new(|f: NodeFactory| todo!());
|
||||
__b
|
||||
}
|
||||
};
|
||||
|
||||
// let b: Box<dyn Fn(NodeFactory) -> VNode> = match "alph" {
|
||||
// "beta" => Box::new(|f: NodeFactory| {
|
||||
// //
|
||||
// todo!()
|
||||
// }),
|
||||
// _ => Box::new(|f: NodeFactory| {
|
||||
// //
|
||||
// todo!()
|
||||
// }),
|
||||
// };
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
}
|
||||
})
|
||||
};
|
||||
@@ -0,0 +1,259 @@
|
||||
use std::borrow::BorrowMut;
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use dioxus::component::Scope;
|
||||
use dioxus::events::on::MouseEvent;
|
||||
use dioxus::nodes::{IntoVNode, VComponent, VFragment, VText};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
fn main() {}
|
||||
|
||||
fn t() {
|
||||
let g = rsx! {
|
||||
div {
|
||||
div {
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let g = {
|
||||
let ___p: Box<dyn FnOnce(NodeFactory) -> VNode> = Box::new(|__cx: NodeFactory| {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
__cx.element(dioxus_elements::div, [], [], [], None)
|
||||
});
|
||||
// let __z = ___p as ;
|
||||
// __z
|
||||
};
|
||||
}
|
||||
|
||||
// #[derive(PartialEq, Props)]
|
||||
// struct OurProps {
|
||||
// foo: String,
|
||||
// }
|
||||
|
||||
// fn App<'a>((cx, props): Scope<'a, OurProps>) -> Element<'a> {
|
||||
// let a = rsx! {
|
||||
// div {
|
||||
// "asd"
|
||||
// "{props.foo}"
|
||||
// }
|
||||
// };
|
||||
|
||||
// let p = (0..10).map(|f| {
|
||||
// rsx! {
|
||||
// div {
|
||||
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
// let g = match "text" {
|
||||
// "a" => {
|
||||
// rsx!("asd")
|
||||
// }
|
||||
// _ => {
|
||||
// rsx!("asd")
|
||||
// }
|
||||
// };
|
||||
|
||||
// let items = ["bob", "bill", "jack"];
|
||||
|
||||
// let f = items
|
||||
// .iter()
|
||||
// .filter(|f| f.starts_with('b'))
|
||||
// .map(|f| rsx!("hello {f}"));
|
||||
|
||||
// // use dioxus_hooks;
|
||||
// // let g = use_state(|| "hello".to_string());
|
||||
|
||||
// let s: &'a mut String = cx.use_hook(|_| String::new(), |f| f, |_| {});
|
||||
|
||||
// /*
|
||||
// the final closure is allowed to borrow anything provided it
|
||||
// */
|
||||
// // cx.render({
|
||||
// // let p: Option<Box<dyn FnOnce(_) -> _>> = Some(Box::new(move |__cx: NodeFactory| {
|
||||
// // use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
|
||||
// // let props = Child2Props { foo: s };
|
||||
// // let ch: VNode = __cx.component(Child2, props, None, []);
|
||||
// // __cx.element(
|
||||
// // dioxus_elements::div,
|
||||
// // [],
|
||||
// // [],
|
||||
// // [ch],
|
||||
// // // [__cx.component(Child2, fc_to_builder(Child2).foo(s).build(), None, [])],
|
||||
// // None,
|
||||
// // )
|
||||
// // }));
|
||||
// // p
|
||||
// // // let ___p: Box<dyn FnOnce(NodeFactory) -> VNode> = Box::new(move |__cx| {
|
||||
// // // use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
|
||||
// // // let props = Child2Props { foo: s };
|
||||
// // // let ch: VNode = __cx.component(Child2, props, None, []);
|
||||
// // // __cx.element(
|
||||
// // // dioxus_elements::div,
|
||||
// // // [],
|
||||
// // // [],
|
||||
// // // [ch],
|
||||
// // // // [__cx.component(Child2, fc_to_builder(Child2).foo(s).build(), None, [])],
|
||||
// // // None,
|
||||
// // // )
|
||||
// // // });
|
||||
// // // Some(___p)
|
||||
// // })
|
||||
|
||||
// let a = annotate_lazy(move |f| {
|
||||
// //
|
||||
// todo!()
|
||||
// });
|
||||
// let b = annotate_lazy(move |f| {
|
||||
// //
|
||||
// f.text(format_args!("{}", props.foo))
|
||||
// });
|
||||
|
||||
// let c = annotate_lazy(move |f| {
|
||||
// //
|
||||
// f.component(
|
||||
// Child,
|
||||
// OurProps {
|
||||
// //
|
||||
// foo: "hello".to_string(),
|
||||
// },
|
||||
// None,
|
||||
// [],
|
||||
// )
|
||||
// });
|
||||
|
||||
// let st: &'a String = cx.use_hook(|_| "hello".to_string(), |f| f, |_| {});
|
||||
|
||||
// let d = annotate_lazy(move |f| {
|
||||
// //
|
||||
// f.component(
|
||||
// Child2,
|
||||
// Child2Props {
|
||||
// //
|
||||
// foo: st,
|
||||
// },
|
||||
// None,
|
||||
// [],
|
||||
// )
|
||||
// });
|
||||
|
||||
// let e = match "asd" {
|
||||
// b => {
|
||||
// //
|
||||
|
||||
// annotate_lazy(move |f| {
|
||||
// //
|
||||
// f.text(format_args!("{}", props.foo))
|
||||
// })
|
||||
// }
|
||||
// a => {
|
||||
// //
|
||||
|
||||
// annotate_lazy(move |f| {
|
||||
// //
|
||||
// f.text(format_args!("{}", props.foo))
|
||||
// })
|
||||
// }
|
||||
// };
|
||||
|
||||
// cx.render(annotate_lazy(move |f| {
|
||||
// //
|
||||
|
||||
// f.raw_element(
|
||||
// "div",
|
||||
// None,
|
||||
// [],
|
||||
// [],
|
||||
// [
|
||||
// //
|
||||
// f.fragment_from_iter(a),
|
||||
// f.fragment_from_iter(b),
|
||||
// f.fragment_from_iter(c),
|
||||
// f.fragment_from_iter(e),
|
||||
// ],
|
||||
// None,
|
||||
// )
|
||||
// // todo!()
|
||||
// }))
|
||||
// // cx.render(rsx! {
|
||||
// // div {
|
||||
// // div {
|
||||
// // {a}
|
||||
// // // {p}
|
||||
// // // {g}
|
||||
// // // {f}
|
||||
// // }
|
||||
// // // div {
|
||||
// // // "asd"
|
||||
// // // div {
|
||||
// // // "asd"
|
||||
// // // }
|
||||
// // // }
|
||||
// // // Child {
|
||||
// // // foo: "asd".to_string(),
|
||||
// // // }
|
||||
// // Child2 {
|
||||
// // foo: s,
|
||||
// // }
|
||||
// // }
|
||||
// // })
|
||||
// }
|
||||
|
||||
// fn Child((cx, props): Scope<OurProps>) -> Element {
|
||||
// cx.render(rsx! {
|
||||
// div {
|
||||
// div {}
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
#[derive(Props)]
|
||||
struct Child2Props<'a> {
|
||||
foo: &'a String,
|
||||
}
|
||||
|
||||
fn Child2<'a>((cx, props): Scope<'a, Child2Props>) -> Element<'a> {
|
||||
let node = cx
|
||||
.render(rsx! {
|
||||
div {
|
||||
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
ChildrenComp {
|
||||
div {}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
struct ChildrenTest<'a> {
|
||||
children: ScopeChildren<'a>,
|
||||
}
|
||||
|
||||
fn ChildrenComp<'a>((cx, props): Scope<'a, ChildrenTest<'a>>) -> Element<'a> {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
div {
|
||||
{&props.children}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn ChildrenMemo((cx, props): Scope<()>) -> Element {
|
||||
todo!()
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,17 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use dioxus_core::{lazynodes::LazyNodes, prelude::*};
|
||||
|
||||
// #[async_std::main]
|
||||
fn main() {
|
||||
static App: FC<()> =
|
||||
|(cx, props)| cx.render(Some(LazyNodes::new(move |f| f.text(format_args!("hello")))));
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
dom.rebuild();
|
||||
|
||||
// let deadline = async_std::task::sleep(Duration::from_millis(50));
|
||||
|
||||
// let _fut = dom.run_with_deadline(|| deadline.);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+11
-9
@@ -46,7 +46,8 @@ static App: FC<()> = |(cx, props)| {
|
||||
});
|
||||
|
||||
cx.render(rsx! {
|
||||
div { style: { width: "400px", height: "400px", position: "relative", background: "yellow" }
|
||||
div {
|
||||
width: "400px", height: "400px", position: "relative", background: "yellow"
|
||||
button { "reset", onclick: move |_| {} }
|
||||
Horsey { pos: *p1, "horsey 1" }
|
||||
Horsey { pos: *p2, "horsey 2" }
|
||||
@@ -54,18 +55,19 @@ static App: FC<()> = |(cx, props)| {
|
||||
})
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct HorseyProps {
|
||||
#[derive(Props)]
|
||||
struct HorseyProps<'a> {
|
||||
pos: i32,
|
||||
children: ScopeChildren<'a>,
|
||||
}
|
||||
|
||||
static Horsey: FC<HorseyProps> = |(cx, props)| {
|
||||
fn Horsey<'a>((cx, props): Scope<'a, HorseyProps<'a>>) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
button { "pause" }
|
||||
div {
|
||||
{cx.children()}
|
||||
button { "pause" }
|
||||
div {
|
||||
{&props.children}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
+5
-5
@@ -20,12 +20,12 @@ pub struct Client {
|
||||
}
|
||||
|
||||
static App: FC<()> = |(cx, _)| {
|
||||
let clients = use_ref(cx, || vec![] as Vec<Client>);
|
||||
let scene = use_state(cx, || Scene::ClientsList);
|
||||
let mut clients = use_ref(cx, || vec![] as Vec<Client>);
|
||||
let mut scene = use_state(cx, || Scene::ClientsList);
|
||||
|
||||
let firstname = use_state(cx, || String::new());
|
||||
let lastname = use_state(cx, || String::new());
|
||||
let description = use_state(cx, || String::new());
|
||||
let mut firstname = use_state(cx, || String::new());
|
||||
let mut lastname = use_state(cx, || String::new());
|
||||
let mut description = use_state(cx, || String::new());
|
||||
|
||||
let scene = match *scene {
|
||||
Scene::ClientsList => {
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -34,11 +34,11 @@ pub type Todos = HashMap<u32, TodoItem>;
|
||||
|
||||
pub static App: FC<()> = |(cx, _)| {
|
||||
// Share our TodoList to the todos themselves
|
||||
use_provide_state(cx, || Todos::new());
|
||||
use_provide_state(cx, Todos::new);
|
||||
|
||||
// Save state for the draft, filter
|
||||
let mut draft = use_state(cx, || "".to_string());
|
||||
let mut filter = use_state(cx, || FilterState::All);
|
||||
let draft = use_state(cx, || "".to_string());
|
||||
let filter = use_state(cx, || FilterState::All);
|
||||
let mut todo_id = use_state(cx, || 0);
|
||||
|
||||
// Consume the todos
|
||||
@@ -95,7 +95,7 @@ pub static App: FC<()> = |(cx, _)| {
|
||||
placeholder: "What needs to be done?"
|
||||
value: "{draft}"
|
||||
autofocus: "true"
|
||||
oninput: move |evt| draft.set(evt.value.clone())
|
||||
oninput: move |evt| draft.set(evt.value)
|
||||
onkeydown: move |evt| {
|
||||
if evt.key == "Enter" {
|
||||
submit_todo();
|
||||
@@ -136,7 +136,7 @@ pub struct TodoEntryProps {
|
||||
id: u32,
|
||||
}
|
||||
|
||||
pub fn TodoEntry((cx, props): Component<TodoEntryProps>) -> DomTree {
|
||||
pub fn TodoEntry((cx, props): Scope<TodoEntryProps>) -> Element {
|
||||
let todos = use_shared_state::<Todos>(cx)?;
|
||||
|
||||
let _todos = todos.read();
|
||||
@@ -150,7 +150,9 @@ pub fn TodoEntry((cx, props): Component<TodoEntryProps>) -> DomTree {
|
||||
div { class: "view"
|
||||
input { class: "toggle" r#type: "checkbox" id: "cbg-{todo.id}" checked: "{todo.checked}"
|
||||
onchange: move |evt| {
|
||||
todos.write().get_mut(&props.id).map(|todo| todo.checked = evt.value.parse().unwrap());
|
||||
if let Some(todo) = todos.write().get_mut(&props.id) {
|
||||
todo.checked = evt.value.parse().unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +163,9 @@ pub fn TodoEntry((cx, props): Component<TodoEntryProps>) -> DomTree {
|
||||
{is_editing.then(|| rsx!{
|
||||
input { value: "{todo.contents}"
|
||||
oninput: move |evt| {
|
||||
todos.write().get_mut(&props.id).map(|todo| todo.contents = evt.value.clone());
|
||||
if let Some(todo) = todos.write().get_mut(&props.id) {
|
||||
todo.contents = evt.value
|
||||
}
|
||||
},
|
||||
}
|
||||
})}
|
||||
@@ -10,11 +10,11 @@ fn main() {
|
||||
type RowList = im_rc::HashMap<usize, Rc<str>, FxBuildHasher>;
|
||||
|
||||
static App: FC<()> = |(cx, _props)| {
|
||||
let items = use_state(cx, || RowList::default());
|
||||
let mut items = use_state(cx, || RowList::default());
|
||||
|
||||
let create_rendered_rows = move |from, num| move |_| items.set(create_row_list(from, num));
|
||||
|
||||
let append_1_000_rows =
|
||||
let mut append_1_000_rows =
|
||||
move |_| items.set(create_row_list(items.len(), 1000).union((*items).clone()));
|
||||
|
||||
let update_every_10th_row = move |_| {
|
||||
@@ -80,7 +80,7 @@ struct ActionButtonProps<'a> {
|
||||
onclick: &'a dyn Fn(MouseEvent),
|
||||
}
|
||||
|
||||
fn ActionButton<'a>((cx, props): Component<'a, ActionButtonProps>) -> DomTree<'a> {
|
||||
fn ActionButton<'a>((cx, props): Scope<'a, ActionButtonProps>) -> Element<'a> {
|
||||
rsx!(cx, div { class: "col-sm-6 smallpad"
|
||||
button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: {props.onclick},
|
||||
"{props.name}"
|
||||
@@ -93,7 +93,7 @@ struct RowProps {
|
||||
row_id: usize,
|
||||
label: Rc<str>,
|
||||
}
|
||||
fn Row((cx, props): Component<RowProps>) -> DomTree {
|
||||
fn Row((cx, props): Scope<RowProps>) -> Element {
|
||||
rsx!(cx, tr {
|
||||
td { class:"col-md-1", "{props.row_id}" }
|
||||
td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
|
||||
|
||||
@@ -4,7 +4,7 @@ fn main() {
|
||||
dioxus::desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
fn App((cx, props): Component<()>) -> DomTree {
|
||||
fn App((cx, props): Scope<()>) -> Element {
|
||||
cx.render(rsx! (
|
||||
div { "Hello, world!" }
|
||||
))
|
||||
|
||||
@@ -74,14 +74,15 @@ static App: FC<()> = |(cx, props)| {
|
||||
struct CalculatorKeyProps<'a> {
|
||||
name: &'static str,
|
||||
onclick: &'a dyn Fn(MouseEvent),
|
||||
children: ScopeChildren<'a>,
|
||||
}
|
||||
|
||||
fn CalculatorKey<'a>((cx, props): Component<'a, CalculatorKeyProps>) -> DomTree<'a> {
|
||||
fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps<'a>>) -> Element<'a> {
|
||||
cx.render(rsx! {
|
||||
button {
|
||||
class: "calculator-key {props.name}"
|
||||
onclick: {props.onclick}
|
||||
{cx.children()}
|
||||
{&props.children}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
use dioxus::prelude::*;
|
||||
use dioxus::router::Router;
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum Route {
|
||||
// #[at("/")]
|
||||
Home,
|
||||
|
||||
// #[at("/:id")]
|
||||
AllUsers { page: u32 },
|
||||
|
||||
// #[at("/:id")]
|
||||
User { id: u32 },
|
||||
|
||||
// #[at("/:id")]
|
||||
BlogList { page: u32 },
|
||||
|
||||
// #[at("/:id")]
|
||||
BlogPost { post_id: u32 },
|
||||
|
||||
// #[at("/404")]
|
||||
// #[not_found]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
static App: FC<()> = |(cx, props)| {
|
||||
let route = use_router(cx, Route::parse)?;
|
||||
|
||||
cx.render(rsx! {
|
||||
nav {
|
||||
Link { to: Route::Home, "Go home!" }
|
||||
Link { to: Route::AllUsers { page: 0 }, "List all users" }
|
||||
Link { to: Route::BlogList { page: 0 }, "Blog posts" }
|
||||
}
|
||||
{match route {
|
||||
Route::Home => rsx!("Home"),
|
||||
Route::AllUsers { page } => rsx!("All users - page {page}"),
|
||||
Route::User { id } => rsx!("User - id: {id}"),
|
||||
Route::BlogList { page } => rsx!("Blog posts - page {page}"),
|
||||
Route::BlogPost { post_id } => rsx!("Blog post - post {post_id}"),
|
||||
Route::NotFound => rsx!("Not found"),
|
||||
}}
|
||||
footer {}
|
||||
})
|
||||
};
|
||||
|
||||
impl Route {
|
||||
// Generate the appropriate route from the "tail" end of the URL
|
||||
fn parse(url: &str) -> Self {
|
||||
use Route::*;
|
||||
match url {
|
||||
"/" => Home,
|
||||
"/users" => AllUsers { page: 1 },
|
||||
"/users/:page" => AllUsers { page: 1 },
|
||||
"/users/:page/:id" => User { id: 1 },
|
||||
"/blog" => BlogList { page: 1 },
|
||||
"/blog/:page" => BlogList { page: 1 },
|
||||
"/blog/:page/:id" => BlogPost { post_id: 1 },
|
||||
_ => NotFound,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Routable for Route {
|
||||
fn from_path(path: &str, params: &std::collections::HashMap<&str, &str>) -> Option<Self> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn to_path(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn routes() -> Vec<&'static str> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn not_found_route() -> Option<Self> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn recognize(pathname: &str) -> Option<Self> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {}
|
||||
+10
-7
@@ -162,13 +162,13 @@ pub static Example: FC<()> = |(cx, props)| {
|
||||
|
||||
// Can pass in props directly as an expression
|
||||
{{
|
||||
let props = TallerProps {a: "hello"};
|
||||
let props = TallerProps {a: "hello", children: Default::default()};
|
||||
rsx!(Taller { ..props })
|
||||
}}
|
||||
|
||||
// Spreading can also be overridden manually
|
||||
Taller {
|
||||
..TallerProps { a: "ballin!" }
|
||||
..TallerProps { a: "ballin!", children: Default::default() }
|
||||
a: "not ballin!"
|
||||
}
|
||||
|
||||
@@ -180,22 +180,25 @@ pub static Example: FC<()> = |(cx, props)| {
|
||||
|
||||
mod baller {
|
||||
use super::*;
|
||||
#[derive(PartialEq, Props)]
|
||||
#[derive(Props, PartialEq)]
|
||||
pub struct BallerProps {}
|
||||
|
||||
/// This component totally balls
|
||||
pub fn Baller(_: Component<BallerProps>) -> DomTree {
|
||||
pub fn Baller(_: Scope<BallerProps>) -> Element {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Props)]
|
||||
pub struct TallerProps {
|
||||
#[derive(Props)]
|
||||
pub struct TallerProps<'a> {
|
||||
a: &'static str,
|
||||
|
||||
#[builder(default)]
|
||||
children: ScopeChildren<'a>,
|
||||
}
|
||||
|
||||
/// This component is taller than most :)
|
||||
pub fn Taller(_: Component<TallerProps>) -> DomTree {
|
||||
pub fn Taller<'a>(_: Scope<'a, TallerProps<'a>>) -> Element {
|
||||
let b = true;
|
||||
todo!()
|
||||
}
|
||||
+1
-1
@@ -21,6 +21,6 @@ static App: FC<()> = |(cx, props)| {
|
||||
struct MyProps<'a> {
|
||||
text: &'a str,
|
||||
}
|
||||
fn App2<'a>(cx: Context<'a>, props: &'a MyProps) -> DomTree<'a> {
|
||||
fn App2<'a>(cx: Context<'a>, props: &'a MyProps) -> Element<'a> {
|
||||
None
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -119,14 +119,14 @@ pub static Entry: FC<()> = |(cx, props)| {
|
||||
pub static StacksIcon: FC<()> = |(cx, props)| {
|
||||
cx.render(rsx!(
|
||||
svg {
|
||||
// xmlns: "http://www.w3.org/2000/svg"
|
||||
xmlns: "http://www.w3.org/2000/svg"
|
||||
fill: "none"
|
||||
stroke: "currentColor"
|
||||
stroke_linecap: "round"
|
||||
stroke_linejoin: "round"
|
||||
stroke_width: "2"
|
||||
// class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full"
|
||||
viewBox: "0 0 24 24"
|
||||
class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full"
|
||||
view_box: "0 0 24 24"
|
||||
path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"}
|
||||
}
|
||||
))
|
||||
@@ -139,8 +139,8 @@ pub static RightArrowIcon: FC<()> = |(cx, props)| {
|
||||
stroke_linecap: "round"
|
||||
stroke_linejoin: "round"
|
||||
stroke_width: "2"
|
||||
// class: "w-4 h-4 ml-1"
|
||||
viewBox: "0 0 24 24"
|
||||
class: "w-4 h-4 ml-1"
|
||||
view_box: "0 0 24 24"
|
||||
path { d: "M5 12h14M12 5l7 7-7 7"}
|
||||
}
|
||||
))
|
||||
@@ -117,7 +117,7 @@ pub static StacksIcon: FC<()> = |(cx, props)| {
|
||||
stroke_linejoin: "round"
|
||||
stroke_width: "2"
|
||||
class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full"
|
||||
viewBox: "0 0 24 24"
|
||||
view_box: "0 0 24 24"
|
||||
path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"}
|
||||
}
|
||||
))
|
||||
@@ -131,7 +131,7 @@ pub static RightArrowIcon: FC<()> = |(cx, props)| {
|
||||
stroke_linejoin: "round"
|
||||
stroke_width: "2"
|
||||
class: "w-4 h-4 ml-1"
|
||||
viewBox: "0 0 24 24"
|
||||
view_box: "0 0 24 24"
|
||||
path { d: "M5 12h14M12 5l7 7-7 7"}
|
||||
}
|
||||
))
|
||||
|
||||
+6
-6
@@ -23,9 +23,9 @@ pub struct TodoItem {
|
||||
|
||||
const STYLE: &str = include_str!("./assets/todomvc.css");
|
||||
const App: FC<()> = |(cx, props)| {
|
||||
let draft = use_state(cx, || "".to_string());
|
||||
let todos = use_state(cx, || HashMap::<u32, Rc<TodoItem>>::new());
|
||||
let filter = use_state(cx, || FilterState::All);
|
||||
let mut draft = use_state(cx, || "".to_string());
|
||||
let mut todos = use_state(cx, || HashMap::<u32, Rc<TodoItem>>::new());
|
||||
let mut filter = use_state(cx, || FilterState::All);
|
||||
|
||||
let todolist = todos
|
||||
.iter()
|
||||
@@ -85,9 +85,9 @@ pub struct TodoEntryProps {
|
||||
todo: Rc<TodoItem>,
|
||||
}
|
||||
|
||||
pub fn TodoEntry((cx, props): Component<TodoEntryProps>) -> DomTree {
|
||||
let is_editing = use_state(cx, || false);
|
||||
let contents = use_state(cx, || String::from(""));
|
||||
pub fn TodoEntry((cx, props): Scope<TodoEntryProps>) -> Element {
|
||||
let mut is_editing = use_state(cx, || false);
|
||||
let mut contents = use_state(cx, || String::from(""));
|
||||
let todo = &props.todo;
|
||||
|
||||
rsx!(cx, li {
|
||||
|
||||
@@ -26,7 +26,7 @@ static App: FC<()> = |(cx, props)| {
|
||||
},
|
||||
|cx, props| {
|
||||
//
|
||||
rsx!(cx, WeatherDisplay {})
|
||||
rsx!(WeatherDisplay {})
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
File renamed without changes.
@@ -68,13 +68,13 @@ static APP: FC<()> = |(cx, _)| {
|
||||
})
|
||||
};
|
||||
|
||||
fn render_bullets(cx: Context) -> DomTree {
|
||||
fn render_bullets(cx: Context) -> Element {
|
||||
rsx!(cx, div {
|
||||
"bite me"
|
||||
})
|
||||
}
|
||||
|
||||
fn render_list(cx: Context, count: usize) -> DomTree {
|
||||
fn render_list(cx: Context, count: usize) -> Element {
|
||||
let items = (0..count).map(|f| {
|
||||
rsx! {
|
||||
li { "a - {f}" }
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -45,7 +45,7 @@ struct RowProps {
|
||||
row_id: usize,
|
||||
label: Label,
|
||||
}
|
||||
fn Row((cx, props): Component<RowProps>) -> DomTree {
|
||||
fn Row((cx, props): Scope<RowProps>) -> Element {
|
||||
let [adj, col, noun] = props.label.0;
|
||||
cx.render(rsx! {
|
||||
tr {
|
||||
|
||||
@@ -22,7 +22,6 @@ static App: FC<()> = |(cx, props)| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
h1 { "Hifive counter: {count}" }
|
||||
{cx.children()}
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ use syn::parse_macro_input;
|
||||
pub(crate) mod htm;
|
||||
pub(crate) mod ifmt;
|
||||
pub(crate) mod props;
|
||||
pub(crate) mod router;
|
||||
pub(crate) mod rsx;
|
||||
|
||||
#[proc_macro]
|
||||
@@ -82,12 +83,9 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
|
||||
/// }}
|
||||
///
|
||||
/// // Matching
|
||||
/// // Matching will throw a Rust error about "no two closures are the same type"
|
||||
/// // To fix this, call "render" method or use the "in" syntax to produce VNodes.
|
||||
/// // There's nothing we can do about it, sorry :/ (unless you want *really* unhygenic macros)
|
||||
/// {match true {
|
||||
/// true => rsx!(cx, h1 {"Top text"}),
|
||||
/// false => cx.render(rsx!( h1 {"Bottom text"}))
|
||||
/// true => rsx!(h1 {"Top text"}),
|
||||
/// false => rsx!(h1 {"Bottom text"})
|
||||
/// }}
|
||||
///
|
||||
/// // Conditional rendering
|
||||
@@ -95,11 +93,11 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
|
||||
/// // You can convert a bool condition to rsx! with .then and .or
|
||||
/// {true.then(|| rsx!(div {}))}
|
||||
///
|
||||
/// // True conditions need to be rendered (same reasons as matching)
|
||||
/// // True conditions
|
||||
/// {if true {
|
||||
/// rsx!(cx, h1 {"Top text"})
|
||||
/// rsx!(h1 {"Top text"})
|
||||
/// } else {
|
||||
/// rsx!(cx, h1 {"Bottom text"})
|
||||
/// rsx!(h1 {"Bottom text"})
|
||||
/// }}
|
||||
///
|
||||
/// // returning "None" is a bit noisy... but rare in practice
|
||||
@@ -186,3 +184,31 @@ pub fn rsx(s: TokenStream) -> TokenStream {
|
||||
Ok(s) => s.to_token_stream().into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Derive macro used to mark an enum as Routable.
|
||||
///
|
||||
/// This macro can only be used on enums. Every varient of the macro needs to be marked
|
||||
/// with the `at` attribute to specify the URL of the route. It generates an implementation of
|
||||
/// `yew_router::Routable` trait and `const`s for the routes passed which are used with `Route`
|
||||
/// component.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use yew_router::Routable;
|
||||
/// #[derive(Debug, Clone, Copy, PartialEq, Routable)]
|
||||
/// enum Routes {
|
||||
/// #[at("/")]
|
||||
/// Home,
|
||||
/// #[at("/secure")]
|
||||
/// Secure,
|
||||
/// #[at("/404")]
|
||||
/// NotFound,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(Routable, attributes(at, not_found))]
|
||||
pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
use router::{routable_derive_impl, Routable};
|
||||
let input = parse_macro_input!(input as Routable);
|
||||
routable_derive_impl(input).into()
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::spanned::Spanned;
|
||||
use syn::{Data, DeriveInput, Fields, Ident, LitStr, Variant};
|
||||
|
||||
const AT_ATTR_IDENT: &str = "at";
|
||||
const NOT_FOUND_ATTR_IDENT: &str = "not_found";
|
||||
|
||||
pub struct Routable {
|
||||
ident: Ident,
|
||||
ats: Vec<LitStr>,
|
||||
variants: Punctuated<Variant, syn::token::Comma>,
|
||||
not_found_route: Option<Ident>,
|
||||
}
|
||||
|
||||
impl Parse for Routable {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let DeriveInput { ident, data, .. } = input.parse()?;
|
||||
|
||||
let data = match data {
|
||||
Data::Enum(data) => data,
|
||||
Data::Struct(s) => {
|
||||
return Err(syn::Error::new(
|
||||
s.struct_token.span(),
|
||||
"expected enum, found struct",
|
||||
))
|
||||
}
|
||||
Data::Union(u) => {
|
||||
return Err(syn::Error::new(
|
||||
u.union_token.span(),
|
||||
"expected enum, found union",
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let (not_found_route, ats) = parse_variants_attributes(&data.variants)?;
|
||||
|
||||
Ok(Self {
|
||||
ident,
|
||||
variants: data.variants,
|
||||
ats,
|
||||
not_found_route,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_variants_attributes(
|
||||
variants: &Punctuated<Variant, syn::token::Comma>,
|
||||
) -> syn::Result<(Option<Ident>, Vec<LitStr>)> {
|
||||
let mut not_founds = vec![];
|
||||
let mut ats: Vec<LitStr> = vec![];
|
||||
|
||||
let mut not_found_attrs = vec![];
|
||||
|
||||
for variant in variants.iter() {
|
||||
if let Fields::Unnamed(ref field) = variant.fields {
|
||||
return Err(syn::Error::new(
|
||||
field.span(),
|
||||
"only named fields are supported",
|
||||
));
|
||||
}
|
||||
|
||||
let attrs = &variant.attrs;
|
||||
let at_attrs = attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path.is_ident(AT_ATTR_IDENT))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let attr = match at_attrs.len() {
|
||||
1 => *at_attrs.first().unwrap(),
|
||||
0 => {
|
||||
return Err(syn::Error::new(
|
||||
variant.span(),
|
||||
format!(
|
||||
"{} attribute must be present on every variant",
|
||||
AT_ATTR_IDENT
|
||||
),
|
||||
))
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new_spanned(
|
||||
quote! { #(#at_attrs)* },
|
||||
format!("only one {} attribute must be present", AT_ATTR_IDENT),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let lit = attr.parse_args::<LitStr>()?;
|
||||
ats.push(lit);
|
||||
|
||||
for attr in attrs.iter() {
|
||||
if attr.path.is_ident(NOT_FOUND_ATTR_IDENT) {
|
||||
not_found_attrs.push(attr);
|
||||
not_founds.push(variant.ident.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if not_founds.len() > 1 {
|
||||
return Err(syn::Error::new_spanned(
|
||||
quote! { #(#not_found_attrs)* },
|
||||
format!("there can only be one {}", NOT_FOUND_ATTR_IDENT),
|
||||
));
|
||||
}
|
||||
|
||||
Ok((not_founds.into_iter().next(), ats))
|
||||
}
|
||||
|
||||
impl Routable {
|
||||
fn build_from_path(&self) -> TokenStream {
|
||||
let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
|
||||
let ident = &variant.ident;
|
||||
let right = match &variant.fields {
|
||||
Fields::Unit => quote! { Self::#ident },
|
||||
Fields::Named(field) => {
|
||||
let fields = field.named.iter().map(|it| {
|
||||
//named fields have idents
|
||||
it.ident.as_ref().unwrap()
|
||||
});
|
||||
quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } }
|
||||
}
|
||||
Fields::Unnamed(_) => unreachable!(), // already checked
|
||||
};
|
||||
|
||||
let left = self.ats.get(i).unwrap();
|
||||
quote! {
|
||||
#left => ::std::option::Option::Some(#right)
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option<Self> {
|
||||
match path {
|
||||
#(#from_path_matches),*,
|
||||
_ => ::std::option::Option::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_to_path(&self) -> TokenStream {
|
||||
let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
|
||||
let ident = &variant.ident;
|
||||
let mut right = self.ats.get(i).unwrap().value();
|
||||
|
||||
match &variant.fields {
|
||||
Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) },
|
||||
Fields::Named(field) => {
|
||||
let fields = field
|
||||
.named
|
||||
.iter()
|
||||
.map(|it| it.ident.as_ref().unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for field in fields.iter() {
|
||||
// :param -> {param}
|
||||
// so we can pass it to `format!("...", param)`
|
||||
right = right.replace(&format!(":{}", field), &format!("{{{}}}", field))
|
||||
}
|
||||
|
||||
quote! {
|
||||
Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*)
|
||||
}
|
||||
}
|
||||
Fields::Unnamed(_) => unreachable!(), // already checked
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
fn to_path(&self) -> ::std::string::String {
|
||||
match self {
|
||||
#(#to_path_matches),*,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn routable_derive_impl(input: Routable) -> TokenStream {
|
||||
let Routable {
|
||||
ats,
|
||||
not_found_route,
|
||||
ident,
|
||||
..
|
||||
} = &input;
|
||||
|
||||
let from_path = input.build_from_path();
|
||||
let to_path = input.build_to_path();
|
||||
|
||||
let not_found_route = match not_found_route {
|
||||
Some(route) => quote! { ::std::option::Option::Some(Self::#route) },
|
||||
None => quote! { ::std::option::Option::None },
|
||||
};
|
||||
|
||||
let cache_thread_local_ident = Ident::new(
|
||||
&format!("__{}_ROUTER_CURRENT_ROUTE_CACHE", ident),
|
||||
ident.span(),
|
||||
);
|
||||
|
||||
quote! {
|
||||
::std::thread_local! {
|
||||
#[doc(hidden)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None);
|
||||
}
|
||||
|
||||
#[automatically_derived]
|
||||
impl ::dioxus::router::Routable for #ident {
|
||||
#from_path
|
||||
#to_path
|
||||
|
||||
fn routes() -> ::std::vec::Vec<&'static str> {
|
||||
::std::vec![#(#ats),*]
|
||||
}
|
||||
|
||||
fn not_found_route() -> ::std::option::Option<Self> {
|
||||
#not_found_route
|
||||
}
|
||||
|
||||
fn current_route() -> ::std::option::Option<Self> {
|
||||
#cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow()))
|
||||
}
|
||||
|
||||
fn recognize(pathname: &str) -> ::std::option::Option<Self> {
|
||||
::std::thread_local! {
|
||||
static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
|
||||
}
|
||||
let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
|
||||
{
|
||||
let route = ::std::clone::Clone::clone(&route);
|
||||
#cache_thread_local_ident.with(move |val| {
|
||||
*val.borrow_mut() = route;
|
||||
});
|
||||
}
|
||||
route
|
||||
}
|
||||
|
||||
fn cleanup() {
|
||||
#cache_thread_local_ident.with(move |val| {
|
||||
*val.borrow_mut() = ::std::option::Option::None;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,19 +49,19 @@ impl ToTokens for CallBody {
|
||||
match &self.custom_context {
|
||||
// The `in cx` pattern allows directly rendering
|
||||
Some(ident) => out_tokens.append_all(quote! {
|
||||
#ident.render(dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
|
||||
#ident.render(NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
|
||||
#inner
|
||||
}))
|
||||
}),
|
||||
// Otherwise we just build the LazyNode wrapper
|
||||
None => out_tokens.append_all(quote! {
|
||||
dioxus::prelude::LazyNodes::new(move |__cx: NodeFactory|{
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
|
||||
#inner
|
||||
})
|
||||
{
|
||||
NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode {
|
||||
use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
||||
#inner
|
||||
})
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -161,12 +161,22 @@ impl ToTokens for Component {
|
||||
None => {
|
||||
let mut toks = quote! { fc_to_builder(#name) };
|
||||
for field in &self.body {
|
||||
if field.name == "key" {
|
||||
has_key = Some(field);
|
||||
} else {
|
||||
toks.append_all(quote! {#field})
|
||||
match field.name.to_string().as_str() {
|
||||
"key" => {
|
||||
//
|
||||
has_key = Some(field);
|
||||
}
|
||||
_ => toks.append_all(quote! {#field}),
|
||||
}
|
||||
}
|
||||
|
||||
if !self.children.is_empty() {
|
||||
let childs = &self.children;
|
||||
toks.append_all(quote! {
|
||||
.children(ScopeChildren::new(__cx.fragment_from_iter([ #( #childs ),* ])))
|
||||
});
|
||||
}
|
||||
|
||||
toks.append_all(quote! {
|
||||
.build()
|
||||
});
|
||||
@@ -182,17 +192,13 @@ impl ToTokens for Component {
|
||||
None => quote! { None },
|
||||
};
|
||||
|
||||
let childs = &self.children;
|
||||
let children = quote! {
|
||||
[ #( #childs ),* ]
|
||||
};
|
||||
// #children
|
||||
|
||||
tokens.append_all(quote! {
|
||||
__cx.component(
|
||||
#name,
|
||||
#builder,
|
||||
#key_token,
|
||||
#children
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
Vendored
-3
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"rust-analyzer.inlayHints.enable": true
|
||||
}
|
||||
@@ -22,7 +22,9 @@ longest-increasing-subsequence = "0.1.0"
|
||||
# internall used
|
||||
log = { version = "0.4", features = ["release_max_level_off"] }
|
||||
|
||||
futures-util = { version = "0.3.15", no_default_features = true }
|
||||
futures-util = { version = "0.3.15", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
smallvec = "1.6.1"
|
||||
|
||||
@@ -35,7 +37,7 @@ once_cell = "1.8.0"
|
||||
|
||||
indexmap = "1.7.0"
|
||||
|
||||
# # Serialize the Edits for use in Webview/Liveview instances
|
||||
# Serialize the Edits for use in Webview/Liveview instances
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
serde_repr = { version = "0.1.7", optional = true }
|
||||
@@ -47,12 +49,13 @@ fern = { version = "0.6.0", features = ["colored"] }
|
||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||
simple_logger = "1.13.0"
|
||||
dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
|
||||
async-std = { version = "1.9.0", features = ["attributes"] }
|
||||
criterion = "0.3.5"
|
||||
# async-std = { version = "1.9.0", features = ["attributes"] }
|
||||
# criterion = "0.3.5"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
serialize = ["serde", "serde_repr"]
|
||||
debug_vdom = []
|
||||
|
||||
[[bench]]
|
||||
name = "create"
|
||||
|
||||
@@ -19,6 +19,7 @@ use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use rand::prelude::*;
|
||||
|
||||
fn main() {}
|
||||
criterion_group!(mbenches, create_rows);
|
||||
criterion_main!(mbenches);
|
||||
|
||||
@@ -57,7 +58,7 @@ struct RowProps {
|
||||
row_id: usize,
|
||||
label: Label,
|
||||
}
|
||||
fn Row((cx, props): Component<RowProps>) -> DomTree {
|
||||
fn Row((cx, props): Scope<RowProps>) -> Element {
|
||||
let [adj, col, noun] = props.label.0;
|
||||
cx.render(rsx! {
|
||||
tr {
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
fn main() {}
|
||||
|
||||
fn app<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> {
|
||||
let vak = use_suspense(
|
||||
cx,
|
||||
|| async {},
|
||||
|c, _res| c.render(LazyNodes::new(move |f| f.text(format_args!("")))),
|
||||
);
|
||||
|
||||
let d1 = cx.render(LazyNodes::new(move |f| {
|
||||
f.raw_element(
|
||||
"div",
|
||||
None,
|
||||
[],
|
||||
[],
|
||||
[
|
||||
f.fragment_from_iter(vak),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
],
|
||||
None,
|
||||
)
|
||||
}));
|
||||
|
||||
cx.render(LazyNodes::new(move |f| {
|
||||
f.raw_element(
|
||||
"div",
|
||||
None,
|
||||
[],
|
||||
[],
|
||||
[
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
d1.unwrap(),
|
||||
],
|
||||
None,
|
||||
)
|
||||
}))
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
use dioxus::component::Component;
|
||||
use dioxus::events::on::MouseEvent;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
fn main() {}
|
||||
|
||||
fn html_usage() {
|
||||
let mo = move |_| {};
|
||||
let r = rsx! {
|
||||
div {
|
||||
onclick: move |_| {}
|
||||
onmouseover: {mo}
|
||||
"type": "bar",
|
||||
"hello world"
|
||||
}
|
||||
};
|
||||
|
||||
let items = ["bob", "bill", "jack"];
|
||||
|
||||
let f = items.iter().filter(|f| f.starts_with("b")).map(|f| {
|
||||
rsx! {
|
||||
"hello {f}"
|
||||
}
|
||||
});
|
||||
|
||||
let p = rsx! {
|
||||
div {
|
||||
{f}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
static App: FC<()> = |(cx, props)| cx.render(LazyNodes::new(|f| f.text(format_args!("hello"))));
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
dom.rebuild();
|
||||
|
||||
let deadline = async_std::task::sleep(Duration::from_millis(50));
|
||||
|
||||
// let _fut = dom.run_with_deadline(|| deadline.);
|
||||
}
|
||||
@@ -21,16 +21,27 @@ pub(crate) struct BumpFrame {
|
||||
|
||||
impl ActiveFrame {
|
||||
pub fn new() -> Self {
|
||||
let b1 = Bump::new();
|
||||
let b2 = Bump::new();
|
||||
|
||||
let frame_a = BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: NodeFactory::unstable_place_holder(),
|
||||
head_node: VNode::Fragment(VFragment {
|
||||
key: None,
|
||||
children: &[],
|
||||
is_static: false,
|
||||
}),
|
||||
bump: b1,
|
||||
|
||||
#[cfg(test)]
|
||||
_name: "wip",
|
||||
};
|
||||
let frame_b = BumpFrame {
|
||||
bump: Bump::new(),
|
||||
head_node: NodeFactory::unstable_place_holder(),
|
||||
head_node: VNode::Fragment(VFragment {
|
||||
key: None,
|
||||
children: &[],
|
||||
is_static: false,
|
||||
}),
|
||||
bump: b2,
|
||||
|
||||
#[cfg(test)]
|
||||
_name: "fin",
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
//! if the type supports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
||||
//! that ensures compile-time required and optional fields on cx.
|
||||
|
||||
use crate::innerlude::{Context, DomTree, LazyNodes, FC};
|
||||
|
||||
use crate::{
|
||||
innerlude::{Context, Element, VAnchor, VFragment, VNode},
|
||||
LazyNodes, ScopeChildren,
|
||||
};
|
||||
/// A component is a wrapper around a Context and some Props that share a lifetime
|
||||
///
|
||||
///
|
||||
@@ -16,7 +18,7 @@ use crate::innerlude::{Context, DomTree, LazyNodes, FC};
|
||||
/// ```rust
|
||||
/// struct State {}
|
||||
///
|
||||
/// fn Example((cx, props): Component<State>) -> DomTree {
|
||||
/// fn Example((cx, props): Scope<State>) -> DomTree {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
@@ -27,7 +29,7 @@ use crate::innerlude::{Context, DomTree, LazyNodes, FC};
|
||||
/// name: &'a str
|
||||
/// }
|
||||
///
|
||||
/// fn Example<'a>((cx, props): Component<'a, State>) -> DomTree<'a> {
|
||||
/// fn Example<'a>((cx, props): Scope<'a, State>) -> DomTree<'a> {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
@@ -39,7 +41,44 @@ use crate::innerlude::{Context, DomTree, LazyNodes, FC};
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
pub type Component<'a, T> = (Context<'a>, &'a T);
|
||||
pub type Scope<'a, T> = (Context<'a>, &'a T);
|
||||
|
||||
pub struct FragmentProps<'a> {
|
||||
children: ScopeChildren<'a>,
|
||||
}
|
||||
|
||||
pub struct FragmentBuilder<'a, const BUILT: bool> {
|
||||
children: Option<ScopeChildren<'a>>,
|
||||
}
|
||||
impl<'a> FragmentBuilder<'a, false> {
|
||||
pub fn children(mut self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
|
||||
FragmentBuilder {
|
||||
children: Some(children),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, const A: bool> FragmentBuilder<'a, A> {
|
||||
pub fn build(self) -> FragmentProps<'a> {
|
||||
FragmentProps {
|
||||
children: self.children.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Properties for FragmentProps<'a> {
|
||||
type Builder = FragmentBuilder<'a, false>;
|
||||
|
||||
const IS_STATIC: bool = false;
|
||||
|
||||
fn builder() -> Self::Builder {
|
||||
FragmentBuilder { children: None }
|
||||
}
|
||||
|
||||
unsafe fn memoize(&self, other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Create inline fragments using Component syntax.
|
||||
///
|
||||
@@ -63,8 +102,10 @@ pub type Component<'a, T> = (Context<'a>, &'a T);
|
||||
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
///
|
||||
#[allow(non_upper_case_globals, non_snake_case)]
|
||||
pub fn Fragment((cx, _): Component<()>) -> DomTree {
|
||||
cx.render(LazyNodes::new(|f| f.fragment_from_iter(cx.children())))
|
||||
pub fn Fragment<'a>((cx, props): Scope<'a, FragmentProps<'a>>) -> Element<'a> {
|
||||
cx.render(Some(LazyNodes::new(|f| {
|
||||
f.fragment_from_iter(&props.children)
|
||||
})))
|
||||
}
|
||||
|
||||
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||
@@ -132,6 +173,6 @@ impl EmptyBuilder {
|
||||
|
||||
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
|
||||
/// to initialize a component's props.
|
||||
pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
||||
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder {
|
||||
T::builder()
|
||||
}
|
||||
@@ -4,7 +4,9 @@
|
||||
//!
|
||||
//! We unsafely implement `send` for the VirtualDom, but those guarantees can only be
|
||||
|
||||
use crate::innerlude::*;
|
||||
use bumpalo::Bump;
|
||||
|
||||
use crate::{innerlude::*, lazynodes::LazyNodes};
|
||||
use std::{any::TypeId, ops::Deref, rc::Rc};
|
||||
|
||||
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
||||
@@ -32,7 +34,7 @@ use std::{any::TypeId, ops::Deref, rc::Rc};
|
||||
/// }
|
||||
/// ```
|
||||
pub struct Context<'src> {
|
||||
pub scope: &'src Scope,
|
||||
pub scope: &'src ScopeInner,
|
||||
}
|
||||
|
||||
impl<'src> Copy for Context<'src> {}
|
||||
@@ -45,56 +47,13 @@ impl<'src> Clone for Context<'src> {
|
||||
// We currently deref to props, but it might make more sense to deref to Scope?
|
||||
// This allows for code that takes cx.xyz instead of cx.props.xyz
|
||||
impl<'a> Deref for Context<'a> {
|
||||
type Target = &'a Scope;
|
||||
type Target = &'a ScopeInner;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.scope
|
||||
}
|
||||
}
|
||||
|
||||
impl<'src> Context<'src> {
|
||||
/// Access the children elements passed into the component
|
||||
///
|
||||
/// This enables patterns where a component is passed children from its parent.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
|
||||
/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
|
||||
/// on the props that takes Context.
|
||||
///
|
||||
/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
|
||||
/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
|
||||
/// props are valid for the static lifetime.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// const App: FC<()> = |(cx, props)|{
|
||||
/// cx.render(rsx!{
|
||||
/// CustomCard {
|
||||
/// h1 {}
|
||||
/// p {}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// const CustomCard: FC<()> = |(cx, props)|{
|
||||
/// cx.render(rsx!{
|
||||
/// div {
|
||||
/// h1 {"Title card"}
|
||||
/// {cx.children()}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Notes:
|
||||
///
|
||||
/// This method returns a "ScopeChildren" object. This object is copy-able and preserve the correct lifetime.
|
||||
pub fn children(&self) -> ScopeChildren<'src> {
|
||||
self.scope.child_nodes()
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
///
|
||||
/// ## Notice: you should prefer using prepare_update and get_scope_id
|
||||
@@ -106,6 +65,10 @@ impl<'src> Context<'src> {
|
||||
(self.scope.memoized_updater)()
|
||||
}
|
||||
|
||||
pub fn needs_update_any(&self, id: ScopeId) {
|
||||
(self.scope.shared.schedule_any_immediate)(id)
|
||||
}
|
||||
|
||||
/// Schedule an update for any component given its ScopeId.
|
||||
///
|
||||
/// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
|
||||
@@ -122,6 +85,11 @@ impl<'src> Context<'src> {
|
||||
self.scope.our_arena_idx
|
||||
}
|
||||
|
||||
pub fn bump(&self) -> &'src Bump {
|
||||
let bump = &self.scope.frames.wip_frame().bump;
|
||||
bump
|
||||
}
|
||||
|
||||
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
|
||||
///
|
||||
/// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
|
||||
@@ -137,12 +105,10 @@ impl<'src> Context<'src> {
|
||||
/// cx.render(lazy_tree)
|
||||
/// }
|
||||
///```
|
||||
pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
|
||||
self,
|
||||
lazy_nodes: LazyNodes<'src, F>,
|
||||
) -> DomTree<'src> {
|
||||
pub fn render(self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<VNode<'src>> {
|
||||
let bump = &self.scope.frames.wip_frame().bump;
|
||||
Some(lazy_nodes.into_vnode(NodeFactory { bump }))
|
||||
let factory = NodeFactory { bump };
|
||||
lazy_nodes.map(|f| f.call(factory))
|
||||
}
|
||||
|
||||
/// `submit_task` will submit the future to be polled.
|
||||
@@ -166,12 +132,11 @@ impl<'src> Context<'src> {
|
||||
(self.scope.shared.submit_task)(task)
|
||||
}
|
||||
|
||||
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
|
||||
/// This method enables the ability to expose state to children further down the VirtualDOM Tree.
|
||||
///
|
||||
/// This is a hook, so it should not be called conditionally!
|
||||
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
||||
///
|
||||
/// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
|
||||
/// so don't put it in a conditional.
|
||||
/// For a hook that provides the same functionality, use `use_provide_state` and `use_consume_state` instead.
|
||||
///
|
||||
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
|
||||
/// the context via Rc/Weak.
|
||||
@@ -182,7 +147,7 @@ impl<'src> Context<'src> {
|
||||
/// struct SharedState(&'static str);
|
||||
///
|
||||
/// static App: FC<()> = |(cx, props)|{
|
||||
/// cx.provide_state(SharedState("world"));
|
||||
/// cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {});
|
||||
/// rsx!(cx, Child {})
|
||||
/// }
|
||||
///
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
use crate::{innerlude::ScopeInner, virtual_dom::VirtualDom, VNode};
|
||||
|
||||
impl std::fmt::Display for VirtualDom {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let base = self.base_scope();
|
||||
let root = base.root_node();
|
||||
|
||||
let renderer = ScopeRenderer {
|
||||
show_fragments: false,
|
||||
skip_components: false,
|
||||
|
||||
_scope: base,
|
||||
_pre_render: false,
|
||||
_newline: true,
|
||||
_indent: true,
|
||||
_max_depth: usize::MAX,
|
||||
};
|
||||
|
||||
renderer.render(self, root, f, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/// render the scope to a string using the rsx! syntax
|
||||
pub(crate) struct ScopeRenderer<'a> {
|
||||
pub skip_components: bool,
|
||||
pub show_fragments: bool,
|
||||
pub _scope: &'a ScopeInner,
|
||||
pub _pre_render: bool,
|
||||
pub _newline: bool,
|
||||
pub _indent: bool,
|
||||
pub _max_depth: usize,
|
||||
}
|
||||
|
||||
// this is more or less a debug tool, but it'll render the entire tree to the terminal
|
||||
impl<'a> ScopeRenderer<'a> {
|
||||
pub fn render(
|
||||
&self,
|
||||
vdom: &VirtualDom,
|
||||
node: &VNode,
|
||||
f: &mut std::fmt::Formatter,
|
||||
il: u16,
|
||||
) -> std::fmt::Result {
|
||||
const INDENT: &str = " ";
|
||||
let write_indent = |_f: &mut std::fmt::Formatter, le| {
|
||||
for _ in 0..le {
|
||||
write!(_f, "{}", INDENT).unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
match &node {
|
||||
VNode::Text(text) => {
|
||||
write_indent(f, il);
|
||||
writeln!(f, "\"{}\"", text.text)?
|
||||
}
|
||||
VNode::Anchor(_anchor) => {
|
||||
write_indent(f, il);
|
||||
writeln!(f, "Anchor {{}}")?;
|
||||
}
|
||||
VNode::Element(el) => {
|
||||
write_indent(f, il);
|
||||
writeln!(f, "{} {{", el.tag_name)?;
|
||||
// write!(f, "element: {}", el.tag_name)?;
|
||||
let mut attr_iter = el.attributes.iter().peekable();
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
match attr.namespace {
|
||||
None => {
|
||||
//
|
||||
write_indent(f, il + 1);
|
||||
writeln!(f, "{}: \"{}\"", attr.name, attr.value)?
|
||||
}
|
||||
|
||||
Some(ns) => {
|
||||
// write the opening tag
|
||||
write_indent(f, il + 1);
|
||||
write!(f, " {}:\"", ns)?;
|
||||
let mut cur_ns_el = attr;
|
||||
'ns_parse: loop {
|
||||
write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?;
|
||||
match attr_iter.peek() {
|
||||
Some(next_attr) if next_attr.namespace == Some(ns) => {
|
||||
cur_ns_el = attr_iter.next().unwrap();
|
||||
}
|
||||
_ => break 'ns_parse,
|
||||
}
|
||||
}
|
||||
// write the closing tag
|
||||
write!(f, "\"")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for child in el.children {
|
||||
self.render(vdom, child, f, il + 1)?;
|
||||
}
|
||||
write_indent(f, il);
|
||||
|
||||
writeln!(f, "}}")?;
|
||||
}
|
||||
VNode::Fragment(frag) => {
|
||||
if self.show_fragments {
|
||||
write_indent(f, il);
|
||||
writeln!(f, "Fragment {{")?;
|
||||
for child in frag.children {
|
||||
self.render(vdom, child, f, il + 1)?;
|
||||
}
|
||||
write_indent(f, il);
|
||||
writeln!(f, "}}")?;
|
||||
} else {
|
||||
for child in frag.children {
|
||||
self.render(vdom, child, f, il)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
VNode::Component(vcomp) => {
|
||||
let idx = vcomp.associated_scope.get().unwrap();
|
||||
if !self.skip_components {
|
||||
let new_node = vdom.get_scope(idx).unwrap().root_node();
|
||||
self.render(vdom, new_node, f, il)?;
|
||||
}
|
||||
}
|
||||
VNode::Suspended { .. } => {
|
||||
// we can't do anything with suspended nodes
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -342,13 +342,12 @@ impl<'bump> DiffMachine<'bump> {
|
||||
let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
|
||||
|
||||
let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
|
||||
Scope::new(
|
||||
ScopeInner::new(
|
||||
caller,
|
||||
new_idx,
|
||||
Some(parent_idx),
|
||||
parent_scope.height + 1,
|
||||
parent_scope.subtree(),
|
||||
ScopeChildren(vcomponent.children),
|
||||
shared,
|
||||
)
|
||||
});
|
||||
@@ -551,7 +550,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
|
||||
// make sure the component's caller function is up to date
|
||||
let scope = self.vdom.get_scope_mut(scope_addr).unwrap();
|
||||
scope.update_scope_dependencies(new.caller, ScopeChildren(new.children));
|
||||
scope.update_scope_dependencies(new.caller);
|
||||
|
||||
// React doesn't automatically memoize, but we do.
|
||||
let props_are_the_same = old.comparator.unwrap();
|
||||
@@ -1125,6 +1124,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
|
||||
self.remove_nodes(e.children, false);
|
||||
}
|
||||
|
||||
VNode::Fragment(f) => {
|
||||
self.remove_nodes(f.children, gen_muts);
|
||||
}
|
||||
@@ -1161,7 +1161,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
|
||||
/// Adds a listener closure to a scope during diff.
|
||||
fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &Scope) {
|
||||
fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &ScopeInner) {
|
||||
let mut queue = scope.listeners.borrow_mut();
|
||||
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
|
||||
queue.push(long_listener as *const _)
|
||||
|
||||
+37
-30
@@ -93,11 +93,11 @@ pub fn use_suspense<'src, Out, Fut, Cb>(
|
||||
cx: Context<'src>,
|
||||
task_initializer: impl FnOnce() -> Fut,
|
||||
user_callback: Cb,
|
||||
) -> DomTree<'src>
|
||||
) -> Element<'src>
|
||||
where
|
||||
Fut: Future<Output = Out> + 'static,
|
||||
Out: 'static,
|
||||
Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> DomTree<'a> + 'static,
|
||||
Cb: for<'a> Fn(SuspendedContext<'a>, &Out) -> Element<'a> + 'static,
|
||||
{
|
||||
/*
|
||||
General strategy:
|
||||
@@ -135,32 +135,35 @@ where
|
||||
None => {
|
||||
let value = hook.value.clone();
|
||||
|
||||
cx.render(LazyNodes::new(|f| {
|
||||
let bump = f.bump();
|
||||
let id = hook.handle.our_id;
|
||||
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
todo!()
|
||||
// Some(LazyNodes::new(move |f| {
|
||||
// let bump = f.bump();
|
||||
|
||||
let f: &mut dyn FnMut(SuspendedContext<'src>) -> DomTree<'src> =
|
||||
bump.alloc(move |sus| {
|
||||
let val = value.borrow();
|
||||
// use bumpalo::boxed::Box as BumpBox;
|
||||
|
||||
let out = val
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.downcast_ref::<Out>()
|
||||
.unwrap();
|
||||
// let f: &mut dyn FnMut(SuspendedContext<'src>) -> Element<'src> =
|
||||
// bump.alloc(move |sus| {
|
||||
// let val = value.borrow();
|
||||
|
||||
user_callback(sus, out)
|
||||
});
|
||||
let callback = unsafe { BumpBox::from_raw(f) };
|
||||
// let out = val
|
||||
// .as_ref()
|
||||
// .unwrap()
|
||||
// .as_ref()
|
||||
// .downcast_ref::<Out>()
|
||||
// .unwrap();
|
||||
|
||||
VNode::Suspended(bump.alloc(VSuspended {
|
||||
dom_id: empty_cell(),
|
||||
task_id: hook.handle.our_id,
|
||||
callback: RefCell::new(Some(callback)),
|
||||
}))
|
||||
}))
|
||||
// user_callback(sus, out)
|
||||
// });
|
||||
// let callback = unsafe { BumpBox::from_raw(f) };
|
||||
|
||||
// VNode::Suspended(bump.alloc(VSuspended {
|
||||
// dom_id: empty_cell(),
|
||||
// task_id: id,
|
||||
// callback: RefCell::new(Some(callback)),
|
||||
// }))
|
||||
// }))
|
||||
}
|
||||
},
|
||||
|_| {},
|
||||
@@ -177,13 +180,17 @@ pub struct SuspendedContext<'a> {
|
||||
}
|
||||
|
||||
impl<'src> SuspendedContext<'src> {
|
||||
pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
|
||||
self,
|
||||
lazy_nodes: LazyNodes<'src, F>,
|
||||
) -> DomTree<'src> {
|
||||
let bump = &self.inner.scope.frames.wip_frame().bump;
|
||||
Some(lazy_nodes.into_vnode(NodeFactory { bump }))
|
||||
}
|
||||
// // pub fn render(
|
||||
// pub fn render(
|
||||
// // pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
|
||||
// self,
|
||||
// lazy_nodes: LazyNodes<'_>,
|
||||
// // lazy_nodes: LazyNodes<'src, '_>,
|
||||
// ) -> Element<'src> {
|
||||
// let bump = &self.inner.scope.frames.wip_frame().bump;
|
||||
// todo!("suspense")
|
||||
// // Some(lazy_nodes.into_vnode(NodeFactory { bump }))
|
||||
// }
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
//! Support for storing lazy-nodes on the stack
|
||||
//!
|
||||
//! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls
|
||||
//! to `rsx!` more efficient.
|
||||
//!
|
||||
//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on NodeFactory closures.
|
||||
//!
|
||||
//! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
|
||||
//! we build a tiny alloactor in the stack and allocate the closure into that.
|
||||
//!
|
||||
//! The logic for this was borrowed from https://docs.rs/stack_dst/0.6.1/stack_dst/. Unfortunately, this crate does not
|
||||
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
|
||||
|
||||
use crate::prelude::{NodeFactory, VNode};
|
||||
use std::mem;
|
||||
|
||||
/// A concrete type provider for closures that build VNode structures.
|
||||
///
|
||||
/// This struct wraps lazy structs that build VNode trees Normally, we cannot perform a blanket implementation over
|
||||
/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode.
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
|
||||
/// ```
|
||||
pub struct LazyNodes<'a, 'b> {
|
||||
inner: StackNodeStorage<'a, 'b>,
|
||||
}
|
||||
|
||||
type StackHeapSize = [usize; 0];
|
||||
|
||||
enum StackNodeStorage<'a, 'b> {
|
||||
Stack(LazyStack),
|
||||
Heap(Box<dyn FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b>),
|
||||
}
|
||||
|
||||
impl<'a, 'b> LazyNodes<'a, 'b> {
|
||||
pub fn new<F>(val: F) -> Self
|
||||
where
|
||||
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
|
||||
{
|
||||
unsafe {
|
||||
let mut ptr: *const _ = &val as &dyn FnOnce(NodeFactory<'a>) -> VNode<'a>;
|
||||
|
||||
assert_eq!(
|
||||
ptr as *const u8, &val as *const _ as *const u8,
|
||||
"MISUSE: Closure returned different pointer"
|
||||
);
|
||||
assert_eq!(
|
||||
std::mem::size_of_val(&*ptr),
|
||||
std::mem::size_of::<F>(),
|
||||
"MISUSE: Closure returned a subset pointer"
|
||||
);
|
||||
let words = ptr_as_slice(&mut ptr);
|
||||
assert!(
|
||||
words[0] == &val as *const _ as usize,
|
||||
"BUG: Pointer layout is not (data_ptr, info...)"
|
||||
);
|
||||
|
||||
// - Ensure that Self is aligned same as data requires
|
||||
assert!(
|
||||
std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
|
||||
"TODO: Enforce alignment >{} (requires {})",
|
||||
std::mem::align_of::<Self>(),
|
||||
std::mem::align_of::<F>()
|
||||
);
|
||||
|
||||
let info = &words[1..];
|
||||
let data = words[0] as *mut ();
|
||||
let size = mem::size_of::<F>();
|
||||
|
||||
if info.len() * mem::size_of::<usize>() + size > mem::size_of::<StackHeapSize>() {
|
||||
log::debug!("lazy nodes was too large to fit into stack. falling back to heap");
|
||||
|
||||
Self {
|
||||
inner: StackNodeStorage::Heap(Box::new(val)),
|
||||
}
|
||||
} else {
|
||||
log::debug!("lazy nodes fits on stack!");
|
||||
let mut buf: StackHeapSize = StackHeapSize::default();
|
||||
|
||||
assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
|
||||
|
||||
// Place pointer information at the end of the region
|
||||
// - Allows the data to be at the start for alignment purposes
|
||||
{
|
||||
let info_ofs = buf.as_ref().len() - info.len();
|
||||
let info_dst = &mut buf.as_mut()[info_ofs..];
|
||||
for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
|
||||
*d = *v;
|
||||
}
|
||||
}
|
||||
|
||||
let src_ptr = data as *const u8;
|
||||
let dataptr = buf.as_mut()[..].as_mut_ptr() as *mut u8;
|
||||
for i in 0..size {
|
||||
*dataptr.add(i) = *src_ptr.add(i);
|
||||
}
|
||||
|
||||
std::mem::forget(val);
|
||||
|
||||
Self {
|
||||
inner: StackNodeStorage::Stack(LazyStack { _align: [], buf }),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
|
||||
match self.inner {
|
||||
StackNodeStorage::Heap(lazy) => lazy(f),
|
||||
StackNodeStorage::Stack(stack) => stack.call(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LazyStack {
|
||||
_align: [u64; 0],
|
||||
buf: StackHeapSize,
|
||||
}
|
||||
|
||||
impl LazyStack {
|
||||
unsafe fn create_boxed<'a>(&mut self) -> Box<dyn FnOnce(NodeFactory<'a>) -> VNode<'a>> {
|
||||
let LazyStack { buf, .. } = self;
|
||||
let data = buf.as_ref();
|
||||
|
||||
let info_size = mem::size_of::<*mut dyn FnOnce(NodeFactory<'a>) -> VNode<'a>>()
|
||||
/ mem::size_of::<usize>()
|
||||
- 1;
|
||||
|
||||
let info_ofs = data.len() - info_size;
|
||||
|
||||
let g: *mut dyn FnOnce(NodeFactory<'a>) -> VNode<'a> =
|
||||
make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]);
|
||||
|
||||
Box::from_raw(g)
|
||||
}
|
||||
|
||||
fn call(mut self, f: NodeFactory) -> VNode {
|
||||
let boxed = unsafe { self.create_boxed() };
|
||||
boxed(f)
|
||||
}
|
||||
}
|
||||
impl Drop for LazyStack {
|
||||
fn drop(&mut self) {
|
||||
let boxed = unsafe { self.create_boxed() };
|
||||
mem::drop(boxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtain mutable access to a pointer's words
|
||||
fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
|
||||
assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
|
||||
let words = mem::size_of::<T>() / mem::size_of::<usize>();
|
||||
// SAFE: Points to valid memory (a raw pointer)
|
||||
unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
|
||||
}
|
||||
|
||||
/// Re-construct a fat pointer
|
||||
unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
|
||||
let mut rv = mem::MaybeUninit::<*mut T>::uninit();
|
||||
{
|
||||
let s = ptr_as_slice(&mut rv);
|
||||
s[0] = data_ptr;
|
||||
s[1..].copy_from_slice(meta_vals);
|
||||
}
|
||||
let rv = rv.assume_init();
|
||||
assert_eq!(rv as *const (), data_ptr as *const ());
|
||||
rv
|
||||
}
|
||||
|
||||
fn round_to_words(len: usize) -> usize {
|
||||
(len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let bump = bumpalo::Bump::new();
|
||||
|
||||
simple_logger::init();
|
||||
|
||||
let factory = NodeFactory { bump: &bump };
|
||||
|
||||
let caller = NodeFactory::annotate_lazy(|f| {
|
||||
//
|
||||
f.text(format_args!("hello world!"))
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let g = caller.call(factory);
|
||||
|
||||
dbg!(g);
|
||||
}
|
||||
@@ -19,10 +19,10 @@ pub mod context;
|
||||
pub mod diff;
|
||||
pub mod diff_stack;
|
||||
pub mod events;
|
||||
// pub mod events2;
|
||||
pub mod heuristics;
|
||||
pub mod hooklist;
|
||||
pub mod hooks;
|
||||
pub mod lazynodes;
|
||||
pub mod mutations;
|
||||
pub mod nodes;
|
||||
pub mod resources;
|
||||
@@ -34,6 +34,9 @@ pub mod threadsafe;
|
||||
pub mod util;
|
||||
pub mod virtual_dom;
|
||||
|
||||
#[cfg(feature = "debug_vdom")]
|
||||
pub mod debug_dom;
|
||||
|
||||
pub(crate) mod innerlude {
|
||||
pub(crate) use crate::bumpframe::*;
|
||||
pub(crate) use crate::childiter::*;
|
||||
@@ -45,6 +48,7 @@ pub(crate) mod innerlude {
|
||||
pub use crate::heuristics::*;
|
||||
pub(crate) use crate::hooklist::*;
|
||||
pub use crate::hooks::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::*;
|
||||
pub(crate) use crate::resources::*;
|
||||
@@ -56,21 +60,21 @@ pub(crate) mod innerlude {
|
||||
pub use crate::util::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
pub type DomTree<'a> = Option<VNode<'a>>;
|
||||
pub type FC<P> = for<'a> fn(Component<'a, P>) -> DomTree<'a>;
|
||||
pub type Element<'a> = Option<VNode<'a>>;
|
||||
pub type FC<P> = for<'a> fn(Scope<'a, P>) -> Element<'a>;
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority, LazyNodes, MountType,
|
||||
Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, SuspendedContext, TaskHandle,
|
||||
TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
|
||||
Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType,
|
||||
Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, SuspendedContext,
|
||||
TaskHandle, TestDom, ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
|
||||
};
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::component::{fc_to_builder, Component, Fragment, Properties};
|
||||
pub use crate::component::{fc_to_builder, Fragment, Properties, Scope};
|
||||
pub use crate::context::Context;
|
||||
pub use crate::hooks::*;
|
||||
pub use crate::innerlude::{DioxusElement, DomTree, LazyNodes, Mutations, NodeFactory, FC};
|
||||
pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, ScopeChildren, FC};
|
||||
pub use crate::nodes::VNode;
|
||||
pub use crate::VirtualDom;
|
||||
}
|
||||
|
||||
+181
-188
@@ -3,14 +3,17 @@
|
||||
//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very*
|
||||
//! cheap and *very* fast to construct - building a full tree should be quick.
|
||||
|
||||
use crate::innerlude::{
|
||||
empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, SuspendedContext, FC,
|
||||
use crate::{
|
||||
innerlude::{
|
||||
empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeInner,
|
||||
SuspendedContext,
|
||||
},
|
||||
lazynodes::LazyNodes,
|
||||
};
|
||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
fmt::{Arguments, Debug, Formatter},
|
||||
marker::PhantomData,
|
||||
};
|
||||
|
||||
/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
|
||||
@@ -33,7 +36,7 @@ pub enum VNode<'src> {
|
||||
/// assert_eq!(vtext.is_static, true);
|
||||
/// }
|
||||
/// ```
|
||||
Text(VText<'src>),
|
||||
Text(&'src VText<'src>),
|
||||
|
||||
/// Element VNodes are VNodes that may contain attributes, listeners, a key, a tag, and children.
|
||||
///
|
||||
@@ -116,7 +119,7 @@ pub enum VNode<'src> {
|
||||
/// assert_eq!(root, VNode::Anchor);
|
||||
/// }
|
||||
/// ```
|
||||
Anchor(VAnchor),
|
||||
Anchor(&'src VAnchor),
|
||||
}
|
||||
|
||||
impl<'src> VNode<'src> {
|
||||
@@ -152,6 +155,41 @@ impl<'src> VNode<'src> {
|
||||
VNode::Component(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Create an "owned" version of the vnode.
|
||||
pub fn decouple(&self) -> VNode<'src> {
|
||||
match self {
|
||||
VNode::Text(t) => VNode::Text(*t),
|
||||
VNode::Element(e) => VNode::Element(*e),
|
||||
VNode::Component(c) => VNode::Component(*c),
|
||||
VNode::Suspended(s) => VNode::Suspended(*s),
|
||||
VNode::Anchor(a) => VNode::Anchor(*a),
|
||||
VNode::Fragment(f) => VNode::Fragment(VFragment {
|
||||
children: f.children,
|
||||
key: f.key,
|
||||
is_static: f.is_static,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for VNode<'_> {
|
||||
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
match &self {
|
||||
VNode::Element(el) => s
|
||||
.debug_struct("VElement")
|
||||
.field("name", &el.tag_name)
|
||||
.field("key", &el.key)
|
||||
.finish(),
|
||||
|
||||
VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
|
||||
VNode::Anchor(_) => write!(s, "VAnchor"),
|
||||
|
||||
VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children),
|
||||
VNode::Suspended { .. } => write!(s, "VSuspended"),
|
||||
VNode::Component(comp) => write!(s, "VComponent {{ fc: {:?}}}", comp.user_fc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A placeholder node only generated when Fragments don't have any children.
|
||||
@@ -287,9 +325,7 @@ pub struct VComponent<'src> {
|
||||
// Function pointer to the FC that was used to generate this component
|
||||
pub user_fc: *const (),
|
||||
|
||||
pub(crate) caller: &'src dyn for<'b> Fn(&'b Scope) -> DomTree<'b>,
|
||||
|
||||
pub(crate) children: &'src [VNode<'src>],
|
||||
pub(crate) caller: &'src dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
|
||||
|
||||
pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
|
||||
|
||||
@@ -306,7 +342,7 @@ pub struct VSuspended<'a> {
|
||||
pub dom_id: Cell<Option<ElementId>>,
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub callback: RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> DomTree<'a>>>>,
|
||||
pub callback: RefCell<Option<BumpBox<'a, dyn FnMut(SuspendedContext<'a>) -> Element<'a>>>>,
|
||||
}
|
||||
|
||||
/// This struct provides an ergonomic API to quickly build VNodes.
|
||||
@@ -328,28 +364,13 @@ impl<'a> NodeFactory<'a> {
|
||||
self.bump
|
||||
}
|
||||
|
||||
pub fn render_directly<F>(&self, lazy_nodes: LazyNodes<'a, F>) -> DomTree<'a>
|
||||
where
|
||||
F: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
Some(lazy_nodes.into_vnode(NodeFactory { bump: self.bump }))
|
||||
}
|
||||
|
||||
pub fn unstable_place_holder() -> VNode<'static> {
|
||||
VNode::Text(VText {
|
||||
text: "",
|
||||
dom_id: empty_cell(),
|
||||
is_static: true,
|
||||
})
|
||||
}
|
||||
|
||||
/// Directly pass in text blocks without the need to use the format_args macro.
|
||||
pub fn static_text(&self, text: &'static str) -> VNode<'a> {
|
||||
VNode::Text(VText {
|
||||
VNode::Text(self.bump.alloc(VText {
|
||||
dom_id: empty_cell(),
|
||||
text,
|
||||
is_static: true,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
|
||||
@@ -372,11 +393,11 @@ impl<'a> NodeFactory<'a> {
|
||||
pub fn text(&self, args: Arguments) -> VNode<'a> {
|
||||
let (text, is_static) = self.raw_text(args);
|
||||
|
||||
VNode::Text(VText {
|
||||
VNode::Text(self.bump.alloc(VText {
|
||||
text,
|
||||
is_static,
|
||||
dom_id: empty_cell(),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn element<L, A, V>(
|
||||
@@ -456,20 +477,16 @@ impl<'a> NodeFactory<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn component<P, V>(
|
||||
pub fn component<P>(
|
||||
&self,
|
||||
component: FC<P>,
|
||||
component: fn(Scope<'a, P>) -> Element<'a>,
|
||||
props: P,
|
||||
key: Option<Arguments>,
|
||||
children: V,
|
||||
) -> VNode<'a>
|
||||
where
|
||||
P: Properties + 'a,
|
||||
V: 'a + AsRef<[VNode<'a>]>,
|
||||
{
|
||||
let bump = self.bump();
|
||||
let children: &'a V = bump.alloc(children);
|
||||
let children = children.as_ref();
|
||||
let props = bump.alloc(props);
|
||||
let raw_props = props as *mut P as *mut ();
|
||||
let user_fc = component as *const ();
|
||||
@@ -491,7 +508,7 @@ impl<'a> NodeFactory<'a> {
|
||||
|
||||
// It's only okay to memoize if there are no children and the props can be memoized
|
||||
// Implementing memoize is unsafe and done automatically with the props trait
|
||||
matches!((props_memoized, children.is_empty()), (true, true))
|
||||
props_memoized
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -522,36 +539,51 @@ impl<'a> NodeFactory<'a> {
|
||||
RefCell::new(Some(drop_props))
|
||||
};
|
||||
|
||||
let is_static = children.is_empty() && P::IS_STATIC && key.is_none();
|
||||
|
||||
let key = key.map(|f| self.raw_text(f).0);
|
||||
|
||||
let caller: &'a mut dyn for<'b> Fn(&'b Scope) -> DomTree<'b> =
|
||||
bump.alloc(move |scope: &Scope| -> DomTree {
|
||||
let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> =
|
||||
bump.alloc(move |scope: &ScopeInner| -> Element {
|
||||
log::debug!("calling component renderr {:?}", scope.our_arena_idx);
|
||||
let props: &'_ P = unsafe { &*(raw_props as *const P) };
|
||||
let res = component((Context { scope }, props));
|
||||
|
||||
let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) };
|
||||
let s: Scope<'a, P> = (Context { scope: scp }, props);
|
||||
|
||||
let res: Element = component(s);
|
||||
unsafe { std::mem::transmute(res) }
|
||||
});
|
||||
|
||||
let can_memoize = children.is_empty() && P::IS_STATIC;
|
||||
|
||||
VNode::Component(bump.alloc(VComponent {
|
||||
user_fc,
|
||||
comparator,
|
||||
raw_props,
|
||||
children,
|
||||
caller,
|
||||
is_static,
|
||||
is_static: P::IS_STATIC,
|
||||
key,
|
||||
can_memoize,
|
||||
can_memoize: P::IS_STATIC,
|
||||
drop_props,
|
||||
associated_scope: Cell::new(None),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn fragment_from_iter(self, node_iter: impl IntoVNodeList<'a>) -> VNode<'a> {
|
||||
let children = node_iter.into_vnode_list(self);
|
||||
pub fn fragment_from_iter(
|
||||
self,
|
||||
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
|
||||
) -> VNode<'a> {
|
||||
let bump = self.bump();
|
||||
let mut nodes = bumpalo::collections::Vec::new_in(bump);
|
||||
|
||||
for node in node_iter {
|
||||
nodes.push(node.into_vnode(self));
|
||||
}
|
||||
|
||||
if nodes.is_empty() {
|
||||
nodes.push(VNode::Anchor(bump.alloc(VAnchor {
|
||||
dom_id: empty_cell(),
|
||||
})));
|
||||
}
|
||||
|
||||
let children = nodes.into_bump_slice();
|
||||
|
||||
// TODO
|
||||
// We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning
|
||||
@@ -578,6 +610,19 @@ impl<'a> NodeFactory<'a> {
|
||||
is_static: false,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn annotate_lazy<'z, 'b, F>(f: F) -> Option<LazyNodes<'z, 'b>>
|
||||
where
|
||||
F: FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b,
|
||||
{
|
||||
Some(LazyNodes::new(f))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for NodeFactory<'_> {
|
||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait implementations for use in the rsx! and html! macros.
|
||||
@@ -599,73 +644,6 @@ pub trait IntoVNode<'a> {
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
|
||||
}
|
||||
|
||||
pub trait IntoVNodeList<'a> {
|
||||
fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>];
|
||||
}
|
||||
|
||||
impl<'a, T, V> IntoVNodeList<'a> for T
|
||||
where
|
||||
T: IntoIterator<Item = V>,
|
||||
V: IntoVNode<'a>,
|
||||
{
|
||||
fn into_vnode_list(self, cx: NodeFactory<'a>) -> &'a [VNode<'a>] {
|
||||
let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
|
||||
|
||||
for node in self.into_iter() {
|
||||
nodes.push(node.into_vnode(cx));
|
||||
}
|
||||
|
||||
if nodes.is_empty() {
|
||||
nodes.push(VNode::Anchor(VAnchor {
|
||||
dom_id: empty_cell(),
|
||||
}));
|
||||
}
|
||||
|
||||
nodes.into_bump_slice()
|
||||
}
|
||||
}
|
||||
|
||||
/// Child nodes of the parent component.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// let children = cx.children();
|
||||
/// let first_node = &children[0];
|
||||
/// rsx!{
|
||||
/// h1 { {first_node} }
|
||||
/// p { {&children[1..]} }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
pub struct ScopeChildren<'a>(pub &'a [VNode<'a>]);
|
||||
|
||||
impl Copy for ScopeChildren<'_> {}
|
||||
|
||||
impl<'a> Clone for ScopeChildren<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
ScopeChildren(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScopeChildren<'_> {
|
||||
// dangerous method - used to fix the associated lifetime
|
||||
pub(crate) unsafe fn extend_lifetime(self) -> ScopeChildren<'static> {
|
||||
std::mem::transmute(self)
|
||||
}
|
||||
|
||||
// dangerous method - used to fix the associated lifetime
|
||||
pub(crate) unsafe fn shorten_lifetime<'a>(self) -> ScopeChildren<'a> {
|
||||
std::mem::transmute(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoVNodeList<'a> for ScopeChildren<'a> {
|
||||
fn into_vnode_list(self, _: NodeFactory<'a>) -> &'a [VNode<'a>] {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
// For the case where a rendered VNode is passed into the rsx! macro through curly braces
|
||||
impl<'a> IntoIterator for VNode<'a> {
|
||||
type Item = VNode<'a>;
|
||||
@@ -675,64 +653,14 @@ impl<'a> IntoIterator for VNode<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// For the case where a rendered VNode is passed into the rsx! macro through curly braces
|
||||
// TODO: do we even need this? It almost seems better not to
|
||||
// // For the case where a rendered VNode is passed into the rsx! macro through curly braces
|
||||
impl<'a> IntoVNode<'a> for VNode<'a> {
|
||||
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A concrete type provider for closures that build VNode structures.
|
||||
///
|
||||
/// This struct wraps lazy structs that build VNode trees Normally, we cannot perform a blanket implementation over
|
||||
/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode.
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
|
||||
/// ```
|
||||
pub struct LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
inner: G,
|
||||
_p: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a, G> LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
pub fn new(f: G) -> Self {
|
||||
Self {
|
||||
inner: f,
|
||||
_p: PhantomData {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Our blanket impl
|
||||
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
(self.inner)(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// Our blanket impl
|
||||
impl<'a, G> IntoIterator for LazyNodes<'a, G>
|
||||
where
|
||||
G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
|
||||
{
|
||||
type Item = Self;
|
||||
type IntoIter = std::iter::Once<Self::Item>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
|
||||
// Conveniently, we also support "null" (nothing) passed in
|
||||
impl IntoVNode<'_> for () {
|
||||
fn into_vnode(self, cx: NodeFactory) -> VNode {
|
||||
@@ -748,50 +676,115 @@ impl IntoVNode<'_> for Option<()> {
|
||||
}
|
||||
|
||||
impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
self.unwrap_or_else(|| cx.fragment_from_iter(None as Option<VNode>))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
match self {
|
||||
Some(n) => n,
|
||||
None => cx.fragment_from_iter(None as Option<VNode>),
|
||||
Some(lazy) => lazy.call(cx),
|
||||
None => VNode::Fragment(VFragment {
|
||||
children: &[],
|
||||
is_static: false,
|
||||
key: None,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoVNode<'a> for LazyNodes<'a, '_> {
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
self.call(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoVNode<'_> for &'static str {
|
||||
fn into_vnode(self, cx: NodeFactory) -> VNode {
|
||||
cx.static_text(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoVNode<'_> for Arguments<'_> {
|
||||
fn into_vnode(self, cx: NodeFactory) -> VNode {
|
||||
cx.text(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for NodeFactory<'_> {
|
||||
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(())
|
||||
/// Access the children elements passed into the component
|
||||
///
|
||||
/// This enables patterns where a component is passed children from its parent.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
|
||||
/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
|
||||
/// on the props that takes Context.
|
||||
///
|
||||
/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
|
||||
/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
|
||||
/// props are valid for the static lifetime.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// const App: FC<()> = |(cx, props)|{
|
||||
/// cx.render(rsx!{
|
||||
/// CustomCard {
|
||||
/// h1 {}
|
||||
/// p {}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// const CustomCard: FC<()> = |(cx, props)|{
|
||||
/// cx.render(rsx!{
|
||||
/// div {
|
||||
/// h1 {"Title card"}
|
||||
/// {props.children}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Notes:
|
||||
///
|
||||
/// This method returns a "ScopeChildren" object. This object is copy-able and preserve the correct lifetime.
|
||||
pub struct ScopeChildren<'a> {
|
||||
root: Option<VNode<'a>>,
|
||||
}
|
||||
|
||||
impl Default for ScopeChildren<'_> {
|
||||
fn default() -> Self {
|
||||
Self { root: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for VNode<'_> {
|
||||
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
match &self {
|
||||
VNode::Element(el) => s
|
||||
.debug_struct("VElement")
|
||||
.field("name", &el.tag_name)
|
||||
.field("key", &el.key)
|
||||
.finish(),
|
||||
impl<'a> ScopeChildren<'a> {
|
||||
pub fn new(root: VNode<'a>) -> Self {
|
||||
Self { root: Some(root) }
|
||||
}
|
||||
pub fn new_option(root: Option<VNode<'a>>) -> Self {
|
||||
Self { root }
|
||||
}
|
||||
}
|
||||
|
||||
VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
|
||||
VNode::Anchor(_) => write!(s, "VAnchor"),
|
||||
impl IntoIterator for &ScopeChildren<'_> {
|
||||
type Item = Self;
|
||||
|
||||
VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children),
|
||||
VNode::Suspended { .. } => write!(s, "VSuspended"),
|
||||
VNode::Component(comp) => write!(
|
||||
s,
|
||||
"VComponent {{ fc: {:?}, children: {:?} }}",
|
||||
comp.user_fc, comp.children
|
||||
),
|
||||
type IntoIter = std::iter::Once<Self>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
std::iter::once(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoVNode<'a> for &ScopeChildren<'a> {
|
||||
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
|
||||
match &self.root {
|
||||
Some(n) => n.decouple(),
|
||||
None => cx.fragment_from_iter(None as Option<VNode>),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ pub(crate) struct ResourcePool {
|
||||
|
||||
Wrapped in Rc so the "get_shared_context" closure can walk the tree (immutably!)
|
||||
*/
|
||||
pub components: Rc<UnsafeCell<Slab<Scope>>>,
|
||||
pub components: Rc<UnsafeCell<Slab<ScopeInner>>>,
|
||||
|
||||
/*
|
||||
Yes, a slab of "nil". We use this for properly ordering ElementIDs - all we care about is the allocation strategy
|
||||
@@ -32,18 +32,18 @@ pub(crate) struct ResourcePool {
|
||||
|
||||
impl ResourcePool {
|
||||
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||
pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
|
||||
pub fn get_scope(&self, idx: ScopeId) -> Option<&ScopeInner> {
|
||||
let inner = unsafe { &*self.components.get() };
|
||||
inner.get(idx.0)
|
||||
}
|
||||
|
||||
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||
pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
|
||||
pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut ScopeInner> {
|
||||
let inner = unsafe { &mut *self.components.get() };
|
||||
inner.get_mut(idx.0)
|
||||
}
|
||||
|
||||
pub fn try_remove(&self, id: ScopeId) -> Option<Scope> {
|
||||
pub fn try_remove(&self, id: ScopeId) -> Option<ScopeInner> {
|
||||
let inner = unsafe { &mut *self.components.get() };
|
||||
Some(inner.remove(id.0))
|
||||
// .try_remove(id.0)
|
||||
@@ -67,7 +67,7 @@ impl ResourcePool {
|
||||
els.remove(id.0);
|
||||
}
|
||||
|
||||
pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
|
||||
pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> ScopeInner) -> ScopeId {
|
||||
let g = unsafe { &mut *self.components.get() };
|
||||
let entry = g.vacant_entry();
|
||||
let id = ScopeId(entry.key());
|
||||
|
||||
@@ -214,7 +214,7 @@ impl Scheduler {
|
||||
let components = components.clone();
|
||||
Rc::new(move |id, ty| {
|
||||
let components = unsafe { &*components.get() };
|
||||
let mut search: Option<&Scope> = components.get(id.0);
|
||||
let mut search: Option<&ScopeInner> = components.get(id.0);
|
||||
while let Some(inner) = search.take() {
|
||||
if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
|
||||
return Some(shared.clone());
|
||||
|
||||
Loaded 100 of 130 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user