diff --git a/Cargo.toml b/Cargo.toml index 4c1863dc8..6d78e7182 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,3 +91,7 @@ path = "./examples/webview.rs" required-features = ["desktop"] name = "tailwind" path = "./examples/tailwind.rs" + + +# [patch.crates-io] +# wasm-bindgen = { path = "../Tinkering/wasm-bindgen" } diff --git a/README.md b/README.md index 4be6f7cdc..69df6e883 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust. ```rust -fn App(cx: Context, props: &()) -> Element { +fn App(cx: Scope, props: &()) -> Element { let mut count = use_state(cx, || 0); cx.render(rsx!( @@ -65,12 +65,11 @@ Dioxus can be used to deliver webapps, desktop apps, static sites, liveview apps If you know React, then you already know Dioxus. ### Unique features: -- The most ergonomic and powerful state management of any Rust UI toolkit. - Desktop apps running natively (no Electron!) in less than 10 lines of code. -- Starting a new app takes zero templates or special tools - get a new app running in just seconds. -- Incredible inline documentation. Supports hover and guides for all HTML elements, listeners, and events. -- Custom bump-allocator backing for all components. Nearly 0 allocations for steady-state components. -- Multithreaded asynchronous coroutine scheduler for powerful async code. +- Incredibly ergonomic and powerful state management. +- Incredible inline documentation - hover and guides for all HTML elements, listeners, and events. +- Extremely memory efficient - 0 global allocations for steady-state components. +- Multithreaded asynchronous coroutine scheduler for first-class async support. - And more! Read the full release post here. ## Get Started with... @@ -95,24 +94,42 @@ If you know React, then you already know Dioxus. See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem. -## Why? +## Why Dioxus and why Rust? -TypeScript is a great addition to JavaScript, but comes with a lot of tweaking flags, a slight performance hit, and an uneven ecosystem where some of the most important packages are not properly typed. TypeScript provides a lot of great benefits to JS projects, but comes with its own "tax" that can slow down dev teams. Rust can be seen as a step up from TypeScript, supporting: +TypeScript is a fantastic addition to JavaScript, but it's still fundamentally JavaScript. TS code runs slightly slower, has tons of configuration options, and not every package is properly typed. -- static types for _all_ libraries -- advanced pattern matching -- immutability by default -- clean, composable iterators -- a good module system -- integrated documentation -- inline built-in unit/integration testing -- best-in-class error handling -- simple and fast build system (compared to WebPack!) -- powerful standard library (no need for lodash or underscore) -- include_str! for integrating html/css/svg templates directly -- various macros (`html!`, `rsx!`) for fast template iteration +In contrast, Dioxus is written in Rust - which is almost like "TypeScript on steroids". -And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time. Dioxus also works on the server, on the web, on mobile, on desktop - and it runs completely natively so performance is never an issue. +By using Rust, we gain: + +- Static types for *every* library +- Immutability by default +- A simple and intuitive module system +- Integrated documentation (`go to source` _actually goes to source_) +- Advanced pattern matching +- Clean, efficient, composable iterators +- Inline built-in unit/integration testing +- Best-in-class error handling +- Powerful and sane standard library +- Flexible macro system +- Access to `crates.io` + +Specifically, Dioxus provides us many other assurances: + +- Proper use of immutable datastructures +- Guaranteed error handling (so you can sleep easy at night not worrying about `cannot read property of undefined`) +- Native performance on mobile +- Direct access to system IO + +And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time. + +### Why NOT Dioxus? +You shouldn't use Dioxus if: + +- You don't like the React Hooks approach to frontend +- You need a no-std renderer +- You want to support browsers where Wasm or asm.js are not supported. +- You need a Send+Sync UI solution (Dioxus is not currently ThreadSafe) # Parity with React @@ -158,189 +175,6 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an - πŸ‘€ = not yet implemented or being worked on - ❓ = not sure if will or can implement -## FAQ: - -### Aren't VDOMs just pure overhead? Why not something like Solid or Svelte? -Remember: Dioxus is a library - not a compiler like Svelte. Plus, the inner VirtualDOM allows Dioxus to easily port into different runtimes, support SSR, and run remotely in the cloud. VDOMs tend to more ergonomic to work with and feel roughly like natural Rust code. The overhead of Dioxus is **extraordinarily** minimal... sure, there may be some overhead but on an order of magnitude lower than the time required to actually update the page. - - -### Isn't the overhead for interacting with the DOM from Wasm too much? -The overhead layer between Wasm and JS APIs is extremely poorly understood. Rust web benchmarks typically suffer from differences in how Rust and JS cache strings. In Dioxus, we solve most of these issues and our JS Framework Benchmark actually beats the Wasm Bindgen benchmark in many cases. Compared to a "pure vanilla JS" solution, Dioxus adds less than 5% of overhead and takes advantage of batched DOM manipulation. - -### Aren't Wasm binaries too huge to deploy in production? -Wasm binary sizes are another poorly understood characteristic of Rust web apps. 50kb of Wasm and 50kb of JS are _not_ made equally. In JS, the code must be downloaded _first_ and _then_ JIT-ted. Just-in-time compiling 50kb of JavaScript takes a while which is why 50kb of JavaScript sounds like a lot! However, with Wasm, the code is downloaded and JIT-ted _simultaneously_ through the magic of streaming compilation. By the time the 50kb of Rust is finished downloading, it is already ready to go. Again, Dioxus beats out many benchmarks with time-to-interactivity. - -For reference, Dioxus `hello-world` clocks in at around 70kb gzip or 60kb brotli, and Dioxus supports SSR. - -### Why hooks? Why not MVC, classes, traits, messages, etc? -There are plenty Rust Elm-like frameworks in the world - we were not interested in making another! Instead, we borrowed hooks from React. JS and Rust share many structural similarities, so if you're comfortable with React, then you'll be plenty comfortable with Dioxus. - -### Why a custom DSL? Why not just pure function calls? -The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from. - -### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm? -Dioxus builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times. - -### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy? -- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms -- Sycamore and Dominator are more like SolidJS/Svelte, requiring no VDOM but has less naturally-Rusty state management -- Percy isn't quite mature yet -- Dodrio is the spiritual predecessor of Dioxus, but is currently an archived research project without the batteries of Dioxus - -### How do the mobile and desktop renderers work? Is it Electron? -Currently, Dioxus uses your device's native WebView library to draw the page. None of your app code is actually running in the WebView thread, so you can access system resources instead of having to go through something like NodeJS. This means your app will use Safari on macOS/iOS, Edge (Chromium) on Windows, and whatever is the default Web Browser for Linux and Android. Because your code is compiled and running natively, performance is not a problem. You will have to use the various "Escape Hatches" to use browser-native APIs (like WebGL) and work around visual differences in how Safari and Chrome render the page. - -In the future, we are interested in using Webrenderer to provide a fully native renderer without having to go through the system WebView library. In practice, Dioxus mobile and desktop are great for CRUD-style apps, but the ergonomic cross-platform APIs (GPS, Camera, etc) are not there yet. - -### Why NOT Dioxus? -You shouldn't use Dioxus if: -- You don't like the React Hooks approach to frontend -- You need a no-std renderer -- You want to support browsers where Wasm or asm.js are not supported. - - -## Show me some examples! - -In our collection of examples, guides, and tutorials, we have: -- The book (an introductory course) -- The guide (an in-depth analysis of everything in Dioxus) -- The reference (a collection of examples with heavy documentation) -- The general examples -- The platform-specific examples (web, ssr, desktop, mobile, server) - -Here's what a few common tasks look like in Dioxus: - -Nested components with children and internal state: -```rust -fn App(cx: Context, props: &()) -> Element { - cx.render(rsx!( Toggle { "Toggle me" } )) -} - -#[derive(PartialEq, Props)] -struct ToggleProps { children: Element } - -fn Toggle(cx: Context, props: &ToggleProps) -> Element { - let mut toggled = use_state(cx, || false); - cx.render(rsx!{ - div { - {&props.children} - button { onclick: move |_| toggled.set(true), - {toggled.and_then(|| "On").or_else(|| "Off")} - } - } - }) -} -``` - -Controlled inputs: -```rust -fn App(cx: Context, props: &()) -> Element { - let value = use_state(cx, String::new); - cx.render(rsx!( - input { - "type": "text", - value: "{value}", - oninput: move |evt| value.set(evt.value.clone()) - } - )) -} -``` - -Lists and Conditional rendering: -```rust -fn App(cx: Context, props: &()) -> Element { - let list = (0..10).map(|i| { - rsx!(li { key: "{i}", "Value: {i}" }) - }); - - let title = match list.len() { - 0 => rsx!("Not enough"), - _ => rsx!("Plenty!"), - }; - - if should_show { - cx.render(rsx!( - {title} - ul { {list} } - )) - } else { - None - } -} -``` - -Tiny components: -```rust -static App: FC<()> = |cx, _| rsx!(cx, div {"hello world!"}); -``` - -Borrowed prop contents: -```rust -fn App(cx: Context, props: &()) -> Element { - let name = use_state(cx, || String::from("example")); - rsx!(cx, Child { title: name.as_str() }) -} - -#[derive(Props)] -struct ChildProps<'a> { title: &'a str } - -fn Child(cx: Context, props: &ChildProps) -> Element { - rsx!(cx, "Hello {props.title}") -} -``` - -Global State -```rust -struct GlobalState { name: String } - -fn App(cx: Context, props: &()) -> Element { - use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") }) - rsx!(cx, Leaf {}) -} - -fn Leaf(cx: Context, props: &()) -> Element { - let state = use_consume_shared_state::(cx)?; - rsx!(cx, "Hello {state.name}") -} -``` - -Router (inspired by Yew-Router) -```rust -#[derive(PartialEq, Clone, Hash, Eq, Routable)] -enum Route { - #[at("/")] - Home, - #[at("/post/{id}")] - Post(id) -} - -fn App(cx: Context, props: &()) -> Element { - let route = use_router(cx, Route::parse); - cx.render(rsx!(div { - {match route { - Route::Home => rsx!( Home {} ), - Route::Post(id) => rsx!( Post { id: id }) - }} - })) -} -``` - -Suspense -```rust -fn App(cx: Context, props: &()) -> Element { - let doggo = use_suspense(cx, - || async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::().await.unwrap() }, - |response| cx.render(rsx!( img { src: "{response.message}" })) - ); - - cx.render(rsx!{ - div { - "One doggo coming right up:" - {doggo} - } - }) -} -``` ## License diff --git a/docs/guide/src/README.md b/docs/guide/src/README.md index 4aaf4d22c..faf58e309 100644 --- a/docs/guide/src/README.md +++ b/docs/guide/src/README.md @@ -5,7 +5,7 @@ **Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get up-and-running with Dioxus running on the Web, Desktop, Mobile, and more. ```rust -fn App((cx, props): Component<()>) -> Element { +fn App(cx: Context, props: &()) -> Element { let mut count = use_state(cx, || 0); cx.render(rsx!( @@ -16,11 +16,20 @@ fn App((cx, props): Component<()>) -> Element { }; ``` -The Dioxus API and patterns closely resemble React - if this guide is lacking in any general concept or an error message is confusing, we recommend substituting "React" for "Dioxus" in your web search terms. A major goal of Dioxus is to provide a familiar toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc) - if you know React, then you already know Dioxus. If you don't know either, this guide will still help you! - +In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing a *familiar* toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc). If you know React, then you already know Dioxus. If you don't know either, this guide will still help you! > This is introduction book! For advanced topics, check out the [Reference Guide]() instead. +## Multiplatform + +Dioxus is a *portable* toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to Web-Sys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the `Html` feature enabled which can be disabled depending on your target renderer. + +Right now, we have several 1st-party renderers: +- WebSys (for WASM) +- Tao/Tokio (for Desktop apps) +- Tao/Tokio (for Mobile apps) +- SSR (for generating static markup) + ### Web Support --- @@ -39,12 +48,7 @@ Examples: ### SSR Support --- -Dioxus supports server-side rendering! In a pinch, you can literally "debug" the VirtualDom: - -```rust -let dom = VirtualDom::new(App); -println!("{:?}, dom"); -``` +Dioxus supports server-side rendering! For rendering statically to an `.html` file or from a WebServer, then you'll want to make sure the `ssr` feature is enabled in the `dioxus` crate and use the `dioxus::ssr` API. We don't expect the SSR API to change drastically in the future. @@ -52,7 +56,6 @@ For rendering statically to an `.html` file or from a WebServer, then you'll wan let contents = dioxus::ssr::render_vdom(&dom, |c| c); ``` - [Jump to the getting started guide for SSR.]() Examples: diff --git a/docs/guide/src/concepts/00-index.md b/docs/guide/src/concepts/00-index.md index 30c137c45..715c3d69b 100644 --- a/docs/guide/src/concepts/00-index.md +++ b/docs/guide/src/concepts/00-index.md @@ -26,7 +26,7 @@ container.push(green_light); container.push(yellow_light); container.push(red_light); -container.onclick(move |_| { +container.set_onclick(move |_| { if red_light.enabled() { red_light.set_enabled(false); green_light.set_enabled(true); @@ -45,22 +45,23 @@ As the UI grows in scale, our logic to keep each element in the proper state wou Instead, with Dioxus, we *declare* what we want our UI to look like: ```rust -let state = "red"; +let mut state = use_state(cx, || "red"); -rsx!( +cx.render(rsx!( Container { - Light { color: "red", enabled: {state == "red"} } - Light { color: "yellow", enabled: {state == "yellow"} } - Light { color: "green", enabled: {state == "green"} } - onclick: |_| { - state = match state { + Light { color: "red", enabled: format_args!("{}", state == "red") } + Light { color: "yellow", enabled: format_args!("{}", state == "yellow") } + Light { color: "green", enabled: format_args!("{}", state == "green") } + + onclick: move |_| { + state.set(match *state { "green" => "yellow", "yellow" => "red", "red" => "green", - } + }) } } -) +)) ``` Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks. diff --git a/docs/guide/src/concepts/10-concurrent-mode.md b/docs/guide/src/concepts/10-concurrent-mode.md index 821f94e12..76e443ab9 100644 --- a/docs/guide/src/concepts/10-concurrent-mode.md +++ b/docs/guide/src/concepts/10-concurrent-mode.md @@ -50,8 +50,8 @@ async fn ExampleLoader(cx: Context<()>) -> Vnode { This API stores the result on the Context object, so the loaded data is taken as reference. */ let name: &Result = use_fetch_data("http://example.com/json", ()) - .place_holder(|(cx, props)|rsx!{
"loading..."
}) - .delayed_place_holder(1000, |(cx, props)|rsx!{
"still loading..."
}) + .place_holder(|cx, props|rsx!{
"loading..."
}) + .delayed_place_holder(1000, |cx, props|rsx!{
"still loading..."
}) .await; match name { diff --git a/docs/guide/src/concepts/11-arena-memo.md b/docs/guide/src/concepts/11-arena-memo.md index dce655bc4..28eccda24 100644 --- a/docs/guide/src/concepts/11-arena-memo.md +++ b/docs/guide/src/concepts/11-arena-memo.md @@ -21,9 +21,9 @@ fn test() -> DomTree { } } -static TestComponent: FC<()> = |(cx, props)|html!{
"Hello world"
}; +static TestComponent: FC<()> = |cx, props|html!{
"Hello world"
}; -static TestComponent: FC<()> = |(cx, props)|{ +static TestComponent: FC<()> = |cx, props|{ let g = "BLAH"; html! {
"Hello world"
@@ -31,7 +31,7 @@ static TestComponent: FC<()> = |(cx, props)|{ }; #[functional_component] -static TestComponent: FC<{ name: String }> = |(cx, props)|html! {
"Hello {name}"
}; +static TestComponent: FC<{ name: String }> = |cx, props|html! {
"Hello {name}"
}; ``` ## Why this behavior? diff --git a/docs/guide/src/concepts/12-signals.md b/docs/guide/src/concepts/12-signals.md index d64a7617e..5000faebc 100644 --- a/docs/guide/src/concepts/12-signals.md +++ b/docs/guide/src/concepts/12-signals.md @@ -96,7 +96,7 @@ Sometimes you want a signal to propagate across your app, either through far-awa ```rust const TITLE: Atom = || "".to_string(); -const Provider: FC<()> = |(cx, props)|{ +const Provider: FC<()> = |cx, props|{ let title = use_signal(&cx, &TITLE); rsx!(cx, input { value: title }) }; @@ -105,7 +105,7 @@ const Provider: FC<()> = |(cx, props)|{ If we use the `TITLE` atom in another component, we can cause updates to flow between components without calling render or diffing either component trees: ```rust -const Receiver: FC<()> = |(cx, props)|{ +const Receiver: FC<()> = |cx, props|{ let title = use_signal(&cx, &TITLE); log::info!("This will only be called once!"); rsx!(cx, @@ -132,7 +132,7 @@ Dioxus automatically understands how to use your signals when mixed with iterato ```rust const DICT: AtomFamily = |_| {}; -const List: FC<()> = |(cx, props)|{ +const List: FC<()> = |cx, props|{ let dict = use_signal(&cx, &DICT); cx.render(rsx!( ul { diff --git a/docs/guide/src/concepts/components.md b/docs/guide/src/concepts/components.md index a5b824817..ef750f87a 100644 --- a/docs/guide/src/concepts/components.md +++ b/docs/guide/src/concepts/components.md @@ -84,7 +84,7 @@ struct PostProps { And our render function: ```rust -fn Post((cx, props): Scope) -> Element { +fn Post(cx: Context, props: &PostProps) -> Element { cx.render(rsx!{ div { class: "post-container" VoteButton { @@ -120,7 +120,7 @@ struct VoteButtonProps { score: i32 } -fn VoteButton((cx, props): Scope) -> Element { +fn VoteButton(cx: Context, props: &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): Scope<'a, TitleCardProps>) -> Element<'a> { +fn TitleCard(cx: Context, props: &TitleCardProps) -> Element { cx.render(rsx!{ h1 { "{props.title}" } }) @@ -174,8 +174,8 @@ 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): Scope) -> Element { - cx.render(rsx!{ }) +fn Post(cx: Context, props: &PostProps) -> Element { + cx.render(rsx!("hello")) } ``` ## Moving forward diff --git a/docs/guide/src/concepts/conditional_rendering.md b/docs/guide/src/concepts/conditional_rendering.md index 977f66597..e35fce36d 100644 --- a/docs/guide/src/concepts/conditional_rendering.md +++ b/docs/guide/src/concepts/conditional_rendering.md @@ -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): Scope) -> Element { +fn App(cx: Context, props: &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): Scope<()>) -> Element { +fn App(cx: Context, props: &())-> 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): Scope<()>) -> Element { We can even match against values: ```rust -fn App((cx, props): Scope<()>) -> Element { +fn App(cx: Context, props: &())-> 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): Scope<()>) -> Element { } ``` -Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. +Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. To turn our `rsx!` into Elements, we need to call `cx.render`. 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): Scope<()>) -> Element { +fn App(cx: Context, props: &())-> Element { match get_name() { "jack" => rsx!(cx, "Hey Jack, how's Diane?" ), "diane" => rsx!(cx, "Hey Diana, how's Jack?" ), @@ -83,13 +83,13 @@ fn App((cx, props): Scope<()>) -> Element { This syntax even enables us to write a one-line component: ```rust -static App: Fc<()> = |(cx, props)| rsx!(cx, "hello world!"); +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 { +fn App(cx: Context, props: &())-> Element { let greeting = match get_name() { "jack" => rsx!("Hey Jack, how's Diane?" ), "diane" => rsx!("Hey Diana, how's Jack?" ), diff --git a/docs/guide/src/concepts/interactivity.md b/docs/guide/src/concepts/interactivity.md index a7403933c..bd8ff14aa 100644 --- a/docs/guide/src/concepts/interactivity.md +++ b/docs/guide/src/concepts/interactivity.md @@ -35,7 +35,7 @@ fn main() { 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) -> Element { +fn App(cx: Context, props: &PostProps) -> Element { cx.render(rsx!{ Title { title: &props.title } Score { score: &props.score } @@ -63,8 +63,8 @@ Instead, you'll want to store state internally in your components and let *that* 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(|| { +fn App(cx: Context, props: &())-> Element { + let post = use_state(cx, || { PostData { id: Uuid::new_v4(), score: 10, @@ -111,8 +111,8 @@ When responding to user-triggered events, we'll want to "listen" for an event on 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()); +fn App(cx: Context, props: &())-> Element { + let post = use_state(cx, || PostData::new()); cx.render(rsx!{ button { @@ -134,8 +134,8 @@ We can use tasks in our components to build a tiny stopwatch that ticks every se ```rust -fn App((cx, props): Scope<()>) -> Element { - let mut sec_elapsed = use_state(|| 0); +fn App(cx: Context, props: &())-> Element { + let mut sec_elapsed = use_state(cx, || 0); cx.spawn_task(async move { TimeoutFuture::from_ms(1000).await; diff --git a/docs/guide/src/concepts/lists.md b/docs/guide/src/concepts/lists.md index 2f9efa6ba..ebe12821d 100644 --- a/docs/guide/src/concepts/lists.md +++ b/docs/guide/src/concepts/lists.md @@ -74,7 +74,7 @@ struct PostListProps<'a> { Next, we're going to define our component: ```rust -fn App((cx, props): Component) -> Element { +fn App(cx: Context, props: &PostList) -> Element { // First, we create a new iterator by mapping the post array let posts = props.posts.iter().map(|post| rsx!{ Post { diff --git a/docs/guide/src/concepts/suspense.md b/docs/guide/src/concepts/suspense.md index f6d8c7e98..6c81fa992 100644 --- a/docs/guide/src/concepts/suspense.md +++ b/docs/guide/src/concepts/suspense.md @@ -1 +1,54 @@ # Suspense + +Suspense in Dioxus is enabled through placeholder nodes. + +just a component that renders nodes into the placeholder after the future is finished? + +in react, suspense is just completely pausing diffing while a + +```rust +let n = use_suspense(cx || { + cx.render(rsx!{ + Suspense { + prom: fut, + callback: || {} + } + }) +}) + +suspense () { + let value = use_state(); + if first_render { + push_task({ + value.set(fut.await); + }); + } else { + callback(value) + } +} + + +let name = fetch_name().await; + + +function ProfileDetails() { + // Try to read user info, although it might not have loaded yet + const user = resource.read(); + return

{user.name}

; +} + + +fn ProfileDteails() { + let user = resource.suspend(cx, |l| rsx!("{l}")); + + // waits for the resource to be ready and updates the placeholder with the tree + let name = resource.suspend_with(cx, |val| rsx!( div { "hello" "{user}" } )); + + cx.render(rsx!( + div { + {user} + {name} + } + )) +} +``` diff --git a/docs/guide/src/concepts/vnodes.md b/docs/guide/src/concepts/vnodes.md index 774b1bfba..e6f1b43d0 100644 --- a/docs/guide/src/concepts/vnodes.md +++ b/docs/guide/src/concepts/vnodes.md @@ -107,7 +107,7 @@ To do this, we use the familiar struct-style syntax that Rust provides. Commas a ```rust rsx!( div { - hidden: true, + hidden: "true", background_color: "blue", class: "card color-{mycolor}" } diff --git a/docs/guide/src/hello_world.md b/docs/guide/src/hello_world.md index 7c703b034..2520c286a 100644 --- a/docs/guide/src/hello_world.md +++ b/docs/guide/src/hello_world.md @@ -1,4 +1,4 @@ -# Hello World +# Hello, World desktop app Let's put together a simple "hello world" desktop application to get acquainted with Dioxus. @@ -92,10 +92,10 @@ use dioxus::prelude::*; fn main() { - dioxus::desktop::start(App, |c| c); + dioxus::desktop::launch(App, |c| c); } -fn App((cx, props): Component<()>) -> Element { +fn App(cx: Context, props: &()) -> Element { cx.render(rsx! ( div { "Hello, world!" } )) @@ -108,7 +108,7 @@ At this point, you could call `cargo run` and be greeted with a simple `Hello, W ### Dissecting our example -This bit of code imports everything from the the `prelude` module. This brings into scope the right traits, types, and macros needed for working with Dioxus. +The `use` statement at the top of our app imports everything from the the `prelude` module. `use`-ing the prelude imports the right traits, types, and macros needed for working with Dioxus. ```rust use dioxus::prelude::*; @@ -118,44 +118,34 @@ This initialization code launches a Tokio runtime on a helper thread where your ```rust fn main() { - dioxus::desktop::start(App, |c| c); + dioxus::desktop::launch(App, |c| c); } ``` Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`. ```rust -fn App((cx, props): Component<()>) -> Element { +fn App(cx: Context, props: &()) -> Element { cx.render(rsx! { div { "Hello, world!" } }) } ``` -In cases where props need to borrow from their parent, you will need to specify lifetimes using the function syntax: + +Writing `fn App(cx: Context, props: &()) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data. ```rust -fn App<'a>(cx: Component<'a, ()>) -> Element<'a> { - cx.render(rsx! { - div { "Hello, world!" } - }) -} +static App: FC<()> = |cx, props| cx.render(rsx!(div { "Hello, world!" })); ``` -Writing `fn App((cx, props): Component<()>) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data. -```rust -static App: Fc<()> = |(cx, props)| { - cx.render(rsx! { - div { "Hello, world!" } - }) -}; -``` +### What is this `Context` object? -### The `Context` object +Coming from React, the `Context` object might be confusing. In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. -In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data. +In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data. The `Context` object provides a handful of useful APIs for features like suspense, rendering, and more. -### The `rsx!` macro +## Moving on + +Congrats! You've built your first desktop application with Dioxus. Next, we're going to learn about the basics of building interactive user interfaces. -Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax. -The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `cx.render`. diff --git a/docs/guide/src/setup.md b/docs/guide/src/setup.md index 497762902..c654179c9 100644 --- a/docs/guide/src/setup.md +++ b/docs/guide/src/setup.md @@ -20,6 +20,16 @@ Dioxus requires a few main things to get up and running: Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more. +### Installing Rust + +Head over to [https://rust-lang.org](http://rust-lang.org) and install the Rust compiler. + +Once installed, make sure to install wasm32-unknown-unknown as a target if you're planning on deploying your app to the web. + +``` +rustup target add wasm32-unknown-uknown +``` + ### Dioxus-CLI for dev server, bundling, etc. We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (should be automatically installed with Rust): diff --git a/examples/README.md b/examples/README.md index 8e888808c..06a73db07 100644 --- a/examples/README.md +++ b/examples/README.md @@ -48,3 +48,148 @@ These web-specific examples must be run with `dioxus-cli` using `dioxus develop | ------- | ------------ | | asd | this does | | asd | this does | + + + +## Show me some examples! + +In our collection of examples, guides, and tutorials, we have: +- The book (an introductory course) +- The guide (an in-depth analysis of everything in Dioxus) +- The reference (a collection of examples with heavy documentation) +- The general examples +- The platform-specific examples (web, ssr, desktop, mobile, server) + +Here's what a few common tasks look like in Dioxus: + +Nested components with children and internal state: +```rust +fn App(cx: Context, props: &()) -> Element { + cx.render(rsx!( Toggle { "Toggle me" } )) +} + +#[derive(PartialEq, Props)] +struct ToggleProps { children: Element } + +fn Toggle(cx: Context, props: &ToggleProps) -> Element { + let mut toggled = use_state(cx, || false); + cx.render(rsx!{ + div { + {&props.children} + button { onclick: move |_| toggled.set(true), + {toggled.and_then(|| "On").or_else(|| "Off")} + } + } + }) +} +``` + +Controlled inputs: +```rust +fn App(cx: Context, props: &()) -> Element { + let value = use_state(cx, String::new); + cx.render(rsx!( + input { + "type": "text", + value: "{value}", + oninput: move |evt| value.set(evt.value.clone()) + } + )) +} +``` + +Lists and Conditional rendering: +```rust +fn App(cx: Context, props: &()) -> Element { + let list = (0..10).map(|i| { + rsx!(li { key: "{i}", "Value: {i}" }) + }); + + let title = match list.len() { + 0 => rsx!("Not enough"), + _ => rsx!("Plenty!"), + }; + + if should_show { + cx.render(rsx!( + {title} + ul { {list} } + )) + } else { + None + } +} +``` + +Tiny components: +```rust +static App: FC<()> = |cx, _| rsx!(cx, div {"hello world!"}); +``` + +Borrowed prop contents: +```rust +fn App(cx: Context, props: &()) -> Element { + let name = use_state(cx, || String::from("example")); + rsx!(cx, Child { title: name.as_str() }) +} + +#[derive(Props)] +struct ChildProps<'a> { title: &'a str } + +fn Child(cx: Context, props: &ChildProps) -> Element { + rsx!(cx, "Hello {props.title}") +} +``` + +Global State +```rust +struct GlobalState { name: String } + +fn App(cx: Context, props: &()) -> Element { + use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") }) + rsx!(cx, Leaf {}) +} + +fn Leaf(cx: Context, props: &()) -> Element { + let state = use_consume_shared_state::(cx)?; + rsx!(cx, "Hello {state.name}") +} +``` + +Router (inspired by Yew-Router) +```rust +#[derive(PartialEq, Clone, Hash, Eq, Routable)] +enum Route { + #[at("/")] + Home, + #[at("/post/{id}")] + Post(id) +} + +fn App(cx: Context, props: &()) -> Element { + let route = use_router(cx, Route::parse); + cx.render(rsx!(div { + {match route { + Route::Home => rsx!( Home {} ), + Route::Post(id) => rsx!( Post { id: id }) + }} + })) +} +``` + +Suspense +```rust +fn App(cx: Context, props: &()) -> Element { + let doggo = use_suspense(cx, + || async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::().await.unwrap() }, + |response| cx.render(rsx!( img { src: "{response.message}" })) + ); + + cx.render(rsx!{ + div { + "One doggo coming right up:" + {doggo} + } + }) +} +``` diff --git a/examples/async.rs b/examples/async.rs index b0ea0a7b4..bb05669e4 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -11,7 +11,7 @@ async fn main() { dioxus::desktop::launch(App, |c| c); } -pub static App: FC<()> = |(cx, _)| { +pub static App: FC<()> = |cx, _| { let count = use_state(cx, || 0); let mut direction = use_state(cx, || 1); diff --git a/examples/borrowed.rs b/examples/borrowed.rs index 786de9da9..bf779f3b5 100644 --- a/examples/borrowed.rs +++ b/examples/borrowed.rs @@ -20,7 +20,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -fn App((cx, props): Scope<()>) -> Element { +fn App(cx: Context, props: &()) -> Element { let text: &mut Vec = cx.use_hook(|_| vec![String::from("abc=def")], |f| f); let first = text.get_mut(0).unwrap(); @@ -39,11 +39,7 @@ struct C1Props<'a> { text: &'a mut String, } -impl<'a> Drop for C1Props<'a> { - fn drop(&mut self) {} -} - -fn Child1<'a>((cx, props): Scope<'a, C1Props>) -> Element<'a> { +fn Child1(cx: Context, props: &C1Props) -> Element { let (left, right) = props.text.split_once("=").unwrap(); cx.render(rsx! { @@ -59,7 +55,7 @@ struct C2Props<'a> { text: &'a str, } -fn Child2<'a>((cx, props): Scope<'a, C2Props>) -> Element<'a> { +fn Child2(cx: Context, props: &C2Props) -> Element { cx.render(rsx! { Child3 { text: props.text @@ -72,7 +68,7 @@ struct C3Props<'a> { text: &'a str, } -fn Child3<'a>((cx, props): Scope<'a, C3Props>) -> Element<'a> { +fn Child3(cx: Context, props: &C3Props) -> Element { cx.render(rsx! { div { "{props.text}"} }) diff --git a/examples/calculator.rs b/examples/calculator.rs index 3fc110b64..1a918879c 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -3,97 +3,82 @@ This example is a simple iOS-style calculator. This particular example can run a This calculator version uses React-style state management. All state is held as individual use_states. */ +use std::sync::Arc; + use dioxus::events::*; use dioxus::prelude::*; +use separator::Separatable; fn main() { dioxus::desktop::launch(APP, |cfg| cfg); } -const APP: FC<()> = |(cx, _)| { +const APP: FC<()> = |cx, _| { let cur_val = use_state(cx, || 0.0_f64); let operator = use_state(cx, || None as Option<&'static str>); let display_value = use_state(cx, || String::from("")); - let clear_display = display_value == "0"; - let clear_text = if clear_display { "C" } else { "AC" }; - + let toggle_percent = move |_| todo!(); let input_digit = move |num: u8| display_value.modify().push_str(num.to_string().as_str()); - let input_dot = move || display_value.modify().push_str("."); - - let perform_operation = move || { - if let Some(op) = operator.as_ref() { - let rhs = display_value.parse::().unwrap(); - let new_val = match *op { - "+" => *cur_val + rhs, - "-" => *cur_val - rhs, - "*" => *cur_val * rhs, - "/" => *cur_val / rhs, - _ => unreachable!(), - }; - cur_val.set(new_val); - display_value.set(new_val.to_string()); - operator.set(None); - } - }; - - let toggle_sign = move |_| { - if display_value.starts_with("-") { - display_value.set(display_value.trim_start_matches("-").to_string()) - } else { - display_value.set(format!("-{}", *display_value)) - } - }; - - let toggle_percent = move |_| todo!(); - - let clear_key = move |_| { - display_value.set("0".to_string()); - if !clear_display { - operator.set(None); - cur_val.set(0.0); - } - }; - - let keydownhandler = move |evt: KeyboardEvent| match evt.key_code { - KeyCode::Add => operator.set(Some("+")), - KeyCode::Subtract => operator.set(Some("-")), - KeyCode::Divide => operator.set(Some("/")), - KeyCode::Multiply => operator.set(Some("*")), - KeyCode::Num0 => input_digit(0), - KeyCode::Num1 => input_digit(1), - KeyCode::Num2 => input_digit(2), - KeyCode::Num3 => input_digit(3), - KeyCode::Num4 => input_digit(4), - KeyCode::Num5 => input_digit(5), - KeyCode::Num6 => input_digit(6), - KeyCode::Num7 => input_digit(7), - KeyCode::Num8 => input_digit(8), - KeyCode::Num9 => input_digit(9), - KeyCode::Backspace => { - if !display_value.as_str().eq("0") { - display_value.modify().pop(); - } - } - _ => {} - }; - - use separator::Separatable; - let formatted_display = cur_val.separated_string(); - rsx!(cx, div { - class: "calculator", onkeydown: {keydownhandler} - div { class: "calculator-display", "{formatted_display}" } + class: "calculator", + onkeydown: move |evt| match evt.key_code { + KeyCode::Add => operator.set(Some("+")), + KeyCode::Subtract => operator.set(Some("-")), + KeyCode::Divide => operator.set(Some("/")), + KeyCode::Multiply => operator.set(Some("*")), + KeyCode::Num0 => input_digit(0), + KeyCode::Num1 => input_digit(1), + KeyCode::Num2 => input_digit(2), + KeyCode::Num3 => input_digit(3), + KeyCode::Num4 => input_digit(4), + KeyCode::Num5 => input_digit(5), + KeyCode::Num6 => input_digit(6), + KeyCode::Num7 => input_digit(7), + KeyCode::Num8 => input_digit(8), + KeyCode::Num9 => input_digit(9), + KeyCode::Backspace => { + if !display_value.as_str().eq("0") { + display_value.modify().pop(); + } + } + _ => {} + } + div { class: "calculator-display", {[format_args!("{}", cur_val.separated_string())]} } div { class: "input-keys" div { class: "function-keys" - CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" } - CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "Β±"} - CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"} + CalculatorKey { + {[if display_value == "0" { "C" } else { "AC" }]} + name: "key-clear", + onclick: move |_| { + display_value.set("0".to_string()); + if display_value != "0" { + operator.set(None); + cur_val.set(0.0); + } + } + } + CalculatorKey { + "Β±" + name: "key-sign", + onclick: move |_| { + if display_value.starts_with("-") { + display_value.set(display_value.trim_start_matches("-").to_string()) + } else { + display_value.set(format!("-{}", *display_value)) + } + }, + } + CalculatorKey { + "%" + onclick: {toggle_percent} + name: "key-percent", + } } div { class: "digit-keys" CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" } - CalculatorKey { name: "key-dot", onclick: move |_| input_dot(), "●" } + CalculatorKey { name: "key-dot", onclick: move |_| display_value.modify().push_str("."), "●" } {(1..9).map(|k| rsx!{ CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" } @@ -104,7 +89,25 @@ const APP: FC<()> = |(cx, _)| { CalculatorKey { name: "key-multiply", onclick: move |_| operator.set(Some("*")) "Γ—" } CalculatorKey { name: "key-subtract", onclick: move |_| operator.set(Some("-")) "βˆ’" } CalculatorKey { name: "key-add", onclick: move |_| operator.set(Some("+")) "+" } - CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" } + CalculatorKey { + "=" + name: "key-equals", + onclick: move |_| { + if let Some(op) = operator.as_ref() { + let rhs = display_value.parse::().unwrap(); + let new_val = match *op { + "+" => *cur_val + rhs, + "-" => *cur_val - rhs, + "*" => *cur_val * rhs, + "/" => *cur_val / rhs, + _ => unreachable!(), + }; + cur_val.set(new_val); + display_value.set(new_val.to_string()); + operator.set(None); + } + }, + } } } }) @@ -113,11 +116,11 @@ const APP: FC<()> = |(cx, _)| { #[derive(Props)] struct CalculatorKeyProps<'a> { name: &'static str, - onclick: &'a dyn Fn(MouseEvent), - children: ScopeChildren<'a>, + onclick: &'a dyn Fn(Arc), + children: Element, } -fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps<'a>>) -> Element { +fn CalculatorKey<'a>(cx: Context, props: &CalculatorKeyProps) -> Element { rsx!(cx, button { class: "calculator-key {props.name}" onclick: {props.onclick} diff --git a/examples/core/alternative.rs b/examples/core/alternative.rs index 3f2fa9790..6c20e2adf 100644 --- a/examples/core/alternative.rs +++ b/examples/core/alternative.rs @@ -9,7 +9,7 @@ fn main() { println!("{}", dom); } -pub static EXAMPLE: FC<()> = |(cx, _)| { +pub static EXAMPLE: FC<()> = |cx, _| { let list = (0..10).map(|_f| { rsx! { "{_f}" diff --git a/examples/core/syntax.rs b/examples/core/syntax.rs index 5eec9d181..9a21bc7e6 100644 --- a/examples/core/syntax.rs +++ b/examples/core/syntax.rs @@ -31,7 +31,7 @@ fn html_usage() { // let p = rsx!(div { {f} }); } -static App2: FC<()> = |(cx, _)| cx.render(rsx!("hello world!")); +static App2: FC<()> = |cx, _| cx.render(rsx!("hello world!")); static App: FC<()> = |cx, props| { let name = cx.use_state(|| 0); diff --git a/examples/crm.rs b/examples/crm.rs index e0c873221..3de5b7e3a 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -19,7 +19,7 @@ pub struct Client { pub description: String, } -static App: FC<()> = |(cx, _)| { +static App: FC<()> = |cx, _| { let mut clients = use_ref(cx, || vec![] as Vec); let mut scene = use_state(cx, || Scene::ClientsList); diff --git a/examples/desktop/crm.rs b/examples/desktop/crm.rs index efb7efed2..8cc5b2988 100644 --- a/examples/desktop/crm.rs +++ b/examples/desktop/crm.rs @@ -21,7 +21,7 @@ pub struct Client { pub description: String, } -static App: FC<()> = |(cx, _)| { +static App: FC<()> = |cx, _| { let mut scene = use_state(cx, || Scene::ClientsList); let clients = use_ref(cx, || vec![] as Vec); diff --git a/examples/desktop/todomvc.rs b/examples/desktop/todomvc.rs index 6e85b87c6..d8edf40e4 100644 --- a/examples/desktop/todomvc.rs +++ b/examples/desktop/todomvc.rs @@ -32,7 +32,7 @@ pub struct TodoItem { } pub type Todos = HashMap; -pub static App: FC<()> = |(cx, _)| { +pub static App: FC<()> = |cx, _| { // Share our TodoList to the todos themselves use_provide_state(cx, Todos::new); diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index 6dd9a9073..782081c41 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -1,50 +1,38 @@ -use dioxus::{events::MouseEvent, prelude::*}; -use fxhash::FxBuildHasher; -use std::rc::Rc; +use dioxus::prelude::*; +use rand::prelude::*; fn main() { - dioxus::desktop::launch(App, |c| c); + dioxus::web::launch(App, |c| c); + // dioxus::desktop::launch(App, |c| c); } -// We use a special immutable hashmap to make hashmap operations efficient -type RowList = im_rc::HashMap, FxBuildHasher>; +#[derive(Clone, PartialEq)] +struct Label { + key: usize, + labels: [&'static str; 3], +} -static App: FC<()> = |(cx, _props)| { - let mut items = use_state(cx, || RowList::default()); - - let create_rendered_rows = move |from, num| move |_| items.set(create_row_list(from, num)); - - let mut append_1_000_rows = - move |_| items.set(create_row_list(items.len(), 1000).union((*items).clone())); - - let update_every_10th_row = move |_| { - let mut new_items = (*items).clone(); - let mut small_rng = SmallRng::from_entropy(); - new_items.iter_mut().step_by(10).for_each(|(_, val)| { - *val = create_new_row_label(&mut String::with_capacity(30), &mut small_rng) - }); - items.set(new_items); - }; - let clear_rows = move |_| items.set(RowList::default()); - - let swap_rows = move |_| { - // this looks a bit ugly because we're using a hashmap instead of a vec - if items.len() > 998 { - let mut new_items = (*items).clone(); - let a = new_items.get(&0).unwrap().clone(); - *new_items.get_mut(&0).unwrap() = new_items.get(&998).unwrap().clone(); - *new_items.get_mut(&998).unwrap() = a; - items.set(new_items); +impl Label { + fn new_list(num: usize) -> Vec { + let mut rng = SmallRng::from_entropy(); + let mut labels = Vec::with_capacity(num); + for _ in 0..num { + labels.push(Label { + key: 0, + labels: [ + ADJECTIVES.choose(&mut rng).unwrap(), + COLOURS.choose(&mut rng).unwrap(), + NOUNS.choose(&mut rng).unwrap(), + ], + }); } - }; + labels + } +} - let rows = items.iter().map(|(key, value)| { - rsx!(Row { - key: "{key}", - row_id: *key as usize, - label: value.clone(), - }) - }); +static App: FC<()> = |cx, _props| { + let mut items = use_ref(cx, || vec![]); + let mut selected = use_state(cx, || None); cx.render(rsx! { div { class: "container" @@ -53,22 +41,49 @@ static App: FC<()> = |(cx, _props)| { div { class: "col-md-6", h1 { "Dioxus" } } div { class: "col-md-6" div { class: "row" - ActionButton { name: "Create 1,000 rows", id: "run", onclick: {create_rendered_rows(0, 1_000)} } - ActionButton { name: "Create 10,000 rows", id: "runlots", onclick: {create_rendered_rows(0, 10_000)} } - ActionButton { name: "Append 1,000 rows", id: "add", onclick: {append_1_000_rows} } - ActionButton { name: "Update every 10th row", id: "update", onclick: {update_every_10th_row} } - ActionButton { name: "Clear", id: "clear", onclick: {clear_rows} } - ActionButton { name: "Swap rows", id: "swaprows", onclick: {swap_rows} } + ActionButton { name: "Create 1,000 rows", id: "run", + onclick: move || items.set(Label::new_list(1_000)), + } + ActionButton { name: "Create 10,000 rows", id: "runlots", + onclick: move || items.set(Label::new_list(10_000)), + } + ActionButton { name: "Append 1,000 rows", id: "add", + onclick: move || items.write().extend(Label::new_list(1_000)), + } + ActionButton { name: "Update every 10th row", id: "update", + onclick: move || items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"), + } + ActionButton { name: "Clear", id: "clear", + onclick: move || items.write().clear(), + } + ActionButton { name: "Swap rows", id: "swaprows", + onclick: move || items.write().swap(0, 998), + } } } } } table { tbody { - {rows} + {items.read().iter().enumerate().map(|(id, item)| { + let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""}; + rsx!(tr { class: "{is_in_danger}" + td { class:"col-md-1" } + td { class:"col-md-1", "{item.key}" } + td { class:"col-md-1", onclick: move |_| selected.set(Some(id)), + a { class: "lbl", {item.labels} } + } + td { class: "col-md-1" + a { class: "remove", onclick: move |_| { items.write().remove(id); }, + span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } + } + } + td { class: "col-md-6" } + }) + })} } } - span {} + // span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" } } }) }; @@ -77,59 +92,17 @@ static App: FC<()> = |(cx, _props)| { struct ActionButtonProps<'a> { name: &'static str, id: &'static str, - onclick: &'a dyn Fn(MouseEvent), + onclick: &'a dyn Fn(), } -fn ActionButton<'a>((cx, props): Scope<'a, ActionButtonProps>) -> Element<'a> { +fn ActionButton(cx: Context, props: &ActionButtonProps) -> Element { rsx!(cx, div { class: "col-sm-6 smallpad" - button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: {props.onclick}, + button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: move |_| (props.onclick)(), "{props.name}" } }) } -#[derive(PartialEq, Props)] -struct RowProps { - row_id: usize, - label: Rc, -} -fn Row((cx, props): Scope) -> Element { - rsx!(cx, 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: move |_| {/* remove */} - span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } - } - } - td { class: "col-md-6" } - }) -} - -use rand::prelude::*; -fn create_new_row_label(label: &mut String, rng: &mut SmallRng) -> Rc { - label.push_str(ADJECTIVES.choose(rng).unwrap()); - label.push(' '); - label.push_str(COLOURS.choose(rng).unwrap()); - label.push(' '); - label.push_str(NOUNS.choose(rng).unwrap()); - Rc::from(label.as_ref()) -} - -fn create_row_list(from: usize, num: usize) -> RowList { - let mut small_rng = SmallRng::from_entropy(); - let mut buf = String::with_capacity(35); - (from..num + from) - .map(|f| { - let o = (f, create_new_row_label(&mut buf, &mut small_rng)); - buf.clear(); - o - }) - .collect::() -} - static ADJECTIVES: &[&str] = &[ "pretty", "large", @@ -167,3 +140,24 @@ static NOUNS: &[&str] = &[ "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard", ]; + +// #[derive(PartialEq, Props)] +// struct RowProps<'a> { +// row_id: usize, +// label: &'a Label, +// } + +// fn Row(cx: Context, props: &RowProps) -> Element { +// rsx!(cx, tr { +// td { class:"col-md-1", "{props.row_id}" } +// td { class:"col-md-1", onclick: move |_| { /* run onselect */ } +// a { class: "lbl", {props.label.labels} } +// } +// td { class: "col-md-1" +// a { class: "remove", onclick: move |_| {/* remove */} +// span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } +// } +// } +// td { class: "col-md-6" } +// }) +// } diff --git a/examples/pattern_reducer.rs b/examples/pattern_reducer.rs index df28fdcf1..221e8eb0d 100644 --- a/examples/pattern_reducer.rs +++ b/examples/pattern_reducer.rs @@ -11,7 +11,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -pub static App: FC<()> = |(cx, _)| { +pub static App: FC<()> = |cx, _| { let state = use_state(cx, PlayerState::new); let is_playing = state.is_playing(); diff --git a/examples/web/basic.rs b/examples/web/basic.rs index 699305df2..4c4d52ce5 100644 --- a/examples/web/basic.rs +++ b/examples/web/basic.rs @@ -14,7 +14,7 @@ fn main() { dioxus_web::launch(APP, |c| c) } -static APP: FC<()> = |(cx, _)| { +static APP: FC<()> = |cx, _| { let mut count = use_state(cx, || 3); let content = use_state(cx, || String::from("h1")); let text_content = use_state(cx, || String::from("Hello, world!")); @@ -86,4 +86,4 @@ fn render_list(cx: Context, count: usize) -> Element { rsx!(cx, ul { {items} }) } -static CHILD: FC<()> = |(cx, _)| rsx!(cx, div {"hello child"}); +static CHILD: FC<()> = |cx, _| rsx!(cx, div {"hello child"}); diff --git a/examples/web/crm2.rs b/examples/web/crm2.rs index 0645f05fc..4dfe9db34 100644 --- a/examples/web/crm2.rs +++ b/examples/web/crm2.rs @@ -28,7 +28,7 @@ pub struct Client { pub description: String, } -static App: FC<()> = |(cx, _)| { +static App: FC<()> = |cx, _| { let scene = use_state(cx, || Scene::ClientsList); let clients = use_ref(cx, || vec![] as Vec); diff --git a/notes/FAQ.md b/notes/FAQ.md new file mode 100644 index 000000000..4e9ea00e4 --- /dev/null +++ b/notes/FAQ.md @@ -0,0 +1,32 @@ + +### Aren't VDOMs just pure overhead? Why not something like Solid or Svelte? +Remember: Dioxus is a library - not a compiler like Svelte. Plus, the inner VirtualDOM allows Dioxus to easily port into different runtimes, support SSR, and run remotely in the cloud. VDOMs tend to more ergonomic to work with and feel roughly like natural Rust code. The overhead of Dioxus is **extraordinarily** minimal... sure, there may be some overhead but on an order of magnitude lower than the time required to actually update the page. + + +### Isn't the overhead for interacting with the DOM from Wasm too much? +The overhead layer between Wasm and JS APIs is extremely poorly understood. Rust web benchmarks typically suffer from differences in how Rust and JS cache strings. In Dioxus, we solve most of these issues and our JS Framework Benchmark actually beats the Wasm Bindgen benchmark in many cases. Compared to a "pure vanilla JS" solution, Dioxus adds less than 5% of overhead and takes advantage of batched DOM manipulation. + +### Aren't Wasm binaries too huge to deploy in production? +Wasm binary sizes are another poorly understood characteristic of Rust web apps. 50kb of Wasm and 50kb of JS are _not_ made equally. In JS, the code must be downloaded _first_ and _then_ JIT-ted. Just-in-time compiling 50kb of JavaScript takes a while which is why 50kb of JavaScript sounds like a lot! However, with Wasm, the code is downloaded and JIT-ted _simultaneously_ through the magic of streaming compilation. By the time the 50kb of Rust is finished downloading, it is already ready to go. Again, Dioxus beats out many benchmarks with time-to-interactivity. + +For reference, Dioxus `hello-world` clocks in at around 70kb gzip or 60kb brotli, and Dioxus supports SSR. + +### Why hooks? Why not MVC, classes, traits, messages, etc? +There are plenty Rust Elm-like frameworks in the world - we were not interested in making another! Instead, we borrowed hooks from React. JS and Rust share many structural similarities, so if you're comfortable with React, then you'll be plenty comfortable with Dioxus. + +### Why a custom DSL? Why not just pure function calls? +The `RSX` DSL is _barely_ a DSL. Rustaceans will find the DSL very similar to simply assembling nested structs, but without the syntactical overhead of "Default" everywhere or having to jump through hoops with the builder pattern. Between RSX, HTML, the Raw Factory API, and the NodeBuilder syntax, there's plenty of options to choose from. + +### What are the build times like? Why on earth would I choose Rust instead of JS/TS/Elm? +Dioxus builds as roughly as fast as a complex WebPack-TypeScript site. Compile times will be slower than an equivalent TypeScript site, but not unbearably slow. The Wasm compiler backend for Rust is very fast. Iterating on small components is basically instant and larger apps takes a few seconds. In practice, the compiler guarantees of Rust balance out the rebuild times. + +### What about Yew/Seed/Sycamore/Dominator/Dodrio/Percy? +- Yew and Seed use an Elm-like pattern and don't support SSR or any alternate rendering platforms +- Sycamore and Dominator are more like SolidJS/Svelte, requiring no VDOM but has less naturally-Rusty state management +- Percy isn't quite mature yet +- Dodrio is the spiritual predecessor of Dioxus, but is currently an archived research project without the batteries of Dioxus + +### How do the mobile and desktop renderers work? Is it Electron? +Currently, Dioxus uses your device's native WebView library to draw the page. None of your app code is actually running in the WebView thread, so you can access system resources instead of having to go through something like NodeJS. This means your app will use Safari on macOS/iOS, Edge (Chromium) on Windows, and whatever is the default Web Browser for Linux and Android. Because your code is compiled and running natively, performance is not a problem. You will have to use the various "Escape Hatches" to use browser-native APIs (like WebGL) and work around visual differences in how Safari and Chrome render the page. + +In the future, we are interested in using Webrenderer to provide a fully native renderer without having to go through the system WebView library. In practice, Dioxus mobile and desktop are great for CRUD-style apps, but the ergonomic cross-platform APIs (GPS, Camera, etc) are not there yet. diff --git a/notes/Parity.md b/notes/Parity.md index 9842b7963..5cc0851c9 100644 --- a/notes/Parity.md +++ b/notes/Parity.md @@ -12,7 +12,7 @@ https://github.com/rustwasm/gloo For example, resize observer would function like this: ```rust -pub static Example: FC<()> = |(cx, props)|{ +pub static Example: FC<()> = |cx, props|{ let observer = use_resize_observer(); cx.render(rsx!( diff --git a/notes/SOLVEDPROBLEMS.md b/notes/SOLVEDPROBLEMS.md index d7bc60344..3c42094ee 100644 --- a/notes/SOLVEDPROBLEMS.md +++ b/notes/SOLVEDPROBLEMS.md @@ -153,13 +153,13 @@ Notice that LiveComponent receivers (the client-side interpretation of a LiveCom The `VNodeTree` type is a very special type that allows VNodes to be created using a pluggable allocator. The html! macro creates something that looks like: ```rust -pub static Example: FC<()> = |(cx, props)|{ +pub static Example: FC<()> = |cx, props|{ html! {
"blah"
} }; // expands to... -pub static Example: FC<()> = |(cx, props)|{ +pub static Example: FC<()> = |cx, props|{ // This function converts a Fn(allocator) -> DomTree closure to a VNode struct that will later be evaluated. html_macro_to_vnodetree(move |allocator| { let mut node0 = allocator.alloc(VElement::div); @@ -313,7 +313,7 @@ Here's how react does it: Any "dirty" node causes an entire subtree render. Calling "setState" at the very top will cascade all the way down. This is particularly bad for this component design: ```rust -static APP: FC<()> = |(cx, props)|{ +static APP: FC<()> = |cx, props|{ let title = use_context(Title); cx.render(html!{
@@ -334,7 +334,7 @@ static APP: FC<()> = |(cx, props)|{
}) }; -static HEAVY_LIST: FC<()> = |(cx, props)|{ +static HEAVY_LIST: FC<()> = |cx, props|{ cx.render({ {0.100.map(i => )} }) @@ -378,7 +378,7 @@ struct Props { } -static Component: FC = |(cx, props)|{ +static Component: FC = |cx, props|{ } ``` diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index a218ccc4d..6e754a163 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -30,7 +30,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token /// /// ## Complete Reference Guide: /// ``` -/// const Example: FC<()> = |(cx, props)|{ +/// const Example: FC<()> = |cx, props|{ /// let formatting = "formatting!"; /// let formatting_tuple = ("a", "b"); /// let lazy_fmt = format_args!("lazily formatted text"); diff --git a/packages/core-macro/src/router.rs b/packages/core-macro/src/router.rs index 2ab9f3a17..3edad75e3 100644 --- a/packages/core-macro/src/router.rs +++ b/packages/core-macro/src/router.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use proc_macro2::TokenStream; use quote::quote; use syn::parse::{Parse, ParseStream}; @@ -109,140 +111,104 @@ fn parse_variants_attributes( } 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 - }; + // 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) - } - }); + // 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 { - match path { - #(#from_path_matches),*, - _ => ::std::option::Option::None, - } - } - } - } + // quote! { + // fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option { + // 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(); + // 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::>(); + // 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::>(); - for field in fields.iter() { - // :param -> {param} - // so we can pass it to `format!("...", param)` - right = right.replace(&format!(":{}", field), &format!("{{{}}}", field)) - } + // 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! { + // 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),*, - } - } - } - } + // 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, + // 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(), - ); + // let from_path = input.build_from_path(); + // let to_path = input.build_to_path(); 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 { + + // fn recognize(pathname: &str) -> ::std::option::Option { + // todo!() + // // ::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 + // } // } - - #[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 { - #not_found_route - } - - // fn current_route() -> ::std::option::Option { - // #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow())) - // } - - fn recognize(pathname: &str) -> ::std::option::Option { - todo!() - // ::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; - // }); - // } - } } } diff --git a/packages/core-macro/src/rsx/body.rs b/packages/core-macro/src/rsx/body.rs index cafdb696b..83d2886e6 100644 --- a/packages/core-macro/src/rsx/body.rs +++ b/packages/core-macro/src/rsx/body.rs @@ -49,19 +49,17 @@ impl ToTokens for CallBody { match &self.custom_context { // The `in cx` pattern allows directly rendering Some(ident) => out_tokens.append_all(quote! { - #ident.render(NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode { + #ident.render(LazyNodes::new_some(move |__cx: NodeFactory| -> VNode { use dioxus_elements::{GlobalAttributes, SvgAttributes}; #inner })) }), // Otherwise we just build the LazyNode wrapper None => out_tokens.append_all(quote! { - { - NodeFactory::annotate_lazy(move |__cx: NodeFactory| -> VNode { - use dioxus_elements::{GlobalAttributes, SvgAttributes}; - #inner - }) - } + LazyNodes::new_some(move |__cx: NodeFactory| -> VNode { + use dioxus_elements::{GlobalAttributes, SvgAttributes}; + #inner + }) }), }; } diff --git a/packages/core-macro/src/rsxtemplate.rs b/packages/core-macro/src/rsxtemplate.rs index e228929cf..1f712997d 100644 --- a/packages/core-macro/src/rsxtemplate.rs +++ b/packages/core-macro/src/rsxtemplate.rs @@ -72,7 +72,7 @@ impl ToTokens for RsxTemplate { // // create a lazy tree that accepts a bump allocator // let final_tokens = quote! { - // dioxus::prelude::LazyNodes::new(move |(cx, props)|{ + // dioxus::prelude::LazyNodes::new(move |cx, props|{ // let bump = &cx.bump(); // #new_toks diff --git a/packages/core-macro/styles/calc.rs b/packages/core-macro/styles/calc.rs deleted file mode 100644 index f52b3eda3..000000000 --- a/packages/core-macro/styles/calc.rs +++ /dev/null @@ -1,264 +0,0 @@ -//! The `calc` functionality. -use crate::LengthPercentage; -use ::{ - proc_macro2::TokenStream, - quote::{quote, ToTokens}, - std::fmt, - syn::{ - custom_keyword, parenthesized, - parse::{Parse, ParseStream}, - Token, - }, -}; - -/// Values that can be a calculaion (currently restricted to length & percentages) -#[derive(Debug, Clone, PartialEq)] -pub enum Calc { - Calculated(CalcSum), - Normal(LengthPercentage), -} - -impl fmt::Display for Calc { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Calc::Calculated(inner) => write!(f, "calc({})", inner), - Calc::Normal(inner) => write!(f, "{}", inner), - } - } -} - -impl Parse for Calc { - fn parse(s: ParseStream) -> syn::Result { - custom_keyword!(calc); - if s.peek(calc) { - s.parse::()?; - let content; - parenthesized!(content in s); - Ok(Calc::Calculated(content.parse()?)) - } else { - Ok(Calc::Normal(s.parse()?)) - } - } -} - -impl ToTokens for Calc { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Calc::Calculated(inner) => quote!(style::Calc::Calculated(#inner)), - Calc::Normal(inner) => quote!(style::Calc::Normal(#inner)), - }); - } -} - -#[test] -fn test_calc() { - for (input, output) in vec![ - ("calc(10% - 20\"em\")", "calc(10% - 20em)"), - ("calc(100% + 5px)", "calc(100% + 5px)"), - ("calc(100% - 60px)", "calc(100% - 60px)"), - ] { - assert_eq!(&syn::parse_str::(input).unwrap().to_string(), output); - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct CalcSum { - pub first: CalcProduct, - pub rest: Vec, -} - -impl fmt::Display for CalcSum { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.first)?; - for op in self.rest.iter() { - write!(f, "{}", op)?; - } - Ok(()) - } -} - -impl Parse for CalcSum { - fn parse(s: ParseStream) -> syn::Result { - let first: CalcProduct = s.parse()?; - let mut rest: Vec = vec![]; - while SumOp::peek(s) { - rest.push(s.parse()?); - } - Ok(CalcSum { first, rest }) - } -} - -impl ToTokens for CalcSum { - fn to_tokens(&self, tokens: &mut TokenStream) { - let first = &self.first; - let rest = self.rest.iter(); - tokens.extend(quote! { - style::calc::CalcSum { - first: #first, - rest: vec![#(#rest,)*] - } - }); - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum SumOp { - Add(CalcProduct), - Sub(CalcProduct), -} - -impl SumOp { - fn peek(s: ParseStream) -> bool { - s.peek(Token![+]) || s.peek(Token![-]) - } -} - -impl fmt::Display for SumOp { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - SumOp::Add(inner) => write!(f, " + {}", inner), - SumOp::Sub(inner) => write!(f, " - {}", inner), - } - } -} - -impl Parse for SumOp { - fn parse(s: ParseStream) -> syn::Result { - let lookahead = s.lookahead1(); - if lookahead.peek(Token![+]) { - s.parse::()?; - Ok(SumOp::Add(s.parse()?)) - } else if lookahead.peek(Token![-]) { - s.parse::()?; - Ok(SumOp::Sub(s.parse()?)) - } else { - Err(lookahead.error()) - } - } -} - -impl ToTokens for SumOp { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - SumOp::Add(inner) => quote!(style::SumOp::Add(#inner)), - SumOp::Sub(inner) => quote!(style::SumOp::Sub(#inner)), - }); - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct CalcProduct { - pub first: CalcValue, - pub rest: Vec, -} - -impl fmt::Display for CalcProduct { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.first)?; - for op in self.rest.iter() { - write!(f, "{}", op)?; - } - Ok(()) - } -} - -impl Parse for CalcProduct { - fn parse(s: ParseStream) -> syn::Result { - let first: CalcValue = s.parse()?; - let mut rest: Vec = vec![]; - while ProductOp::peek(s) { - rest.push(s.parse()?); - } - Ok(CalcProduct { first, rest }) - } -} - -impl ToTokens for CalcProduct { - fn to_tokens(&self, tokens: &mut TokenStream) { - let first = &self.first; - let rest = self.rest.iter(); - tokens.extend(quote! { - style::calc::CalcProduct { - first: #first, - rest: vec![#(#rest,)*] - } - }); - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum ProductOp { - Mul(CalcValue), - // todo Div(Number), -} - -impl ProductOp { - pub fn peek(s: ParseStream) -> bool { - s.peek(Token![*]) // || s.peek(Token[/]) - } -} - -impl fmt::Display for ProductOp { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ProductOp::Mul(inner) => write!(f, "*{}", inner), - //ProductOp::Div(inner) => write!(f, "/{}", inner), - } - } -} - -impl Parse for ProductOp { - fn parse(s: ParseStream) -> syn::Result { - let lookahead = s.lookahead1(); - if lookahead.peek(Token![*]) { - s.parse::()?; - Ok(ProductOp::Mul(s.parse()?)) - /* - } else if lookahead.peek(Token![/]) { - s.parse::()?; - Ok(ProductOp::Div(s.parse()?)) - */ - } else { - Err(lookahead.error()) - } - } -} - -impl ToTokens for ProductOp { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - ProductOp::Mul(inner) => quote!(style::ProductOp::Mul(#inner)), - //ProductOp::Div(inner) => quote!(style::ProductOp::Div(#inner)), - }); - } -} - -#[derive(Debug, Clone, PartialEq)] -pub enum CalcValue { - LengthPercentage(LengthPercentage), - // todo more variants -} - -impl Parse for CalcValue { - fn parse(s: ParseStream) -> syn::Result { - Ok(CalcValue::LengthPercentage(s.parse()?)) - } -} - -impl fmt::Display for CalcValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - CalcValue::LengthPercentage(inner) => write!(f, "{}", inner), - } - } -} - -impl ToTokens for CalcValue { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - CalcValue::LengthPercentage(inner) => { - quote!(style::CalcValue::LengthPercentage(#inner)) - } - }); - } -} diff --git a/packages/core-macro/styles/codegen.rs b/packages/core-macro/styles/codegen.rs deleted file mode 100644 index d4a399452..000000000 --- a/packages/core-macro/styles/codegen.rs +++ /dev/null @@ -1,1150 +0,0 @@ -use crate::*; -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; - -macro_rules! path { - ($($t:tt)+) => { - ::quote::quote!(::style:: $($t)+) - }; -} - -impl ToTokens for DynamicStyles { - fn to_tokens(&self, tokens: &mut TokenStream) { - let parts = self - .rules - .iter() - .filter(|style| !style.is_dummy()) - .map(|style| style.to_token_stream()); - tokens.extend(quote! { - { - let mut styles = style::Styles::new(); - #(styles.push(#parts);)* - styles - } - }) - } -} - -impl ToTokens for DynamicStyle { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - DynamicStyle::Dynamic(block) => quote!(#block), - DynamicStyle::Literal(lit) => quote!(#lit), - }) - } -} - -impl ToTokens for Style { - fn to_tokens(&self, tokens: &mut TokenStream) { - let path = quote!(::style::Style::); - tokens.extend(match self { - Style::Dummy => quote!(#path Dummy), - Style::Unchecked(v) => quote!(#path Unchecked(String::from(#v))), - - Style::AlignContent(v) => quote!(#path AlignContent(#v)), - Style::AlignItems(v) => quote!(#path AlignItems(#v)), - Style::AlignSelf(v) => quote!(#path AlignSelf(#v)), - // all - // background - Style::BackgroundAttachment(v) => quote!(#path BackgroundAttachment(#v)), - Style::BackgroundBlendMode(v) => quote!(#path BackgroundBlendMode(#v)), - Style::BackgroundClip(v) => quote!(#path BackgroundClip(#v)), - Style::BackgroundColor(v) => quote!(#path BackgroundColor(#v)), - Style::BackgroundImage(v) => quote!(#path BackgroundImage(#v)), - Style::BackgroundOrigin(v) => quote!(#path BackgroundOrigin(#v)), - Style::BackgroundPosition(v) => quote!(#path BackgroundPosition(#v)), - Style::BackgroundRepeat(v) => quote!(#path BackgroundRepeat(#v)), - Style::BackgroundSize(v) => quote!(#path BackgroundSize(#v)), - Style::Border(v) => quote!(#path Border(#v)), - Style::BorderBottom(v) => quote!(#path BorderBottom(#v)), - Style::BorderBottomColor(v) => quote!(#path BorderBottomColor(#v)), - Style::BorderBottomLeftRadius(v) => quote!(#path BorderBottomLeftRadius(#v)), - Style::BorderBottomRightRadius(v) => quote!(#path BorderBottomRightRadius(#v)), - Style::BorderBottomStyle(v) => quote!(#path BorderBottomStyle(#v)), - Style::BorderBottomWidth(v) => quote!(#path BorderBottomWidth(#v)), - Style::BorderCollapse(v) => quote!(#path BorderCollapse(#v)), - Style::BorderColor(v) => quote!(#path BorderColor(#v)), - // border-image - // border-image-outset - // border-image-repeat - // border-image-slice - // border-image-source - // border-image-width - Style::BorderLeft(v) => quote!(#path BorderLeft(#v)), - Style::BorderLeftColor(v) => quote!(#path BorderLeftColor(#v)), - Style::BorderLeftStyle(v) => quote!(#path BorderLeftStyle(#v)), - Style::BorderLeftWidth(v) => quote!(#path BorderLeftWidth(#v)), - Style::BorderRadius(v) => quote!(#path BorderRadius(#v)), - Style::BorderRight(v) => quote!(#path BorderRight(#v)), - Style::BorderRightColor(v) => quote!(#path BorderRightColor(#v)), - Style::BorderRightStyle(v) => quote!(#path BorderRightStyle(#v)), - Style::BorderRightWidth(v) => quote!(#path BorderRightWidth(#v)), - // border-spacing - Style::BorderStyle(v) => quote!(#path BorderStyle(#v)), - Style::BorderTop(v) => quote!(#path BorderTop(#v)), - Style::BorderTopColor(v) => quote!(#path BorderTopColor(#v)), - Style::BorderTopLeftRadius(v) => quote!(#path BorderTopLeftRadius(#v)), - Style::BorderTopRightRadius(v) => quote!(#path BorderTopRightRadius(#v)), - Style::BorderTopStyle(v) => quote!(#path BorderTopStyle(#v)), - Style::BorderTopWidth(v) => quote!(#path BorderTopWidth(#v)), - Style::BorderWidth(v) => quote!(#path BorderWidth(#v)), - Style::Bottom(v) => quote!(#path Bottom(#v)), - // box-decoration-break - Style::BoxShadow(v) => quote!(#path BoxShadow(#v)), - Style::BoxSizing(v) => quote!(#path BoxSizing(#v)), - // break-after - // break-before - // break-inside - // caption-side - // caret-color - Style::Clear(v) => quote!(#path Clear(#v)), - // clip - // clip-path - // clip-rule - Style::ColumnCount(v) => quote!(#path ColumnCount(#v)), - Style::Color(v) => quote!(#path Color(#v)), - // contain - // content - // counter-increment - // counter-reset - // cue - // cue-after - // cue-before - Style::Cursor(v) => quote!(#path Cursor(#v)), - // direction - Style::Display(v) => quote!(#path Display(#v)), - // elevation - // empty-cells - // flex - Style::FlexBasis(v) => quote!(#path FlexBasis(#v)), - Style::FlexDirection(v) => quote!(#path FlexDirection(#v)), - // flex-flow - Style::FlexGrow(v) => quote!(#path FlexGrow(#v)), - Style::FlexShrink(v) => quote!(#path FlexShrink(#v)), - Style::FlexWrap(v) => quote!(#path FlexWrap(#v)), - Style::Float(v) => quote!(#path Float(#v)), - // font - Style::FontFamily(v) => quote!(#path FontFamily(#v)), - // font-feature-settings - // font-kerning - Style::FontSize(v) => quote!(#path FontSize(#v)), - // font-size-adjust - // font-stretch - Style::FontStyle(v) => quote!(#path FontStyle(#v)), - // font-synthesis - // font-variant - // font-variant-caps - // font-variant-east-asian - // font-variant-ligatures - // font-variant-numeric - // font-variant-position - Style::FontWeight(v) => quote!(#path FontWeight(#v)), - // glyph-orientation-vertical - // grid - // grid-area - // grid-auto-columns - // grid-auto-flow - // grid-auto-rows - // grid-column - // grid-column-end - // grid-column-start - // grid-row - // grid-row-end - // grid-row-start - // grid-template - // grid-template-areas - // grid-template-columns - // grid-template-rows - Style::Height(v) => quote!(#path Height(#v)), - // image-orientation - // image-rendering - // isolation - Style::JustifyContent(v) => quote!(#path JustifyContent(#v)), - Style::Left(v) => quote!(#path Left(#v)), - // letter-spacing - Style::LineHeight(v) => quote!(#path LineHeight(#v)), - // list-style - // list-style-image - // list-style-position - Style::ListStyleType(v) => quote!(#path ListStyleType(#v)), - Style::Margin(v) => quote!(#path Margin(#v)), - Style::MarginBottom(v) => quote!(#path MarginBottom(#v)), - Style::MarginLeft(v) => quote!(#path MarginLeft(#v)), - Style::MarginRight(v) => quote!(#path MarginRight(#v)), - Style::MarginTop(v) => quote!(#path MarginTop(#v)), - // mask - // mask-border - // mask-border-mode - // mask-border-outset - // mask-border-repeat - // mask-border-slice - // mask-border-source - // mask-border-width - // mask-clip - // mask-composite - // mask-image - // mask-mode - // mask-origin - // mask-position - // mask-repeat - // mask-size - // mask-type - Style::MaxHeight(v) => quote!(#path MaxHeight(#v)), - Style::MaxWidth(v) => quote!(#path MaxWidth(#v)), - Style::MinHeight(v) => quote!(#path MinHeight(#v)), - Style::MinWidth(v) => quote!(#path MinWidth(#v)), - // mix-blend-mode - Style::ObjectFit(v) => quote!(#path ObjectFit(#v)), - // object-position - // opacity - // order - // orphans - // outline - // outline-color - // outline-offset - // outline-style - // outline-width - Style::Overflow(v) => quote!(#path Overflow(#v)), - Style::OverflowX(v) => quote!(#path OverflowX(#v)), - Style::OverflowY(v) => quote!(#path OverflowY(#v)), - Style::Padding(v) => quote!(#path Padding(#v)), - Style::PaddingBottom(v) => quote!(#path PaddingBottom(#v)), - Style::PaddingLeft(v) => quote!(#path PaddingLeft(#v)), - Style::PaddingRight(v) => quote!(#path PaddingRight(#v)), - Style::PaddingTop(v) => quote!(#path PaddingTop(#v)), - // page-break-after - // page-break-before - // page-break-inside - // pause - // pause-after - // pause-before - // pitch - // pitch-range - // play-during - Style::Position(v) => quote!(#path Position(#v)), - // quotes - Style::Resize(v) => quote!(#path Resize(#v)), - // richness - Style::Right(v) => quote!(#path Right(#v)), - // scroll-margin - // scroll-margin-block - // scroll-margin-block-end - // scroll-margin-block-start - // scroll-margin-bottom - // scroll-margin-inline - // scroll-margin-inline-end - // scroll-margin-inline-start - // scroll-margin-left - // scroll-margin-right - // scroll-margin-top - // scroll-padding - // scroll-padding-block - // scroll-padding-block-end - // scroll-padding-block-start - // scroll-padding-bottom - // scroll-padding-inline - // scroll-padding-inline-end - // scroll-padding-inline-start - // scroll-padding-left - // scroll-padding-right - // scroll-padding-top - // scroll-snap-align - // scroll-snap-stop - // scroll-snap-type - // shape-image-threshold - // shape-margin - // shape-outside - // speak - // speak-header - // speak-numeral - // speak-punctuation - // speech-rate - // stress - // table-layout - Style::TextAlign(v) => quote!(#path TextAlign(#v)), - // text-combine-upright - // text-decoration - // text-decoration-color - // text-decoration-line - // text-decoration-style - // text-emphasis - // text-emphasis-color - // text-emphasis-position - // text-emphasis-style - // text-indent - // text-orientation - // text-overflow - // text-shadow - // text-transform - // text-underline-position - Style::Top(v) => quote!(#path Top(#v)), - // transform - // transform-box - // transform-origin - // unicode-bidi - // vertical-align - // visibility - // voice-family - // volume - // white-space - Style::WhiteSpace(v) => quote!(#path WhiteSpace(#v)), - Style::Widows(v) => quote!(#path Widows(#v)), - Style::Width(v) => quote!(#path Width(#v)), - // will-change - // word-spacing - // writing-mode - // z-index - }); - } -} - -impl ToTokens for AlignContent { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - AlignContent::FlexStart => path!(AlignContent::FlexStart), - AlignContent::Center => path!(AlignContent::Center), - AlignContent::FlexEnd => path!(style::AlignContent::FlexEnd), - AlignContent::SpaceAround => path!(AlignContent::SpaceAround), - AlignContent::SpaceBetween => path!(AlignContent::SpaceBetween), - AlignContent::Stretch => path!(AlignContent::Stretch), - }); - } -} - -impl ToTokens for Cursor { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Cursor::Auto => path!(Cursor::Auto), - Cursor::Default => path!(Cursor::Default), - Cursor::None => path!(Cursor::None), - Cursor::ContextMenu => path!(Cursor::ContextMenu), - Cursor::Help => path!(Cursor::Help), - Cursor::Pointer => path!(Cursor::Pointer), - Cursor::Progress => path!(Cursor::Progress), - Cursor::Wait => path!(Cursor::Wait), - Cursor::Cell => path!(Cursor::Cell), - Cursor::Crosshair => path!(Cursor::Crosshair), - Cursor::Text => path!(Cursor::Text), - Cursor::VerticalText => path!(Cursor::VerticalText), - Cursor::Alias => path!(Cursor::Alias), - Cursor::Copy => path!(Cursor::Copy), - Cursor::Move => path!(Cursor::Move), - Cursor::NoDrop => path!(Cursor::NoDrop), - Cursor::NotAllowed => path!(Cursor::NotAllowed), - Cursor::Grab => path!(Cursor::Grab), - Cursor::Grabbing => path!(Cursor::Grabbing), - Cursor::EResize => path!(Cursor::EResize), - Cursor::NResize => path!(Cursor::NResize), - Cursor::NEResize => path!(Cursor::NEResize), - Cursor::NWResize => path!(Cursor::NWResize), - Cursor::SResize => path!(Cursor::SResize), - Cursor::SEResize => path!(Cursor::SEResize), - Cursor::SWResize => path!(Cursor::SWResize), - Cursor::WResize => path!(Cursor::WResize), - Cursor::EWResize => path!(Cursor::EWResize), - Cursor::NSResize => path!(Cursor::NSResize), - Cursor::NESWResize => path!(Cursor::NESWResize), - Cursor::NWSEResize => path!(Cursor::NWSEResize), - Cursor::ColResize => path!(Cursor::ColResize), - Cursor::RowResize => path!(Cursor::RowResize), - Cursor::AllScroll => path!(Cursor::AllScroll), - Cursor::ZoomIn => path!(Cursor::ZoomIn), - Cursor::ZoomOut => path!(Cursor::ZoomOut), - }) - } -} - -impl ToTokens for Display { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Display::Block => path!(Display::Block), - Display::Flex => path!(Display::Flex), - Display::Inline => path!(Display::Inline), - }); - } -} - -impl ToTokens for FlexBasis { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - FlexBasis::Width(v) => path!(FlexBasis::Width(#v)), - FlexBasis::Content => path!(FlexBasis::Content), - }); - } -} - -impl ToTokens for FlexDirection { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - FlexDirection::Row => path!(FlexDirection::Row), - FlexDirection::Column => path!(FlexDirection::Column), - }); - } -} - -impl ToTokens for FlexWrap { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - FlexWrap::Wrap => path!(FlexWrap::Wrap), - FlexWrap::Nowrap => path!(FlexWrap::Nowrap), - }); - } -} - -impl ToTokens for AlignItems { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - AlignItems::Normal => path!(AlignItems::Normal), - AlignItems::Stretch => path!(AlignItems::Stretch), - AlignItems::Center => path!(AlignItems::Center), - AlignItems::Start => path!(AlignItems::Start), - AlignItems::End => path!(AlignItems::End), - AlignItems::FlexStart => path!(AlignItems::FlexStart), - AlignItems::FlexEnd => path!(AlignItems::FlexEnd), - AlignItems::Baseline => path!(AlignItems::Baseline), - AlignItems::FirstBaseline => path!(AlignItems::FirstBaseline), - AlignItems::LastBaseline => path!(AlignItems::LastBaseline), - AlignItems::SafeCenter => path!(AlignItems::SafeCenter), - AlignItems::UnsafeCenter => path!(AlignItems::UnsafeCenter), - }); - } -} - -impl ToTokens for AlignSelf { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - AlignSelf::Auto => path!(AlignSelf::Auto), - AlignSelf::Normal => path!(AlignSelf::Normal), - AlignSelf::Center => path!(AlignSelf::Center), - AlignSelf::Start => path!(AlignSelf::Start), - AlignSelf::End => path!(AlignSelf::End), - AlignSelf::SelfStart => path!(AlignSelf::SelfStart), - AlignSelf::SelfEnd => path!(AlignSelf::SelfEnd), - AlignSelf::FlexStart => path!(AlignSelf::FlexStart), - AlignSelf::FlexEnd => path!(AlignSelf::FlexEnd), - AlignSelf::Baseline => path!(AlignSelf::Baseline), - AlignSelf::FirstBaseline => path!(AlignSelf::FirstBaseline), - AlignSelf::LastBaseline => path!(AlignSelf::LastBaseline), - AlignSelf::Stretch => path!(AlignSelf::Stretch), - AlignSelf::SafeCenter => path!(AlignSelf::SafeCenter), - AlignSelf::UnsafeCenter => path!(AlignSelf::UnsafeCenter), - }); - } -} - -impl ToTokens for BackgroundAttachment { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BackgroundAttachment::Scroll => path!(BackgroundAttachment::Scroll), - BackgroundAttachment::Fixed => path!(BackgroundAttachment::Fixed), - BackgroundAttachment::Local => path!(BackgroundAttachment::Local), - }) - } -} - -impl ToTokens for BlendMode { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BlendMode::Normal => path!(BlendMode::Normal), - BlendMode::Multiply => path!(BlendMode::Multiply), - BlendMode::Screen => path!(BlendMode::Screen), - BlendMode::Overlay => path!(BlendMode::Overlay), - BlendMode::Darken => path!(BlendMode::Darken), - BlendMode::Lighten => path!(BlendMode::Lighten), - BlendMode::ColorDodge => path!(BlendMode::ColorDodge), - BlendMode::ColorBurn => path!(BlendMode::ColorBurn), - BlendMode::HardLight => path!(BlendMode::HardLight), - BlendMode::SoftLight => path!(BlendMode::SoftLight), - BlendMode::Difference => path!(BlendMode::Difference), - BlendMode::Exclusion => path!(BlendMode::Exclusion), - BlendMode::Hue => path!(BlendMode::Hue), - BlendMode::Saturation => path!(BlendMode::Saturation), - BlendMode::Color => path!(BlendMode::Color), - BlendMode::Luminosity => path!(BlendMode::Luminosity), - }) - } -} - -impl ToTokens for BackgroundBox { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BackgroundBox::BorderBox => path!(BackgroundBox::BorderBox), - BackgroundBox::PaddingBox => path!(BackgroundBox::PaddingBox), - BackgroundBox::ContentBox => path!(BackgroundBox::ContentBox), - }) - } -} - -impl ToTokens for BackgroundImage { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BackgroundImage::None => path!(BackgroundImage::None), - BackgroundImage::Url(url) => path!(BackgroundImage::Url(#url)), - }) - } -} - -impl ToTokens for BackgroundPosition { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BackgroundPosition::Top => path!(BackgroundPosition::Top), - BackgroundPosition::Bottom => path!(BackgroundPosition::Bottom), - BackgroundPosition::Left => path!(BackgroundPosition::Left), - BackgroundPosition::Right => path!(BackgroundPosition::Right), - BackgroundPosition::Center => path!(BackgroundPosition::Center), - }) - } -} - -impl ToTokens for BackgroundRepeat { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BackgroundRepeat::RepeatX => path!(BackgroundRepeat::RepeatX), - BackgroundRepeat::RepeatY => path!(BackgroundRepeat::RepeatY), - BackgroundRepeat::SingleOrDouble(v) => path!(BackgroundRepeat::SingleOrDouble(#v)), - }) - } -} - -impl ToTokens for BgRepeatPart { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BgRepeatPart::Repeat => path!(BgRepeatPart::Repeat), - BgRepeatPart::Space => path!(BgRepeatPart::Space), - BgRepeatPart::Round => path!(BgRepeatPart::Round), - BgRepeatPart::NoRepeat => path!(BgRepeatPart::NoRepeat), - }) - } -} - -impl ToTokens for BackgroundSize { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BackgroundSize::Cover => path!(BackgroundSize::Cover), - BackgroundSize::Contain => path!(BackgroundSize::Contain), - BackgroundSize::SingleOrDouble(v) => path!(BackgroundSize::SingleOrDouble(#v)), - }) - } -} - -impl ToTokens for BorderCollapse { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BorderCollapse::Collapse => path!(BorderCollapse::Collapse), - BorderCollapse::Separate => path!(BorderCollapse::Separate), - }) - } -} - -impl ToTokens for JustifyContent { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - JustifyContent::FlexStart => path!(JustifyContent::FlexStart), - JustifyContent::Center => path!(JustifyContent::Center), - JustifyContent::FlexEnd => path!(JustifyContent::FlexEnd), - JustifyContent::SpaceAround => path!(JustifyContent::SpaceAround), - JustifyContent::SpaceBetween => path!(JustifyContent::SpaceBetween), - }); - } -} - -impl ToTokens for Float { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Float::None => path!(Float::None), - Float::Left => path!(Float::Left), - Float::Right => path!(Float::Right), - Float::InlineStart => path!(Float::InlineStart), - Float::InlineEnd => path!(Float::InlineEnd), - }) - } -} - -impl ToTokens for FontWeight { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - FontWeight::Normal => path!(FontWeight::Normal), - FontWeight::Bold => path!(FontWeight::Bold), - FontWeight::Lighter => path!(FontWeight::Lighter), - FontWeight::Bolder => path!(FontWeight::Bolder), - FontWeight::Number(v) => path!(FontWeight::Number(#v)), - }); - } -} - -impl ToTokens for Font { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Font::Named(inner) => path!(Font::Named(String::from(#inner))), - Font::Serif => path!(Font::Serif), - Font::SansSerif => path!(Font::SansSerif), - Font::Cursive => path!(Font::Cursive), - Font::Fantasy => path!(Font::Fantasy), - Font::Monospace => path!(Font::Monospace), - }) - } -} - -impl ToTokens for FontSize { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - FontSize::XXSmall => path!(FontSize::XXSmall), - FontSize::XSmall => path!(FontSize::XSmall), - FontSize::Small => path!(FontSize::Small), - FontSize::Medium => path!(FontSize::Medium), - FontSize::Large => path!(FontSize::Large), - FontSize::XLarge => path!(FontSize::XLarge), - FontSize::XXLarge => path!(FontSize::XXLarge), - FontSize::XXXLarge => path!(FontSize::XXXLarge), - FontSize::Larger => path!(FontSize::Larger), - FontSize::Smaller => path!(FontSize::Smaller), - FontSize::LengthPercentage(v) => path!(FontSize::LengthPercentage(#v)), - }); - } -} - -impl ToTokens for FontStyle { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - FontStyle::Normal => path!(FontStyle::Normal), - FontStyle::Italic => path!(FontStyle::Italic), - FontStyle::Oblique => path!(FontStyle::Oblique), - }); - } -} - -impl ToTokens for Border { - fn to_tokens(&self, tokens: &mut TokenStream) { - let line_width = match self.line_width { - Some(line_width) => quote!(Some(#line_width)), - None => quote!(None), - }; - let line_style = match self.line_style { - Some(line_style) => quote!(Some(#line_style)), - None => quote!(None), - }; - let color = match self.color { - Some(color) => quote!(Some(#color)), - None => quote!(None), - }; - tokens.extend(quote!( - style::Border { - line_width: #line_width, - line_style: #line_style, - color: #color, - } - )) - } -} - -impl ToTokens for BoxShadow { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BoxShadow::None => path!(BoxShadow::None), - BoxShadow::Shadows(list) => path!(BoxShadow::Shadows(#list)), - }); - } -} - -impl ToTokens for BoxSizing { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - BoxSizing::BorderBox => path!(BoxSizing::BorderBox), - BoxSizing::ContentBox => path!(BoxSizing::ContentBox), - }); - } -} - -impl ToTokens for Clear { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Clear::None => path!(Clear::None), - Clear::Left => path!(Clear::Left), - Clear::Right => path!(Clear::Right), - Clear::Both => path!(Clear::Both), - Clear::InlineStart => path!(Clear::InlineStart), - Clear::InlineEnd => path!(Clear::InlineEnd), - }) - } -} - -impl ToTokens for ColumnCount { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - ColumnCount::Auto => path!(ColumnCount::Auto), - ColumnCount::Fixed(v) => path!(ColumnCount::Fixed(#v)), - }) - } -} - -impl ToTokens for Overflow { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Overflow::Both(v) => path!(Overflow::Both(#v)), - Overflow::XY(x, y) => path!(Overflow::XY(#x, #y)), - }) - } -} - -impl ToTokens for OverflowXY { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - OverflowXY::Visible => path!(OverflowXY::Visible), - OverflowXY::Hidden => path!(OverflowXY::Hidden), - OverflowXY::Clip => path!(OverflowXY::Clip), - OverflowXY::Scroll => path!(OverflowXY::Scroll), - OverflowXY::Auto => path!(OverflowXY::Auto), - }) - } -} - -impl ToTokens for ObjectFit { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - ObjectFit::Fill => path!(ObjectFit::Fill), - ObjectFit::None => path!(ObjectFit::None), - ObjectFit::Contain { scale_down } => { - path!(ObjectFit::Contain { scale_down: #scale_down }) - } - ObjectFit::Cover { scale_down } => path!(ObjectFit::Cover { scale_down: #scale_down }), - }) - } -} - -impl ToTokens for Rect -where - T: ToTokens, -{ - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Rect::All(v) => path!(Rect::All(#v)), - Rect::VerticalHorizontal(v, h) => path!(Rect::VerticalHorizontal(#v, #h)), - Rect::TopHorizontalBottom(t, h, b) => path!(Rect::TopHorizontalBottom(#t, #h, #b)), - Rect::TopRightBottomLeft(t, r, b, l) => path!(Rect::TopRightBottomLeft(#t, #r, #b, #l)), - }); - } -} - -impl ToTokens for LengthPercentage { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - LengthPercentage::Length(v) => path!(LengthPercentage::Length(#v)), - LengthPercentage::Percentage(v) => path!(LengthPercentage::Percentage(#v)), - }); - } -} - -impl ToTokens for AutoLengthPercentage { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - AutoLengthPercentage::LengthPercentage(v) => { - path!(AutoLengthPercentage::LengthPercentage(#v)) - } - AutoLengthPercentage::Auto => path!(AutoLengthPercentage::Auto), - }); - } -} - -impl ToTokens for LineStyle { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - LineStyle::None => path!(LineStyle::None), - LineStyle::Hidden => path!(LineStyle::Hidden), - LineStyle::Dotted => path!(LineStyle::Dotted), - LineStyle::Dashed => path!(LineStyle::Dashed), - LineStyle::Solid => path!(LineStyle::Solid), - LineStyle::Double => path!(LineStyle::Double), - LineStyle::Groove => path!(LineStyle::Groove), - LineStyle::Ridge => path!(LineStyle::Ridge), - LineStyle::Inset => path!(LineStyle::Inset), - LineStyle::Outset => path!(LineStyle::Outset), - }) - } -} - -impl ToTokens for LineWidth { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - LineWidth::Length(length) => path!(LineWidth::Length(#length)), - LineWidth::Thin => path!(LineWidth::Thin), - LineWidth::Medium => path!(LineWidth::Medium), - LineWidth::Thick => path!(LineWidth::Thick), - }) - } -} - -impl ToTokens for LineHeight { - fn to_tokens(&self, tokens: &mut TokenStream) { - self.0.to_tokens(tokens) - } -} - -impl ToTokens for ListStyleType { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - ListStyleType::Disc => path!(ListStyleType::Disc), - ListStyleType::Circle => path!(ListStyleType::Circle), - ListStyleType::Square => path!(ListStyleType::Square), - ListStyleType::Decimal => path!(ListStyleType::Decimal), - ListStyleType::DecimalLeadingZero => path!(ListStyleType::DecimalLeadingZero), - ListStyleType::LowerRoman => path!(ListStyleType::LowerRoman), - ListStyleType::UpperRoman => path!(ListStyleType::UpperRoman), - ListStyleType::LowerGreek => path!(ListStyleType::LowerGreek), - ListStyleType::UpperGreek => path!(ListStyleType::UpperGreek), - ListStyleType::LowerLatin => path!(ListStyleType::LowerLatin), - ListStyleType::UpperLatin => path!(ListStyleType::UpperLatin), - ListStyleType::Armenian => path!(ListStyleType::Armenian), - ListStyleType::Georgian => path!(ListStyleType::Georgian), - ListStyleType::LowerAlpha => path!(ListStyleType::LowerAlpha), - ListStyleType::UpperAlpha => path!(ListStyleType::UpperAlpha), - ListStyleType::None => path!(ListStyleType::None), - }) - } -} - -impl ToTokens for Position { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Position::Static => path!(Position::Static), - Position::Relative => path!(Position::Relative), - Position::Absolute => path!(Position::Absolute), - Position::Fixed => path!(Position::Fixed), - }) - } -} - -impl ToTokens for Resize { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Resize::None => path!(Resize::None), - Resize::Both => path!(Resize::Both), - Resize::Horizontal => path!(Resize::Horizontal), - Resize::Vertical => path!(Resize::Vertical), - }) - } -} - -impl ToTokens for WhiteSpace { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - WhiteSpace::Normal => path!(WhiteSpace::Normal), - WhiteSpace::Pre => path!(WhiteSpace::Pre), - WhiteSpace::Nowrap => path!(WhiteSpace::Nowrap), - WhiteSpace::PreWrap => path!(WhiteSpace::PreWrap), - WhiteSpace::PreLine => path!(WhiteSpace::PreLine), - }) - } -} - -impl ToTokens for WidthHeight { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - WidthHeight::Auto => path!(WidthHeight::Auto), - WidthHeight::LengthPercentage(v) => path!(WidthHeight::LengthPercentage(#v)), - WidthHeight::MinContent => path!(WidthHeight::MinContent), - WidthHeight::MaxContent => path!(WidthHeight::MaxContent), - WidthHeight::FitContent(v) => path!(WidthHeight::FitContent(#v)), - }) - } -} - -impl ToTokens for MaxWidthHeight { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - MaxWidthHeight::None => path!(MaxWidthHeight::None), - MaxWidthHeight::LengthPercentage(v) => path!(MaxWidthHeight::LengthPercentage(#v)), - MaxWidthHeight::MinContent => path!(MaxWidthHeight::MinContent), - MaxWidthHeight::MaxContent => path!(MaxWidthHeight::MaxContent), - MaxWidthHeight::FitContent(v) => path!(MaxWidthHeight::FitContent(#v)), - }) - } -} - -impl ToTokens for Width21 { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Width21::Auto => path!(Width21::Auto), - Width21::LengthPercentage(v) => path!(Width21::LengthPercentage(#v)), - }) - } -} - -impl ToTokens for Shadow { - fn to_tokens(&self, tokens: &mut TokenStream) { - let color = match self.color.as_ref() { - Some(color) => quote!(Some(#color)), - None => quote!(None), - }; - let length = &self.length; - let inset = &self.inset; - tokens.extend(quote! { - style::Shadow { - color: #color, - length: #length, - inset: #inset, - } - }) - } -} - -impl ToTokens for ShadowLength { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - ShadowLength::Offsets { - vertical, - horizontal, - } => path!(ShadowLength::Offsets { - vertical: #vertical, - horizontal: #horizontal, - }), - ShadowLength::OffsetsBlur { - vertical, - horizontal, - blur, - } => path!(ShadowLength::OffsetsBlur { - vertical: #vertical, - horizontal: #horizontal, - blur: #blur, - }), - ShadowLength::OffsetsBlurSpread { - vertical, - horizontal, - blur, - spread, - } => path!(ShadowLength::Offsets { - vertical: #vertical, - horizontal: #horizontal, - blur: #blur, - spread: #spread, - }), - }) - } -} - -impl ToTokens for TextAlign { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - TextAlign::Left => path!(TextAlign::Left), - TextAlign::Right => path!(TextAlign::Right), - TextAlign::Center => path!(TextAlign::Center), - TextAlign::Justify => path!(TextAlign::Justify), - }); - } -} - -impl ToTokens for Length { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Length::Em(v) => path!(Length::Em(#v)), - Length::Ex(v) => path!(Length::Ex(#v)), - Length::In(v) => path!(Length::In(#v)), - Length::Cm(v) => path!(Length::Cm(#v)), - Length::Mm(v) => path!(Length::Mm(#v)), - Length::Pt(v) => path!(Length::Pt(#v)), - Length::Pc(v) => path!(Length::Pc(#v)), - Length::Px(v) => path!(Length::Px(#v)), - Length::Zero => path!(Length::Zero), - }) - } -} - -impl ToTokens for Percentage { - fn to_tokens(&self, tokens: &mut TokenStream) { - let val = self.0; - tokens.extend(path!(Percentage(#val))); - } -} - -impl ToTokens for DynamicColor { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - DynamicColor::Dynamic(block) => path!(DynamicColor::Literal(#block)), - DynamicColor::Literal(color) => path!(DynamicColor::Literal(#color)), - }) - } -} - -impl ToTokens for Color { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - Color::HexRGB(r, g, b) => path!(Color::HexRGB(#r, #g, #b)), - Color::HexRGBA(r, g, b, a) => path!(Color::HexRGB(#r, #g, #b, #a)), - Color::HSL(h, s, l) => path!(Color::HSL(#h, #s, #l)), - Color::HSLA(h, s, l, a) => path!(Color::HSLA(#h, #s, #l, #a)), - Color::IndianRed => path!(Color::IndianRed), - Color::LightCoral => path!(Color::LightCoral), - Color::Salmon => path!(Color::Salmon), - Color::DarkSalmon => path!(Color::DarkSalmon), - Color::LightSalmon => path!(Color::LightSalmon), - Color::Crimson => path!(Color::Crimson), - Color::Red => path!(Color::Red), - Color::FireBrick => path!(Color::FireBrick), - Color::DarkRed => path!(Color::DarkRed), - Color::Pink => path!(Color::Pink), - Color::LightPink => path!(Color::LightPink), - Color::HotPink => path!(Color::HotPink), - Color::DeepPink => path!(Color::DeepPink), - Color::MediumVioletRed => path!(Color::MediumVioletRed), - Color::PaleVioletRed => path!(Color::PaleVioletRed), - Color::Coral => path!(Color::Coral), - Color::Tomato => path!(Color::Tomato), - Color::OrangeRed => path!(Color::OrangeRed), - Color::DarkOrange => path!(Color::DarkOrange), - Color::Orange => path!(Color::Orange), - Color::Gold => path!(Color::Gold), - Color::Yellow => path!(Color::Yellow), - Color::LightYellow => path!(Color::LightYellow), - Color::LemonChiffon => path!(Color::LemonChiffon), - Color::LightGoldenrodYellow => path!(Color::LightGoldenrodYellow), - Color::PapayaWhip => path!(Color::PapayaWhip), - Color::Moccasin => path!(Color::Moccasin), - Color::PeachPuff => path!(Color::PeachPuff), - Color::PaleGoldenrod => path!(Color::PaleGoldenrod), - Color::Khaki => path!(Color::Khaki), - Color::DarkKhaki => path!(Color::DarkKhaki), - Color::Lavender => path!(Color::Lavender), - Color::Thistle => path!(Color::Thistle), - Color::Plum => path!(Color::Plum), - Color::Violet => path!(Color::Violet), - Color::Orchid => path!(Color::Orchid), - Color::Fuchsia => path!(Color::Fuchsia), - Color::Magenta => path!(Color::Magenta), - Color::MediumOrchid => path!(Color::MediumOrchid), - Color::MediumPurple => path!(Color::MediumPurple), - Color::RebeccaPurple => path!(Color::RebeccaPurple), - Color::BlueViolet => path!(Color::BlueViolet), - Color::DarkViolet => path!(Color::DarkViolet), - Color::DarkOrchid => path!(Color::DarkOrchid), - Color::DarkMagenta => path!(Color::DarkMagenta), - Color::Purple => path!(Color::Purple), - Color::Indigo => path!(Color::Indigo), - Color::SlateBlue => path!(Color::SlateBlue), - Color::DarkSlateBlue => path!(Color::DarkSlateBlue), - Color::MediumSlateBlue => path!(Color::MediumSlateBlue), - Color::GreenYellow => path!(Color::GreenYellow), - Color::Chartreuse => path!(Color::Chartreuse), - Color::LawnGreen => path!(Color::LawnGreen), - Color::Lime => path!(Color::Lime), - Color::LimeGreen => path!(Color::LimeGreen), - Color::PaleGreen => path!(Color::PaleGreen), - Color::LightGreen => path!(Color::LightGreen), - Color::MediumSpringGreen => path!(Color::MediumSpringGreen), - Color::SpringGreen => path!(Color::SpringGreen), - Color::MediumSeaGreen => path!(Color::MediumSeaGreen), - Color::SeaGreen => path!(Color::SeaGreen), - Color::ForestGreen => path!(Color::ForestGreen), - Color::Green => path!(Color::Green), - Color::DarkGreen => path!(Color::DarkGreen), - Color::YellowGreen => path!(Color::YellowGreen), - Color::OliveDrab => path!(Color::OliveDrab), - Color::Olive => path!(Color::Olive), - Color::DarkOliveGreen => path!(Color::DarkOliveGreen), - Color::MediumAquamarine => path!(Color::MediumAquamarine), - Color::DarkSeaGreen => path!(Color::DarkSeaGreen), - Color::LightSeaGreen => path!(Color::LightSeaGreen), - Color::DarkCyan => path!(Color::DarkCyan), - Color::Teal => path!(Color::Teal), - Color::Aqua => path!(Color::Aqua), - Color::Cyan => path!(Color::Cyan), - Color::LightCyan => path!(Color::LightCyan), - Color::PaleTurquoise => path!(Color::PaleTurquoise), - Color::Aquamarine => path!(Color::Aquamarine), - Color::Turquoise => path!(Color::Turquoise), - Color::MediumTurquoise => path!(Color::MediumTurquoise), - Color::DarkTurquoise => path!(Color::DarkTurquoise), - Color::CadetBlue => path!(Color::CadetBlue), - Color::SteelBlue => path!(Color::SteelBlue), - Color::LightSteelBlue => path!(Color::LightSteelBlue), - Color::PowderBlue => path!(Color::PowderBlue), - Color::LightBlue => path!(Color::LightBlue), - Color::SkyBlue => path!(Color::SkyBlue), - Color::LightSkyBlue => path!(Color::LightSkyBlue), - Color::DeepSkyBlue => path!(Color::DeepSkyBlue), - Color::DodgerBlue => path!(Color::DodgerBlue), - Color::CornflowerBlue => path!(Color::CornflowerBlue), - Color::RoyalBlue => path!(Color::RoyalBlue), - Color::Blue => path!(Color::Blue), - Color::MediumBlue => path!(Color::MediumBlue), - Color::DarkBlue => path!(Color::DarkBlue), - Color::Navy => path!(Color::Navy), - Color::MidnightBlue => path!(Color::MidnightBlue), - Color::Cornsilk => path!(Color::Cornsilk), - Color::BlanchedAlmond => path!(Color::BlanchedAlmond), - Color::Bisque => path!(Color::Bisque), - Color::NavajoWhite => path!(Color::NavajoWhite), - Color::Wheat => path!(Color::Wheat), - Color::BurlyWood => path!(Color::BurlyWood), - Color::Tan => path!(Color::Tan), - Color::RosyBrown => path!(Color::RosyBrown), - Color::SandyBrown => path!(Color::SandyBrown), - Color::Goldenrod => path!(Color::Goldenrod), - Color::DarkGoldenrod => path!(Color::DarkGoldenrod), - Color::Peru => path!(Color::Peru), - Color::Chocolate => path!(Color::Chocolate), - Color::SaddleBrown => path!(Color::SaddleBrown), - Color::Sienna => path!(Color::Sienna), - Color::Brown => path!(Color::Brown), - Color::Maroon => path!(Color::Maroon), - Color::White => path!(Color::White), - Color::Snow => path!(Color::Snow), - Color::HoneyDew => path!(Color::HoneyDew), - Color::MintCream => path!(Color::MintCream), - Color::Azure => path!(Color::Azure), - Color::AliceBlue => path!(Color::AliceBlue), - Color::GhostWhite => path!(Color::GhostWhite), - Color::WhiteSmoke => path!(Color::WhiteSmoke), - Color::SeaShell => path!(Color::SeaShell), - Color::Beige => path!(Color::Beige), - Color::OldLace => path!(Color::OldLace), - Color::FloralWhite => path!(Color::FloralWhite), - Color::Ivory => path!(Color::Ivory), - Color::AntiqueWhite => path!(Color::AntiqueWhite), - Color::Linen => path!(Color::Linen), - Color::LavenderBlush => path!(Color::LavenderBlush), - Color::MistyRose => path!(Color::MistyRose), - Color::Gainsboro => path!(Color::Gainsboro), - Color::LightGray => path!(Color::LightGray), - Color::Silver => path!(Color::Silver), - Color::DarkGray => path!(Color::DarkGray), - Color::Gray => path!(Color::Gray), - Color::DimGray => path!(Color::DimGray), - Color::LightSlateGray => path!(Color::LightSlateGray), - Color::SlateGray => path!(Color::SlateGray), - Color::DarkSlateGray => path!(Color::DarkSlateGray), - Color::Black => path!(Color::Black), - }) - } -} - -// Generic containers - -impl ToTokens for NonemptyCommaList -where - T: ToTokens, -{ - fn to_tokens(&self, tokens: &mut TokenStream) { - let first = &self.first; - let rest = &self.rest; - tokens.extend(path! { - NonemptyCommaList { - first: #first, - rest: vec![#(#rest),*], - } - }) - } -} - -impl ToTokens for SingleOrDouble -where - T: ToTokens, -{ - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(match self { - SingleOrDouble::Single(t) => path!(SingleOrDouble::Single(#t)), - SingleOrDouble::Double { vert, horiz } => path!(SingleOrDouble::Double { - vert: #vert, - horiz: #horiz, - }), - }) - } -} diff --git a/packages/core-macro/styles/color.rs b/packages/core-macro/styles/color.rs deleted file mode 100644 index 15bc7cfb5..000000000 --- a/packages/core-macro/styles/color.rs +++ /dev/null @@ -1,854 +0,0 @@ -use std::fmt; - -/// A color that possibly is possibly code, rather than a literal -#[derive(Debug, Clone, PartialEq)] -pub enum DynamicColor { - Literal(Color), - /// The type of the block is not checked here (it is checked by typeck). - Dynamic(syn::Block), -} - -impl DynamicColor { - pub fn is_dynamic(&self) -> bool { - match self { - DynamicColor::Dynamic(_) => true, - DynamicColor::Literal(_) => false, - } - } -} - -impl fmt::Display for DynamicColor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DynamicColor::Dynamic(_) => Ok(()), - DynamicColor::Literal(color) => color.fmt(f), - } - } -} - -// TODO other color variants. -#[derive(Debug, Clone, Copy, PartialEq)] -#[non_exhaustive] -pub enum Color { - HexRGB(u8, u8, u8), - HexRGBA(u8, u8, u8, u8), - // Invariants: `0 <= .0 < 360`, `0 <= .1 < 100`, `0 <= .2 < 100`. - HSL(f64, f64, f64), - // Invariants: `0 <= .0 < 360`, `0 <= .1 < 100`, `0 <= .2 < 100`, `0 <= .3 < 1`. - HSLA(f64, f64, f64, f64), - - // Red HTML Color Names - /// rgb(205, 92, 92) - IndianRed, - /// rgb(240, 128, 128) - LightCoral, - /// rgb(250, 128, 114) - Salmon, - /// rgb(233, 150, 122) - DarkSalmon, - /// rgb(255, 160, 122) - LightSalmon, - /// rgb(220, 20, 60) - Crimson, - /// rgb(255, 0, 0) - Red, - /// rgb(178, 34, 34) - FireBrick, - /// rgb(139, 0, 0) - DarkRed, - // Pink HTML Color Names - /// rgb(255, 192, 203) - Pink, - /// rgb(255, 182, 193) - LightPink, - /// rgb(255, 105, 180) - HotPink, - /// rgb(255, 20, 147) - DeepPink, - /// rgb(199, 21, 133) - MediumVioletRed, - /// rgb(219, 112, 147) - PaleVioletRed, - //Orange HTML Color Names - // /// rgb(255, 160, 122) redefined - // LightSalmon, - /// rgb(255, 127, 80) - Coral, - /// rgb(255, 99, 71) - Tomato, - /// rgb(255, 69, 0) - OrangeRed, - /// rgb(255, 140, 0) - DarkOrange, - /// rgb(255, 165, 0) - Orange, - // Yellow HTML Color Names - /// rgb(255, 215, 0) - Gold, - /// rgb(255, 255, 0) - Yellow, - /// rgb(255, 255, 224) - LightYellow, - /// rgb(255, 250, 205) - LemonChiffon, - /// rgb(250, 250, 210) - LightGoldenrodYellow, - /// rgb(255, 239, 213) - PapayaWhip, - /// rgb(255, 228, 181) - Moccasin, - /// rgb(255, 218, 185) - PeachPuff, - /// rgb(238, 232, 170) - PaleGoldenrod, - /// rgb(240, 230, 140) - Khaki, - /// rgb(189, 183, 107) - DarkKhaki, - // Purple HTML Color Names - /// rgb(230, 230, 250) - Lavender, - /// rgb(216, 191, 216) - Thistle, - /// rgb(221, 160, 221) - Plum, - /// rgb(238, 130, 238) - Violet, - /// rgb(218, 112, 214) - Orchid, - /// rgb(255, 0, 255) - Fuchsia, - /// rgb(255, 0, 255) - Magenta, - /// rgb(186, 85, 211) - MediumOrchid, - /// rgb(147, 112, 219) - MediumPurple, - /// rgb(102, 51, 153) - RebeccaPurple, - /// rgb(138, 43, 226) - BlueViolet, - /// rgb(148, 0, 211) - DarkViolet, - /// rgb(153, 50, 204) - DarkOrchid, - /// rgb(139, 0, 139) - DarkMagenta, - /// rgb(128, 0, 128) - Purple, - /// rgb(75, 0, 130) - Indigo, - /// rgb(106, 90, 205) - SlateBlue, - /// rgb(72, 61, 139) - DarkSlateBlue, - /// rgb(123, 104, 238) - MediumSlateBlue, - // Green HTML Color Names - /// rgb(173, 255, 47) - GreenYellow, - /// rgb(127, 255, 0) - Chartreuse, - /// rgb(124, 252, 0) - LawnGreen, - /// rgb(0, 255, 0) - Lime, - /// rgb(50, 205, 50) - LimeGreen, - /// rgb(152, 251, 152) - PaleGreen, - /// rgb(144, 238, 144) - LightGreen, - /// rgb(0, 250, 154) - MediumSpringGreen, - /// rgb(0, 255, 127) - SpringGreen, - /// rgb(60, 179, 113) - MediumSeaGreen, - /// rgb(46, 139, 87) - SeaGreen, - /// rgb(34, 139, 34) - ForestGreen, - /// rgb(0, 128, 0) - Green, - /// rgb(0, 100, 0) - DarkGreen, - /// rgb(154, 205, 50) - YellowGreen, - /// rgb(107, 142, 35) - OliveDrab, - /// rgb(128, 128, 0) - Olive, - /// rgb(85, 107, 47) - DarkOliveGreen, - /// rgb(102, 205, 170) - MediumAquamarine, - /// rgb(143, 188, 139) - DarkSeaGreen, - /// rgb(32, 178, 170) - LightSeaGreen, - /// rgb(0, 139, 139) - DarkCyan, - /// rgb(0, 128, 128) - Teal, - // Blue HTML Color Names - /// rgb(0, 255, 255) - Aqua, - /// rgb(0, 255, 255) - Cyan, - /// rgb(224, 255, 255) - LightCyan, - /// rgb(175, 238, 238) - PaleTurquoise, - /// rgb(127, 255, 212) - Aquamarine, - /// rgb(64, 224, 208) - Turquoise, - /// rgb(72, 209, 204) - MediumTurquoise, - /// rgb(0, 206, 209) - DarkTurquoise, - /// rgb(95, 158, 160) - CadetBlue, - /// rgb(70, 130, 180) - SteelBlue, - /// rgb(176, 196, 222) - LightSteelBlue, - /// rgb(176, 224, 230) - PowderBlue, - /// rgb(173, 216, 230) - LightBlue, - /// rgb(135, 206, 235) - SkyBlue, - /// rgb(135, 206, 250) - LightSkyBlue, - /// rgb(0, 191, 255) - DeepSkyBlue, - /// rgb(30, 144, 255) - DodgerBlue, - /// rgb(100, 149, 237) - CornflowerBlue, - // /// rgb(123, 104, 238) duplicate - //MediumSlateBlue, - /// rgb(65, 105, 225) - RoyalBlue, - /// rgb(0, 0, 255) - Blue, - /// rgb(0, 0, 205) - MediumBlue, - /// rgb(0, 0, 139) - DarkBlue, - /// rgb(0, 0, 128) - Navy, - /// rgb(25, 25, 112) - MidnightBlue, - // Brown HTML Color Names - /// rgb(255, 248, 220) - Cornsilk, - /// rgb(255, 235, 205) - BlanchedAlmond, - /// rgb(255, 228, 196) - Bisque, - /// rgb(255, 222, 173) - NavajoWhite, - /// rgb(245, 222, 179) - Wheat, - /// rgb(222, 184, 135) - BurlyWood, - /// rgb(210, 180, 140) - Tan, - /// rgb(188, 143, 143) - RosyBrown, - /// rgb(244, 164, 96) - SandyBrown, - /// rgb(218, 165, 32) - Goldenrod, - /// rgb(184, 134, 11) - DarkGoldenrod, - /// rgb(205, 133, 63) - Peru, - /// rgb(210, 105, 30) - Chocolate, - /// rgb(139, 69, 19) - SaddleBrown, - /// rgb(160, 82, 45) - Sienna, - /// rgb(165, 42, 42) - Brown, - /// rgb(128, 0, 0) - Maroon, - // White HTML Color Names - /// rgb(255, 255, 255) - White, - /// rgb(255, 250, 250) - Snow, - /// rgb(240, 255, 240) - HoneyDew, - /// rgb(245, 255, 250) - MintCream, - /// rgb(240, 255, 255) - Azure, - /// rgb(240, 248, 255) - AliceBlue, - /// rgb(248, 248, 255) - GhostWhite, - /// rgb(245, 245, 245) - WhiteSmoke, - /// rgb(255, 245, 238) - SeaShell, - /// rgb(245, 245, 220) - Beige, - /// rgb(253, 245, 230) - OldLace, - /// rgb(255, 250, 240) - FloralWhite, - /// rgb(255, 255, 240) - Ivory, - /// rgb(250, 235, 215) - AntiqueWhite, - /// rgb(250, 240, 230) - Linen, - /// rgb(255, 240, 245) - LavenderBlush, - /// rgb(255, 228, 225) - MistyRose, - // Gray HTML Color Names - /// rgb(220, 220, 220) - Gainsboro, - /// rgb(211, 211, 211) - LightGray, - /// rgb(192, 192, 192) - Silver, - /// rgb(169, 169, 169) - DarkGray, - /// rgb(128, 128, 128) - Gray, - /// rgb(105, 105, 105) - DimGray, - /// rgb(119, 136, 153) - LightSlateGray, - /// rgb(112, 128, 144) - SlateGray, - /// rgb(47, 79, 79) - DarkSlateGray, - /// rgb(0, 0, 0) - Black, -} - -impl Color { - // todo similar for others - pub fn to_rgb(self) -> Color { - use Color::*; - match self { - HexRGB(r, g, b) => HexRGB(r, g, b), - HexRGBA(r, g, b, _) => HexRGB(r, g, b), - HSL(h, s, l) => { - let s = s * 0.01; // percent conversion - let l = l * 0.01; // percent conversion - let (r, g, b) = hsl_to_rgb(h, s, l); - HexRGB((r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8) - } - HSLA(h, s, l, _) => Color::to_rgb(HSL(h, s, l)), - IndianRed => HexRGB(205, 92, 92), - LightCoral => HexRGB(240, 128, 128), - Salmon => HexRGB(250, 128, 114), - DarkSalmon => HexRGB(233, 150, 122), - LightSalmon => HexRGB(255, 160, 122), - Crimson => HexRGB(220, 20, 60), - Red => HexRGB(255, 0, 0), - FireBrick => HexRGB(178, 34, 34), - DarkRed => HexRGB(139, 0, 0), - Pink => HexRGB(255, 192, 203), - LightPink => HexRGB(255, 182, 193), - HotPink => HexRGB(255, 105, 180), - DeepPink => HexRGB(255, 20, 147), - MediumVioletRed => HexRGB(199, 21, 133), - PaleVioletRed => HexRGB(219, 112, 147), - Coral => HexRGB(255, 127, 80), - Tomato => HexRGB(255, 99, 71), - OrangeRed => HexRGB(255, 69, 0), - DarkOrange => HexRGB(255, 140, 0), - Orange => HexRGB(255, 165, 0), - Gold => HexRGB(255, 215, 0), - Yellow => HexRGB(255, 255, 0), - LightYellow => HexRGB(255, 255, 224), - LemonChiffon => HexRGB(255, 250, 205), - LightGoldenrodYellow => HexRGB(250, 250, 210), - PapayaWhip => HexRGB(255, 239, 213), - Moccasin => HexRGB(255, 228, 181), - PeachPuff => HexRGB(255, 218, 185), - PaleGoldenrod => HexRGB(238, 232, 170), - Khaki => HexRGB(240, 230, 140), - DarkKhaki => HexRGB(189, 183, 107), - Lavender => HexRGB(230, 230, 250), - Thistle => HexRGB(216, 191, 216), - Plum => HexRGB(221, 160, 221), - Violet => HexRGB(238, 130, 238), - Orchid => HexRGB(218, 112, 214), - Fuchsia => HexRGB(255, 0, 255), - Magenta => HexRGB(255, 0, 255), - MediumOrchid => HexRGB(186, 85, 211), - MediumPurple => HexRGB(147, 112, 219), - RebeccaPurple => HexRGB(102, 51, 153), - BlueViolet => HexRGB(138, 43, 226), - DarkViolet => HexRGB(148, 0, 211), - DarkOrchid => HexRGB(153, 50, 204), - DarkMagenta => HexRGB(139, 0, 139), - Purple => HexRGB(128, 0, 128), - Indigo => HexRGB(75, 0, 130), - SlateBlue => HexRGB(106, 90, 205), - DarkSlateBlue => HexRGB(72, 61, 139), - MediumSlateBlue => HexRGB(123, 104, 238), - GreenYellow => HexRGB(173, 255, 47), - Chartreuse => HexRGB(127, 255, 0), - LawnGreen => HexRGB(124, 252, 0), - Lime => HexRGB(0, 255, 0), - LimeGreen => HexRGB(50, 205, 50), - PaleGreen => HexRGB(152, 251, 152), - LightGreen => HexRGB(144, 238, 144), - MediumSpringGreen => HexRGB(0, 250, 154), - SpringGreen => HexRGB(0, 255, 127), - MediumSeaGreen => HexRGB(60, 179, 113), - SeaGreen => HexRGB(46, 139, 87), - ForestGreen => HexRGB(34, 139, 34), - Green => HexRGB(0, 128, 0), - DarkGreen => HexRGB(0, 100, 0), - YellowGreen => HexRGB(154, 205, 50), - OliveDrab => HexRGB(107, 142, 35), - Olive => HexRGB(128, 128, 0), - DarkOliveGreen => HexRGB(85, 107, 47), - MediumAquamarine => HexRGB(102, 205, 170), - DarkSeaGreen => HexRGB(143, 188, 139), - LightSeaGreen => HexRGB(32, 178, 170), - DarkCyan => HexRGB(0, 139, 139), - Teal => HexRGB(0, 128, 128), - Aqua => HexRGB(0, 255, 255), - Cyan => HexRGB(0, 255, 255), - LightCyan => HexRGB(224, 255, 255), - PaleTurquoise => HexRGB(175, 238, 238), - Aquamarine => HexRGB(127, 255, 212), - Turquoise => HexRGB(64, 224, 208), - MediumTurquoise => HexRGB(72, 209, 204), - DarkTurquoise => HexRGB(0, 206, 209), - CadetBlue => HexRGB(95, 158, 160), - SteelBlue => HexRGB(70, 130, 180), - LightSteelBlue => HexRGB(176, 196, 222), - PowderBlue => HexRGB(176, 224, 230), - LightBlue => HexRGB(173, 216, 230), - SkyBlue => HexRGB(135, 206, 235), - LightSkyBlue => HexRGB(135, 206, 250), - DeepSkyBlue => HexRGB(0, 191, 255), - DodgerBlue => HexRGB(30, 144, 255), - CornflowerBlue => HexRGB(100, 149, 237), - RoyalBlue => HexRGB(65, 105, 225), - Blue => HexRGB(0, 0, 255), - MediumBlue => HexRGB(0, 0, 205), - DarkBlue => HexRGB(0, 0, 139), - Navy => HexRGB(0, 0, 128), - MidnightBlue => HexRGB(25, 25, 112), - Cornsilk => HexRGB(255, 248, 220), - BlanchedAlmond => HexRGB(255, 235, 205), - Bisque => HexRGB(255, 228, 196), - NavajoWhite => HexRGB(255, 222, 173), - Wheat => HexRGB(245, 222, 179), - BurlyWood => HexRGB(222, 184, 135), - Tan => HexRGB(210, 180, 140), - RosyBrown => HexRGB(188, 143, 143), - SandyBrown => HexRGB(244, 164, 96), - Goldenrod => HexRGB(218, 165, 32), - DarkGoldenrod => HexRGB(184, 134, 11), - Peru => HexRGB(205, 133, 63), - Chocolate => HexRGB(210, 105, 30), - SaddleBrown => HexRGB(139, 69, 19), - Sienna => HexRGB(160, 82, 45), - Brown => HexRGB(165, 42, 42), - Maroon => HexRGB(128, 0, 0), - White => HexRGB(255, 255, 255), - Snow => HexRGB(255, 250, 250), - HoneyDew => HexRGB(240, 255, 240), - MintCream => HexRGB(245, 255, 250), - Azure => HexRGB(240, 255, 255), - AliceBlue => HexRGB(240, 248, 255), - GhostWhite => HexRGB(248, 248, 255), - WhiteSmoke => HexRGB(245, 245, 245), - SeaShell => HexRGB(255, 245, 238), - Beige => HexRGB(245, 245, 220), - OldLace => HexRGB(253, 245, 230), - FloralWhite => HexRGB(255, 250, 240), - Ivory => HexRGB(255, 255, 240), - AntiqueWhite => HexRGB(250, 235, 215), - Linen => HexRGB(250, 240, 230), - LavenderBlush => HexRGB(255, 240, 245), - MistyRose => HexRGB(255, 228, 225), - Gainsboro => HexRGB(220, 220, 220), - LightGray => HexRGB(211, 211, 211), - Silver => HexRGB(192, 192, 192), - DarkGray => HexRGB(169, 169, 169), - Gray => HexRGB(128, 128, 128), - DimGray => HexRGB(105, 105, 105), - LightSlateGray => HexRGB(119, 136, 153), - SlateGray => HexRGB(112, 128, 144), - DarkSlateGray => HexRGB(47, 79, 79), - Black => HexRGB(0, 0, 0), - } - } - - pub fn from_named(name: &str) -> Option { - // todo use a faster search (e.g. hashmap, aho-corasick) - use Color::*; - Some(match name { - "indianred" => IndianRed, - "lightcoral" => LightCoral, - "salmon" => Salmon, - "darksalmon" => DarkSalmon, - "lightsalmon" => LightSalmon, - "crimson" => Crimson, - "red" => Red, - "firebrick" => FireBrick, - "darkred" => DarkRed, - "pink" => Pink, - "lightpink" => LightPink, - "hotpink" => HotPink, - "deeppink" => DeepPink, - "mediumvioletred" => MediumVioletRed, - "palevioletred" => PaleVioletRed, - "coral" => Coral, - "tomato" => Tomato, - "orangered" => OrangeRed, - "darkorange" => DarkOrange, - "orange" => Orange, - "gold" => Gold, - "yellow" => Yellow, - "lightyellow" => LightYellow, - "lemonchiffon" => LemonChiffon, - "lightgoldenrodyellow" => LightGoldenrodYellow, - "papayawhip" => PapayaWhip, - "Moccasin" => Moccasin, - "Peachpuff" => PeachPuff, - "palegoldenrod" => PaleGoldenrod, - "khaki" => Khaki, - "darkkhaki" => DarkKhaki, - "lavender" => Lavender, - "thistle" => Thistle, - "plum" => Plum, - "violet" => Violet, - "orchid" => Orchid, - "fuchsia" => Fuchsia, - "magenta" => Magenta, - "mediumorchid" => MediumOrchid, - "mediumpurple" => MediumPurple, - "rebeccapurple" => RebeccaPurple, - "blueviolet" => BlueViolet, - "darkviolet" => DarkViolet, - "darkorchid" => DarkOrchid, - "darkmagenta" => DarkMagenta, - "purple" => Purple, - "indigo" => Indigo, - "slateblue" => SlateBlue, - "darkslateblue" => DarkSlateBlue, - "mediumslateblue" => MediumSlateBlue, - "greenyellow" => GreenYellow, - "chartreuse" => Chartreuse, - "lawngreen" => LawnGreen, - "lime" => Lime, - "limegreen" => LimeGreen, - "palegreen" => PaleGreen, - "lightgreen" => LightGreen, - "mediumspringgreen" => MediumSpringGreen, - "springgreen" => SpringGreen, - "mediumseagreen" => MediumSeaGreen, - "seagreen" => SeaGreen, - "forestgreen" => ForestGreen, - "green" => Green, - "darkgreen" => DarkGreen, - "yellowgreen" => YellowGreen, - "olivedrab" => OliveDrab, - "olive" => Olive, - "darkolivegreen" => DarkOliveGreen, - "mediumaquamarine" => MediumAquamarine, - "darkseagreen" => DarkSeaGreen, - "lightseagreen" => LightSeaGreen, - "darkcyan" => DarkCyan, - "teal" => Teal, - "aqua" => Aqua, - "cyan" => Cyan, - "lightcyan" => LightCyan, - "paleturquoise" => PaleTurquoise, - "aquamarine" => Aquamarine, - "turquoise" => Turquoise, - "mediumturquoise" => MediumTurquoise, - "darkturquoise" => DarkTurquoise, - "cadetblue" => CadetBlue, - "steelblue" => SteelBlue, - "lightsteelblue" => LightSteelBlue, - "powderblue" => PowderBlue, - "lightblue" => LightBlue, - "skyblue" => SkyBlue, - "lightskyblue" => LightSkyBlue, - "deepskyblue" => DeepSkyBlue, - "dodgerblue" => DodgerBlue, - "cornflowerblue" => CornflowerBlue, - "royalblue" => RoyalBlue, - "blue" => Blue, - "mediumblue" => MediumBlue, - "darkblue" => DarkBlue, - "navy" => Navy, - "midnightblue" => MidnightBlue, - "cornsilk" => Cornsilk, - "blanchedalmond" => BlanchedAlmond, - "bisque" => Bisque, - "navajowhite" => NavajoWhite, - "wheat" => Wheat, - "burlywood" => BurlyWood, - "tan" => Tan, - "rosybrown" => RosyBrown, - "sandybrown" => SandyBrown, - "goldenrod" => Goldenrod, - "darkgoldenrod" => DarkGoldenrod, - "peru" => Peru, - "chocolate" => Chocolate, - "saddlebrown" => SaddleBrown, - "sienna" => Sienna, - "brown" => Brown, - "maroon" => Maroon, - "white" => White, - "snow" => Snow, - "honeydew" => HoneyDew, - "mintcream" => MintCream, - "azure" => Azure, - "aliceblue" => AliceBlue, - "ghostwhite" => GhostWhite, - "whitesmoke" => WhiteSmoke, - "seashell" => SeaShell, - "beige" => Beige, - "oldlace" => OldLace, - "floralwhite" => FloralWhite, - "ivory" => Ivory, - "antiquewhite" => AntiqueWhite, - "linen" => Linen, - "lavenderblush" => LavenderBlush, - "mistyrose" => MistyRose, - "gainsboro" => Gainsboro, - "lightgray" => LightGray, - "silver" => Silver, - "darkgray" => DarkGray, - "gray" => Gray, - "dimgray" => DimGray, - "lightslategray" => LightSlateGray, - "slategray" => SlateGray, - "darkslategray" => DarkSlateGray, - "black" => Black, - _ => return None, - }) - } -} - -impl fmt::Display for Color { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use Color::*; - match self { - HexRGB(r, g, b) => write!(f, "#{:02x}{:02x}{:02x}", r, g, b), - HexRGBA(r, g, b, a) => write!(f, "#{:02x}{:02x}{:02x}{:02x}", r, g, b, a), - HSL(h, s, l) => write!(f, "hsl({}, {}%, {}%)", h, s, l), - HSLA(h, s, l, a) => write!(f, "hsla({}, {}%, {}%, {})", h, s, l, a), - IndianRed => write!(f, "indianred"), - LightCoral => write!(f, "lightcoral"), - Salmon => write!(f, "salmon"), - DarkSalmon => write!(f, "darksalmon"), - LightSalmon => write!(f, "lightsalmon"), - Crimson => write!(f, "crimson"), - Red => write!(f, "red"), - FireBrick => write!(f, "firebrick"), - DarkRed => write!(f, "darkred"), - Pink => write!(f, "pink"), - LightPink => write!(f, "lightpink"), - HotPink => write!(f, "hotpink"), - DeepPink => write!(f, "deeppink"), - MediumVioletRed => write!(f, "mediumvioletred"), - PaleVioletRed => write!(f, "palevioletred"), - Coral => write!(f, "coral"), - Tomato => write!(f, "tomato"), - OrangeRed => write!(f, "orangered"), - DarkOrange => write!(f, "darkorange"), - Orange => write!(f, "orange"), - Gold => write!(f, "gold"), - Yellow => write!(f, "yellow"), - LightYellow => write!(f, "lightyellow"), - LemonChiffon => write!(f, "lemonchiffon"), - LightGoldenrodYellow => write!(f, "lightgoldenrodyellow"), - PapayaWhip => write!(f, "papayawhip"), - Moccasin => write!(f, "Moccasin"), - PeachPuff => write!(f, "Peachpuff"), - PaleGoldenrod => write!(f, "palegoldenrod"), - Khaki => write!(f, "khaki"), - DarkKhaki => write!(f, "darkkhaki"), - Lavender => write!(f, "lavender"), - Thistle => write!(f, "thistle"), - Plum => write!(f, "plum"), - Violet => write!(f, "violet"), - Orchid => write!(f, "orchid"), - Fuchsia => write!(f, "fuchsia"), - Magenta => write!(f, "magenta"), - MediumOrchid => write!(f, "mediumorchid"), - MediumPurple => write!(f, "mediumpurple"), - RebeccaPurple => write!(f, "rebeccapurple"), - BlueViolet => write!(f, "blueviolet"), - DarkViolet => write!(f, "darkviolet"), - DarkOrchid => write!(f, "darkorchid"), - DarkMagenta => write!(f, "darkmagenta"), - Purple => write!(f, "purple"), - Indigo => write!(f, "indigo"), - SlateBlue => write!(f, "slateblue"), - DarkSlateBlue => write!(f, "darkslateblue"), - MediumSlateBlue => write!(f, "mediumslateblue"), - GreenYellow => write!(f, "greenyellow"), - Chartreuse => write!(f, "chartreuse"), - LawnGreen => write!(f, "lawngreen"), - Lime => write!(f, "lime"), - LimeGreen => write!(f, "limegreen"), - PaleGreen => write!(f, "palegreen"), - LightGreen => write!(f, "lightgreen"), - MediumSpringGreen => write!(f, "mediumspringgreen"), - SpringGreen => write!(f, "springgreen"), - MediumSeaGreen => write!(f, "mediumseagreen"), - SeaGreen => write!(f, "seagreen"), - ForestGreen => write!(f, "forestgreen"), - Green => write!(f, "green"), - DarkGreen => write!(f, "darkgreen"), - YellowGreen => write!(f, "yellowgreen"), - OliveDrab => write!(f, "olivedrab"), - Olive => write!(f, "olive"), - DarkOliveGreen => write!(f, "darkolivegreen"), - MediumAquamarine => write!(f, "mediumaquamarine"), - DarkSeaGreen => write!(f, "darkseagreen"), - LightSeaGreen => write!(f, "lightseagreen"), - DarkCyan => write!(f, "darkcyan"), - Teal => write!(f, "teal"), - Aqua => write!(f, "aqua"), - Cyan => write!(f, "cyan"), - LightCyan => write!(f, "lightcyan"), - PaleTurquoise => write!(f, "paleturquoise"), - Aquamarine => write!(f, "aquamarine"), - Turquoise => write!(f, "turquoise"), - MediumTurquoise => write!(f, "mediumturquoise"), - DarkTurquoise => write!(f, "darkturquoise"), - CadetBlue => write!(f, "cadetblue"), - SteelBlue => write!(f, "steelblue"), - LightSteelBlue => write!(f, "lightsteelblue"), - PowderBlue => write!(f, "powderblue"), - LightBlue => write!(f, "lightblue"), - SkyBlue => write!(f, "skyblue"), - LightSkyBlue => write!(f, "lightskyblue"), - DeepSkyBlue => write!(f, "deepskyblue"), - DodgerBlue => write!(f, "dodgerblue"), - CornflowerBlue => write!(f, "cornflowerblue"), - RoyalBlue => write!(f, "royalblue"), - Blue => write!(f, "blue"), - MediumBlue => write!(f, "mediumblue"), - DarkBlue => write!(f, "darkblue"), - Navy => write!(f, "navy"), - MidnightBlue => write!(f, "midnightblue"), - Cornsilk => write!(f, "cornsilk"), - BlanchedAlmond => write!(f, "blanchedalmond"), - Bisque => write!(f, "bisque"), - NavajoWhite => write!(f, "navajowhite"), - Wheat => write!(f, "wheat"), - BurlyWood => write!(f, "burlywood"), - Tan => write!(f, "tan"), - RosyBrown => write!(f, "rosybrown"), - SandyBrown => write!(f, "sandybrown"), - Goldenrod => write!(f, "goldenrod"), - DarkGoldenrod => write!(f, "darkgoldenrod"), - Peru => write!(f, "peru"), - Chocolate => write!(f, "chocolate"), - SaddleBrown => write!(f, "saddlebrown"), - Sienna => write!(f, "sienna"), - Brown => write!(f, "brown"), - Maroon => write!(f, "maroon"), - White => write!(f, "white"), - Snow => write!(f, "snow"), - HoneyDew => write!(f, "honeydew"), - MintCream => write!(f, "mintcream"), - Azure => write!(f, "azure"), - AliceBlue => write!(f, "aliceblue"), - GhostWhite => write!(f, "ghostwhite"), - WhiteSmoke => write!(f, "whitesmoke"), - SeaShell => write!(f, "seashell"), - Beige => write!(f, "beige"), - OldLace => write!(f, "oldlace"), - FloralWhite => write!(f, "floralwhite"), - Ivory => write!(f, "ivory"), - AntiqueWhite => write!(f, "antiquewhite"), - Linen => write!(f, "linen"), - LavenderBlush => write!(f, "lavenderblush"), - MistyRose => write!(f, "mistyrose"), - Gainsboro => write!(f, "gainsboro"), - LightGray => write!(f, "lightgray"), - Silver => write!(f, "silver"), - DarkGray => write!(f, "darkgray"), - Gray => write!(f, "gray"), - DimGray => write!(f, "dimgray"), - LightSlateGray => write!(f, "lightslategray"), - SlateGray => write!(f, "slategray"), - DarkSlateGray => write!(f, "darkslategray"), - Black => write!(f, "black"), - } - } -} - -fn hsl_to_rgb(h: f64, s: f64, l: f64) -> (f64, f64, f64) { - debug_assert!(h >= 0.0 && h < 360.0); - debug_assert!(s >= 0.0 && s <= 1.0); - debug_assert!(l >= 0.0 && l <= 1.0); - let c = (1.0 - (2.0 * l - 1.0).abs()) * s; - let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs()); - let m = l - c * 0.5; - let (rp, gp, bp) = if h < 60.0 { - (c, x, 0.0) - } else if h < 120.0 { - (x, c, 0.0) - } else if h < 180.0 { - (0.0, c, x) - } else if h < 240.0 { - (0.0, x, c) - } else if h < 300.0 { - (x, 0.0, c) - } else { - (c, 0.0, x) - }; - (rp + m, gp + m, bp + m) -} - -pub fn parse_hex(hex: &str) -> Option { - match hex.len() { - 3 => { - let r = u8::from_str_radix(hex.get(0..1)?, 16).ok()?; - let g = u8::from_str_radix(hex.get(1..2)?, 16).ok()?; - let b = u8::from_str_radix(hex.get(2..3)?, 16).ok()?; - // #fff is equivalent to #ffffff - Some(Color::HexRGB(r << 4 | r, g << 4 | g, b << 4 | b)) - } - 6 => { - let r = u8::from_str_radix(hex.get(0..2)?, 16).ok()?; - let g = u8::from_str_radix(hex.get(2..4)?, 16).ok()?; - let b = u8::from_str_radix(hex.get(4..6)?, 16).ok()?; - Some(Color::HexRGB(r, g, b)) - } - 8 => { - let r = u8::from_str_radix(hex.get(0..2)?, 16).ok()?; - let g = u8::from_str_radix(hex.get(2..4)?, 16).ok()?; - let b = u8::from_str_radix(hex.get(4..6)?, 16).ok()?; - let a = u8::from_str_radix(hex.get(6..8)?, 16).ok()?; - Some(Color::HexRGBA(r, g, b, a)) - } - _ => None, - } -} - -#[test] -fn test_color_convert() { - let color = Color::HSL(60.0, 0.0, 100.0); - assert_eq!(color.to_rgb(), Color::HexRGB(255, 255, 255)); - let color = Color::HSL(0.0, 100.0, 50.0); - assert_eq!(color.to_rgb(), Color::HexRGB(255, 0, 0)); -} diff --git a/packages/core-macro/styles/mod.rs b/packages/core-macro/styles/mod.rs deleted file mode 100644 index 47c8259c0..000000000 --- a/packages/core-macro/styles/mod.rs +++ /dev/null @@ -1,2044 +0,0 @@ -//! A module to type styles. -// TODO most stuff here is on the stack, but there are a few heap-allocs here and there. It would -// be good if we could just to allocate them in the bump arena when using bumpalo. -mod calc; -mod codegen; -mod color; -pub mod string; -mod syn_parse; - -use std::{ - fmt, - ops::{Deref, DerefMut}, -}; - -pub use calc::*; -pub use color::{Color, DynamicColor}; -// pub use crate::{ -// calc::*, -// color::{Color, DynamicColor}, -// }; - -pub struct DynamicStyles { - pub rules: Vec, -} - -impl From> for DynamicStyles { - fn from(v: Vec) -> Self { - Self { rules: v } - } -} - -impl fmt::Display for DynamicStyles { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - for style in self - .rules - .iter() - .filter(|style| !(style.is_dynamic() || style.is_dummy())) - { - write!(f, "{};", style)?; - } - Ok(()) - } -} - -// TODO make container generic over heap (e.g. support bumpalo) -#[derive(Debug, Clone, PartialEq)] -pub struct Styles { - pub rules: VecFlame Graph Reset ZoomSearch jsframework-a8f4acf5955e8e7f`core::ptr::drop_in_place<dioxus_core::virtual_dom::VirtualDom> (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`core::ptr::drop_in_place<alloc::boxed::Box<dioxus_core::scopearena::ScopeArena>> (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`<bumpalo::Bump as core::ops::drop::Drop>::drop (1 samples, 1.82%)j..libsystem_malloc.dylib`free_medium (1 samples, 1.82%)l..libsystem_kernel.dylib`madvise (1 samples, 1.82%)l..libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 3.64%)libs..libsystem_malloc.dylib`szone_malloc_should_clear (2 samples, 3.64%)libs..libsystem_malloc.dylib`tiny_malloc_should_clear (2 samples, 3.64%)libs..libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_element_node (7 samples, 12.73%)jsframework-a8f4acf..jsframework-a8f4acf5955e8e7f`alloc::raw_vec::RawVec<T,A>::reserve::do_reserve_and_handle (3 samples, 5.45%)jsframe..jsframework-a8f4acf5955e8e7f`alloc::raw_vec::finish_grow (3 samples, 5.45%)jsframe..libsystem_malloc.dylib`realloc (1 samples, 1.82%)l..libsystem_malloc.dylib`malloc_zone_realloc (1 samples, 1.82%)l..libsystem_malloc.dylib`szone_realloc (1 samples, 1.82%)l..libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::fin_head (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`<bumpalo::Bump as core::default::Default>::default (4 samples, 7.27%)jsframewor..jsframework-a8f4acf5955e8e7f`<bumpalo::Bump as core::ops::drop::Drop>::drop (1 samples, 1.82%)j..libsystem_malloc.dylib`free_tiny (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_free_no_lock (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 1.82%)l..libsystem_malloc.dylib`malloc_zone_realloc (1 samples, 1.82%)l..libsystem_platform.dylib`DYLD-STUB$$_platform_memmove (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`alloc::raw_vec::RawVec<T,A>::reserve::do_reserve_and_handle (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`alloc::raw_vec::finish_grow (2 samples, 3.64%)jsfr..libsystem_malloc.dylib`realloc (2 samples, 3.64%)libs..libsystem_malloc.dylib`szone_size (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`bumpalo::Bump::with_capacity (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`bumpalo::Bump::with_capacity (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::scope::BumpFrame::new (3 samples, 5.45%)jsframe..libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 1.82%)l..libsystem_malloc.dylib`szone_malloc_should_clear (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_malloc_should_clear (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::new_with_key (14 samples, 25.45%)jsframework-a8f4acf5955e8e7f`dioxus_core:..jsframework-a8f4acf5955e8e7f`hashbrown::raw::RawTable<T,A>::insert (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::element (6 samples, 10.91%)jsframework-a8f4..jsframework-a8f4acf5955e8e7f`bumpalo::Bump::alloc_layout_slow (6 samples, 10.91%)jsframework-a8f4..libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 3.64%)libs..libsystem_malloc.dylib`szone_malloc_should_clear (2 samples, 3.64%)libs..libsystem_malloc.dylib`tiny_malloc_should_clear (2 samples, 3.64%)libs..libsystem_malloc.dylib`set_tiny_meta_header_in_use (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::raw_element (12 samples, 21.82%)jsframework-a8f4acf5955e8e7f`dioxu..jsframework-a8f4acf5955e8e7f`bumpalo::Bump::alloc_layout_slow (12 samples, 21.82%)jsframework-a8f4acf5955e8e7f`bumpa..libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 1.82%)l..libsystem_malloc.dylib`szone_malloc_should_clear (1 samples, 1.82%)l..libsystem_malloc.dylib`small_malloc_should_clear (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`<&T as core::fmt::Display>::fmt (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`bumpalo::collections::string::String::into_bump_str (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::text (3 samples, 5.45%)jsframe..jsframework-a8f4acf5955e8e7f`core::fmt::write (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`<&mut W as core::fmt::Write>::write_str (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (22 samples, 40.00%)jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::c..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (22 samples, 40.00%)jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::n..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::empty_cell (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render (23 samples, 41.82%)jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::renderlibsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_node (40 samples, 72.73%)jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_nodejsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_scope (24 samples, 43.64%)jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_s..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component::_{{closure}} (24 samples, 43.64%)jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component..jsframework-a8f4acf5955e8e7f`jsframework::Row (24 samples, 43.64%)jsframework-a8f4acf5955e8e7f`jsframework::Rowlibsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::work (48 samples, 87.27%)jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::workjsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::mount (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`<core::option::Option<dioxus_core::lazynodes::LazyNodes> as dioxus_core::nodes::IntoVNode>::into_vnode (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`main (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`mainjsframework-a8f4acf5955e8e7f`std::rt::lang_start_internal (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`std::rt::lang_start_internaljsframework-a8f4acf5955e8e7f`std::rt::lang_start::_{{closure}} (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`std::rt::lang_start::_{{closure}}jsframework-a8f4acf5955e8e7f`std::sys_common::backtrace::__rust_begin_short_backtrace (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`std::sys_common::backtrace::__rust_begin_short_backtracejsframework-a8f4acf5955e8e7f`jsframework::main (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`jsframework::mainjsframework-a8f4acf5955e8e7f`criterion::Criterion<M>::bench_function (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`criterion::Criterion<M>::bench_functionjsframework-a8f4acf5955e8e7f`criterion::benchmark_group::BenchmarkGroup<M>::bench_function (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`criterion::benchmark_group::BenchmarkGroup<M>::bench_functionjsframework-a8f4acf5955e8e7f`criterion::routine::Routine::test (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`criterion::routine::Routine::testjsframework-a8f4acf5955e8e7f`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iterjsframework-a8f4acf5955e8e7f`criterion::bencher::Bencher<M>::iter (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`criterion::bencher::Bencher<M>::iterjsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::rebuild (50 samples, 90.91%)jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::rebuildjsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_scope (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::new_with_props_and_scheduler::_{{closure}} (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`core::ops::function::FnOnce::call_once (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::fragment_from_iter (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`core::ops::function::impls::_<impl core::ops::function::FnOnce<A> for &mut F>::call_once (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`rand::rng::Rng::gen_range (1 samples, 1.82%)j..all (55 samples, 100%)0x1 (55 samples, 100.00%)0x1libdyld.dylib`start (55 samples, 100.00%)libdyld.dylib`startlibsystem_kernel.dylib`__exit (4 samples, 7.27%)libsystem_.. \ No newline at end of file diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 9aa5bf2ee..62ad62f70 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -5,12 +5,12 @@ //! 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, Element, LazyNodes, NodeLink}; +use crate::innerlude::{Context, Element, LazyNodes, VPortal}; pub struct FragmentProps(Element); pub struct FragmentBuilder(Element); impl FragmentBuilder { - pub fn children(self, children: Option) -> FragmentBuilder { + pub fn children(self, children: Option) -> FragmentBuilder { FragmentBuilder(children) } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 8c6831ca3..8bf67b3d0 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -109,7 +109,6 @@ pub struct DiffState<'bump> { scopes: &'bump ScopeArena, pub mutations: Mutations<'bump>, pub(crate) stack: DiffStack<'bump>, - pub seen_scopes: FxHashSet, pub force_diff: bool, } @@ -119,7 +118,6 @@ impl<'bump> DiffState<'bump> { scopes, mutations: Mutations::new(), stack: DiffStack::new(), - seen_scopes: Default::default(), force_diff: false, } } @@ -198,12 +196,13 @@ impl<'bump> DiffStack<'bump> { } } - fn push_subtree(&mut self) { - self.nodes_created_stack.push(0); - self.instructions.push(DiffInstruction::Mount { - and: MountType::Append, - }); - } + // todo: subtrees + // fn push_subtree(&mut self) { + // self.nodes_created_stack.push(0); + // self.instructions.push(DiffInstruction::Mount { + // and: MountType::Append, + // }); + // } fn push_nodes_created(&mut self, count: usize) { self.nodes_created_stack.push(count); @@ -284,12 +283,12 @@ impl<'bump> DiffState<'bump> { // recursively push all the nodes of a tree onto the stack and return how many are there fn push_all_nodes(&mut self, node: &'bump VNode<'bump>) -> usize { match node { - VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => { + VNode::Text(_) | VNode::Placeholder(_) => { self.mutations.push_root(node.mounted_id()); 1 } - VNode::Linked(linked) => { + VNode::Portal(linked) => { let node = unsafe { &*linked.node }; let node: &VNode = unsafe { std::mem::transmute(node) }; self.push_all_nodes(node) @@ -354,12 +353,11 @@ impl<'bump> DiffState<'bump> { fn create_node(&mut self, node: &'bump VNode<'bump>) { match node { VNode::Text(vtext) => self.create_text_node(vtext, node), - VNode::Suspended(suspended) => self.create_suspended_node(suspended, node), - VNode::Anchor(anchor) => self.create_anchor_node(anchor, node), + VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node), VNode::Element(element) => self.create_element_node(element, node), VNode::Fragment(frag) => self.create_fragment_node(frag), - VNode::Component(component) => self.create_component_node(component), - VNode::Linked(linked) => self.create_linked_node(linked), + VNode::Component(component) => self.create_component_node(*component), + VNode::Portal(linked) => self.create_linked_node(linked), } } @@ -371,17 +369,7 @@ impl<'bump> DiffState<'bump> { self.stack.add_child_count(1); } - fn create_suspended_node(&mut self, suspended: &'bump VSuspended, node: &'bump VNode<'bump>) { - let real_id = self.scopes.reserve_node(node); - self.mutations.create_placeholder(real_id); - - suspended.dom_id.set(Some(real_id)); - self.stack.add_child_count(1); - - self.attach_suspended_node_to_scope(suspended); - } - - fn create_anchor_node(&mut self, anchor: &'bump VAnchor, node: &'bump VNode<'bump>) { + fn create_anchor_node(&mut self, anchor: &'bump VPlaceholder, node: &'bump VNode<'bump>) { let real_id = self.scopes.reserve_node(node); self.mutations.create_placeholder(real_id); @@ -433,6 +421,15 @@ impl<'bump> DiffState<'bump> { self.mutations.set_attribute(attr, real_id.as_u64()); } + // todo: the settext optimization + + // if children.len() == 1 { + // if let VNode::Text(vtext) = children[0] { + // self.mutations.set_text(vtext.text, real_id.as_u64()); + // return; + // } + // } + if !children.is_empty() { self.stack.create_children(children, MountType::Append); } @@ -460,8 +457,6 @@ impl<'bump> DiffState<'bump> { self.scopes .new_with_key(fc_ptr, caller, parent_scope, container, height, subtree); - log::debug!("container for scope {:?} is {:?}", new_idx, container); - // Actually initialize the caller's slot with the right address vcomponent.associated_scope.set(Some(new_idx)); @@ -469,12 +464,12 @@ impl<'bump> DiffState<'bump> { let cur_scope = self.scopes.get_scope(&parent_idx).unwrap(); let extended = unsafe { std::mem::transmute(vcomponent) }; cur_scope.items.borrow_mut().borrowed_props.push(extended); + } else { + // the props are currently bump allocated but we need to move them to the heap } - // TODO: - // add noderefs to current noderef list Noderefs - // add effects to current effect list Effects - let new_component = self.scopes.get_scope(&new_idx).unwrap(); + // TODO: add noderefs to current noderef list Noderefs + let _new_component = self.scopes.get_scope(&new_idx).unwrap(); log::debug!( "initializing component {:?} with height {:?}", @@ -488,16 +483,17 @@ impl<'bump> DiffState<'bump> { let nextnode = self.scopes.fin_head(&new_idx); self.stack.create_component(new_idx, nextnode); - if new_component.is_subtree_root.get() { - self.stack.push_subtree(); - } + // todo: subtrees + // if new_component.is_subtree_root.get() { + // self.stack.push_subtree(); + // } } // Finally, insert this scope as a seen node. - self.seen_scopes.insert(new_idx); + self.mutations.dirty_scopes.insert(new_idx); } - fn create_linked_node(&mut self, link: &'bump NodeLink) { + fn create_linked_node(&mut self, link: &'bump VPortal) { if link.scope_id.get().is_none() { if let Some(cur_scope) = self.stack.current_scope() { link.scope_id.set(Some(cur_scope)); @@ -519,20 +515,17 @@ impl<'bump> DiffState<'bump> { self.diff_text_nodes(old, new, old_node, new_node); } (Component(old), Component(new)) => { - self.diff_component_nodes(old_node, new_node, old, new) + self.diff_component_nodes(old_node, new_node, *old, *new) } (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new), - (Anchor(old), Anchor(new)) => new.dom_id.set(old.dom_id.get()), - (Suspended(old), Suspended(new)) => self.diff_suspended_nodes(old, new), + (Placeholder(old), Placeholder(new)) => new.dom_id.set(old.dom_id.get()), (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node), - (Linked(old), Linked(new)) => self.diff_linked_nodes(old, new), + (Portal(old), Portal(new)) => self.diff_linked_nodes(old, new), // Anything else is just a basic replace and create ( - Linked(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) - | Suspended(_), - Linked(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Anchor(_) - | Suspended(_), + Portal(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), + Portal(_) | Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_), ) => self .stack .create_node(new_node, MountType::Replace { old: old_node }), @@ -657,6 +650,64 @@ impl<'bump> DiffState<'bump> { self.stack.instructions.push(DiffInstruction::PopElement); self.diff_children(old.children, new.children); } + + // todo: this is for the "settext" optimization + // it works, but i'm not sure if it's the direction we want to take + + // match (old.children.len(), new.children.len()) { + // (0, 0) => {} + // (1, 1) => { + // let old1 = &old.children[0]; + // let new1 = &new.children[0]; + + // match (old1, new1) { + // (VNode::Text(old_text), VNode::Text(new_text)) => { + // if old_text.text != new_text.text { + // self.mutations.set_text(new_text.text, root.as_u64()); + // } + // } + // (VNode::Text(_old_text), _) => { + // self.stack.element_stack.push(root); + // self.stack.instructions.push(DiffInstruction::PopElement); + // self.stack.create_node(new1, MountType::Append); + // } + // (_, VNode::Text(new_text)) => { + // self.remove_nodes([old1], false); + // self.mutations.set_text(new_text.text, root.as_u64()); + // } + // _ => { + // self.stack.element_stack.push(root); + // self.stack.instructions.push(DiffInstruction::PopElement); + // self.diff_children(old.children, new.children); + // } + // } + // } + // (0, 1) => { + // if let VNode::Text(text) = &new.children[0] { + // self.mutations.set_text(text.text, root.as_u64()); + // } else { + // self.stack.element_stack.push(root); + // self.stack.instructions.push(DiffInstruction::PopElement); + // } + // } + // (0, _) => { + // self.mutations.edits.push(PushRoot { + // root: root.as_u64(), + // }); + // self.stack.element_stack.push(root); + // self.stack.instructions.push(DiffInstruction::PopElement); + // self.stack.create_children(new.children, MountType::Append); + // } + // (_, 0) => { + // self.remove_nodes(old.children, false); + // self.mutations.set_text("", root.as_u64()); + // } + // (_, _) => { + // self.stack.element_stack.push(root); + // self.stack.instructions.push(DiffInstruction::PopElement); + // self.diff_children(old.children, new.children); + // } + // } } fn diff_component_nodes( @@ -688,6 +739,7 @@ impl<'bump> DiffState<'bump> { .get_scope_mut(&scope_addr) .unwrap_or_else(|| panic!("could not find {:?}", scope_addr)) }; + scope.caller = unsafe { std::mem::transmute(new.caller) }; // React doesn't automatically memoize, but we do. @@ -721,12 +773,7 @@ impl<'bump> DiffState<'bump> { self.diff_children(old.children, new.children); } - fn diff_suspended_nodes(&mut self, old: &'bump VSuspended, new: &'bump VSuspended) { - new.dom_id.set(old.dom_id.get()); - self.attach_suspended_node_to_scope(new); - } - - fn diff_linked_nodes(&mut self, old: &'bump NodeLink, new: &'bump NodeLink) { + fn diff_linked_nodes(&mut self, old: &'bump VPortal, new: &'bump VPortal) { if !std::ptr::eq(old.node, new.node) { // if the ptrs are the same then theyr're the same let old: &VNode = unsafe { std::mem::transmute(&*old.node) }; @@ -769,14 +816,14 @@ impl<'bump> DiffState<'bump> { (_, []) => { self.remove_nodes(old, true); } - ([VNode::Anchor(old_anchor)], [VNode::Anchor(new_anchor)]) => { + ([VNode::Placeholder(old_anchor)], [VNode::Placeholder(new_anchor)]) => { old_anchor.dom_id.set(new_anchor.dom_id.get()); } - ([VNode::Anchor(_)], _) => { + ([VNode::Placeholder(_)], _) => { self.stack .create_children(new, MountType::Replace { old: &old[0] }); } - (_, [VNode::Anchor(_)]) => { + (_, [VNode::Placeholder(_)]) => { let new: &'bump VNode<'bump> = &new[0]; if let Some(first_old) = old.get(0) { self.remove_nodes(&old[1..], true); @@ -1178,9 +1225,8 @@ impl<'bump> DiffState<'bump> { match &search_node.take().unwrap() { VNode::Text(t) => break t.dom_id.get(), VNode::Element(t) => break t.dom_id.get(), - VNode::Suspended(t) => break t.dom_id.get(), - VNode::Anchor(t) => break t.dom_id.get(), - VNode::Linked(l) => { + VNode::Placeholder(t) => break t.dom_id.get(), + VNode::Portal(l) => { let node: &VNode = unsafe { std::mem::transmute(&*l.node) }; self.find_last_element(node); } @@ -1208,14 +1254,13 @@ impl<'bump> DiffState<'bump> { let scope_id = el.associated_scope.get().unwrap(); search_node = Some(self.scopes.root_node(&scope_id)); } - VNode::Linked(link) => { + VNode::Portal(link) => { let node = unsafe { std::mem::transmute(&*link.node) }; search_node = Some(node); } VNode::Text(t) => break t.dom_id.get(), VNode::Element(t) => break t.dom_id.get(), - VNode::Suspended(t) => break t.dom_id.get(), - VNode::Anchor(t) => break t.dom_id.get(), + VNode::Placeholder(t) => break t.dom_id.get(), } } } @@ -1233,7 +1278,7 @@ impl<'bump> DiffState<'bump> { self.remove_nodes(el.children, false); } - VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => { + VNode::Text(_) | VNode::Placeholder(_) => { let id = old .try_mounted_id() .unwrap_or_else(|| panic!("broke on {:?}", old)); @@ -1255,7 +1300,7 @@ impl<'bump> DiffState<'bump> { self.scopes.try_remove(&scope_id).unwrap(); } - VNode::Linked(l) => { + VNode::Portal(l) => { let node: &'bump VNode<'bump> = unsafe { std::mem::transmute(&*l.node) }; self.replace_node(node, nodes_created); } @@ -1282,15 +1327,7 @@ impl<'bump> DiffState<'bump> { } } } - VNode::Suspended(s) => { - let id = s.dom_id.get().unwrap(); - self.scopes.collect_garbage(id); - - if gen_muts { - self.mutations.remove(id.as_u64()); - } - } - VNode::Anchor(a) => { + VNode::Placeholder(a) => { let id = a.dom_id.get().unwrap(); self.scopes.collect_garbage(id); @@ -1312,7 +1349,7 @@ impl<'bump> DiffState<'bump> { self.remove_nodes(f.children, gen_muts); } - VNode::Linked(l) => { + VNode::Portal(l) => { let node = unsafe { std::mem::transmute(&*l.node) }; self.remove_nodes(Some(node), gen_muts); } @@ -1337,21 +1374,4 @@ impl<'bump> DiffState<'bump> { let long_listener = unsafe { std::mem::transmute(listener) }; scope.items.borrow_mut().listeners.push(long_listener) } - - fn attach_suspended_node_to_scope(&mut self, suspended: &'bump VSuspended) { - if let Some(scope) = self - .stack - .current_scope() - .and_then(|id| self.scopes.get_scope(&id)) - { - todo!() - // // safety: this lifetime is managed by the logic on scope - // let extended = unsafe { std::mem::transmute(suspended) }; - // scope - // .items - // .borrow_mut() - // .suspended_nodes - // .insert(suspended.task_id, extended); - } - } } diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index bdf236cdb..fb474dc69 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -35,6 +35,14 @@ enum StackNodeStorage<'a, 'b> { } impl<'a, 'b> LazyNodes<'a, 'b> { + + pub fn new_some(_val: F) -> Option + where + F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, + { + Some(Self::new(_val)) + } + pub fn new(_val: F) -> Self where F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, @@ -221,7 +229,7 @@ fn it_works() { let factory = NodeFactory { bump: &bump }; - let caller = NodeFactory::annotate_lazy(|f| { + let caller = LazyNodes::new_some(|f| { // f.text(format_args!("hello world!")) }) @@ -254,7 +262,7 @@ fn it_drops() { .map(|i| { let val = val.clone(); - NodeFactory::annotate_lazy(move |f| { + LazyNodes::new_some(move |f| { log::debug!("hell closure"); let inner = DropInner { id: i }; f.text(format_args!("hello world {:?}, {:?}", inner.id, val)) @@ -262,7 +270,7 @@ fn it_drops() { }) .collect::>(); - NodeFactory::annotate_lazy(|f| { + LazyNodes::new_some(|f| { log::debug!("main closure"); f.fragment_from_iter(it) }) diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index a4ea08399..f656b5a18 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -31,14 +31,14 @@ pub(crate) mod innerlude { pub use crate::scopearena::*; pub use crate::virtual_dom::*; - pub type Element = Option; + pub type Element = Option; pub type FC

= for<'a> fn(Context<'a>, &'a P) -> Element; } pub use crate::innerlude::{ Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode, LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, - UserEvent, VAnchor, VElement, VFragment, VNode, VSuspended, VirtualDom, FC, + UserEvent, VElement, VFragment, VNode, VirtualDom, FC, }; pub mod prelude { diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index ffad7c967..e7c54cdca 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -14,6 +14,7 @@ use std::{any::Any, fmt::Debug}; /// Mutations are the only link between the RealDOM and the VirtualDOM. pub struct Mutations<'a> { pub edits: Vec>, + pub dirty_scopes: FxHashSet, pub refs: Vec>, pub effects: Vec<&'a dyn FnMut()>, } @@ -103,6 +104,7 @@ pub enum DomEdit<'bump> { }, } +use fxhash::FxHashSet; use DomEdit::*; impl<'a> Mutations<'a> { @@ -110,6 +112,7 @@ impl<'a> Mutations<'a> { Self { edits: Vec::new(), refs: Vec::new(), + dirty_scopes: Default::default(), effects: Vec::new(), } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index bba4ad1f0..f02b553b5 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -97,19 +97,9 @@ pub enum VNode<'src> { /// ``` Component(&'src VComponent<'src>), - /// Suspended VNodes represent chunks of the UI tree that are not yet ready to be displayed. + /// Placeholders are a type of placeholder VNode used when fragments don't contain any children. /// - /// # Example - /// - /// ```rust, ignore - /// - /// - /// ``` - Suspended(&'src VSuspended), - - /// Anchors are a type of placeholder VNode used when fragments don't contain any children. - /// - /// Anchors cannot be directly constructed via public APIs. + /// Placeholders cannot be directly constructed via public APIs. /// /// # Example /// @@ -123,7 +113,7 @@ pub enum VNode<'src> { /// assert_eq!(root, VNode::Anchor); /// } /// ``` - Anchor(&'src VAnchor), + Placeholder(&'src VPlaceholder), /// A VNode that is actually a pointer to some nodes rather than the nodes directly. Useful when rendering portals /// or eliding lifetimes on VNodes through runtime checks. @@ -139,7 +129,7 @@ pub enum VNode<'src> { /// /// let node: NodeLink = vdom.render_vnode(rsx!( "hello" )); /// ``` - Linked(NodeLink), + Portal(VPortal), } impl<'src> VNode<'src> { @@ -149,11 +139,9 @@ impl<'src> VNode<'src> { VNode::Element(el) => el.key, VNode::Component(c) => c.key, VNode::Fragment(f) => f.key, - VNode::Text(_t) => None, - VNode::Suspended(_s) => None, - VNode::Anchor(_f) => None, - VNode::Linked(_c) => None, + VNode::Placeholder(_f) => None, + VNode::Portal(_c) => None, } } @@ -171,10 +159,8 @@ impl<'src> VNode<'src> { match &self { VNode::Text(el) => el.dom_id.get(), VNode::Element(el) => el.dom_id.get(), - VNode::Anchor(el) => el.dom_id.get(), - VNode::Suspended(el) => el.dom_id.get(), - - VNode::Linked(_) => None, + VNode::Placeholder(el) => el.dom_id.get(), + VNode::Portal(_) => None, VNode::Fragment(_) => None, VNode::Component(_) => None, } @@ -183,7 +169,6 @@ impl<'src> VNode<'src> { pub(crate) fn children(&self) -> &[VNode<'src>] { match &self { VNode::Fragment(f) => f.children, - VNode::Component(_c) => todo!("children are not accessible through this"), _ => &[], } } @@ -194,13 +179,12 @@ impl<'src> VNode<'src> { 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::Placeholder(a) => VNode::Placeholder(*a), VNode::Fragment(f) => VNode::Fragment(VFragment { children: f.children, key: f.key, }), - VNode::Linked(c) => VNode::Linked(NodeLink { + VNode::Portal(c) => VNode::Portal(VPortal { scope_id: c.scope_id.clone(), link_idx: c.link_idx.clone(), node: c.node, @@ -217,16 +201,13 @@ impl Debug for VNode<'_> { .field("name", &el.tag_name) .field("key", &el.key) .finish(), - VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text), - VNode::Anchor(_) => write!(s, "VNode::VAnchor"), - + VNode::Placeholder(_) => write!(s, "VNode::VAnchor"), VNode::Fragment(frag) => { write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children) } - VNode::Suspended { .. } => write!(s, "VNode::VSuspended"), VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc), - VNode::Linked(c) => write!(s, "VNode::VCached {{ scope_id: {:?} }}", c.scope_id.get()), + VNode::Portal(c) => write!(s, "VNode::VCached {{ scope_id: {:?} }}", c.scope_id.get()), } } } @@ -254,7 +235,7 @@ fn empty_cell() -> Cell> { } /// A placeholder node only generated when Fragments don't have any children. -pub struct VAnchor { +pub struct VPlaceholder { pub dom_id: Cell>, } @@ -357,10 +338,37 @@ pub struct Listener<'bump> { pub event: &'static str, /// The actual callback that the user specified + #[allow(clippy::type_complexity)] pub(crate) callback: RefCell) + 'bump>>>, } +/// A cached node is a "pointer" to a "rendered" node in a particular scope +/// +/// It does not provide direct access to the node, so it doesn't carry any lifetime information with it +/// +/// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key +/// is essentially a unique key to guarantee safe usage of the Node. +/// +/// Linked VNodes can only be made through the [`Context::render`] method +/// +/// Typically, NodeLinks are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into +/// an `rsx!` call. +/// +/// todo: remove the raw pointer and use runtime checks instead +#[derive(Debug)] +pub struct VPortal { + pub(crate) link_idx: Cell, + pub(crate) scope_id: Cell>, + pub(crate) node: *const VNode<'static>, +} + +impl PartialEq for VPortal { + fn eq(&self, other: &Self) -> bool { + self.node == other.node + } +} + /// Virtual Components for custom user-defined components /// Only supports the functional syntax pub struct VComponent<'src> { @@ -379,45 +387,14 @@ pub struct VComponent<'src> { pub(crate) bump_props: *const (), // during the "teardown" process we'll take the caller out so it can be dropped properly - // pub(crate) caller: Option>, pub(crate) caller: &'src dyn Fn(&'src Scope) -> Element, pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>, + // todo: re-add this pub(crate) drop_props: RefCell>>, } -pub struct VSuspended { - pub task_id: usize, - pub scope: Cell>, - pub dom_id: Cell>, - pub parent: Cell>, -} - -/// A cached node is a "pointer" to a "rendered" node in a particular scope -/// -/// It does not provide direct access to the node, so it doesn't carry any lifetime information with it -/// -/// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key -/// is essentially a unique key to guarantee safe usage of the Node. -/// -/// Linked VNodes can only be made through the [`Context::render`] method -/// -/// Typically, NodeLinks are found *not* in a VNode. When NodeLinks are in a VNode, the NodeLink was passed into -/// an `rsx!` call. -#[derive(Debug)] -pub struct NodeLink { - pub(crate) link_idx: Cell, - pub(crate) scope_id: Cell>, - pub(crate) node: *const VNode<'static>, -} - -impl PartialEq for NodeLink { - fn eq(&self, other: &Self) -> bool { - self.node == other.node - } -} - /// This struct provides an ergonomic API to quickly build VNodes. /// /// NodeFactory is used to build VNodes in the component's memory space. @@ -637,8 +614,8 @@ impl<'a> NodeFactory<'a> { callback: BumpBox<'a, dyn FnMut(Arc) + 'a>, ) -> Listener<'a> { Listener { - mounted_node: Cell::new(None), event, + mounted_node: Cell::new(None), callback: RefCell::new(Some(callback)), } } @@ -647,15 +624,14 @@ impl<'a> NodeFactory<'a> { self, node_iter: impl IntoIterator + 'c> + 'b, ) -> VNode<'a> { - let bump = self.bump; - let mut nodes = bumpalo::collections::Vec::new_in(bump); + let mut nodes = bumpalo::collections::Vec::new_in(self.bump); for node in node_iter { nodes.push(node.into_vnode(self)); } if nodes.is_empty() { - nodes.push(VNode::Anchor(bump.alloc(VAnchor { + nodes.push(VNode::Placeholder(self.bump.alloc(VPlaceholder { dom_id: empty_cell(), }))); } @@ -670,26 +646,23 @@ impl<'a> NodeFactory<'a> { self, node_iter: impl IntoIterator + 'c> + 'b, ) -> VNode<'a> { - let bump = self.bump; - let mut nodes = bumpalo::collections::Vec::new_in(bump); + let mut nodes = bumpalo::collections::Vec::new_in(self.bump); for node in node_iter { nodes.push(node.into_vnode(self)); } if nodes.is_empty() { - nodes.push(VNode::Anchor(bump.alloc(VAnchor { + nodes.push(VNode::Placeholder(self.bump.alloc(VPlaceholder { dom_id: empty_cell(), }))); } let children = nodes.into_bump_slice(); - // todo: add a backtrace if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none() { - use backtrace::Backtrace; - let bt = Backtrace::new(); + // todo: make the backtrace prettier or remove it altogether log::error!( r#" Warning: Each child in an array or iterator should have a unique "key" prop. @@ -698,7 +671,7 @@ impl<'a> NodeFactory<'a> { ------------- {:?} "#, - bt + backtrace::Backtrace::new() ); } @@ -722,7 +695,7 @@ impl<'a> NodeFactory<'a> { } if nodes.is_empty() { - nodes.push(VNode::Anchor(bump.alloc(VAnchor { + nodes.push(VNode::Placeholder(bump.alloc(VPlaceholder { dom_id: empty_cell(), }))); } @@ -753,18 +726,12 @@ impl<'a> NodeFactory<'a> { key: None, }); let ptr = self.bump.alloc(frag) as *const _; - Some(NodeLink { + Some(VPortal { link_idx: Default::default(), scope_id: Default::default(), node: unsafe { std::mem::transmute(ptr) }, }) } - - pub fn annotate_lazy<'z, 'b>( - f: impl FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b, - ) -> Option> { - Some(LazyNodes::new(f)) - } } impl Debug for NodeFactory<'_> { @@ -860,10 +827,10 @@ impl IntoVNode<'_> for Arguments<'_> { } // called cx.render from a helper function -impl IntoVNode<'_> for Option { +impl IntoVNode<'_> for Option { fn into_vnode(self, _cx: NodeFactory) -> VNode { match self { - Some(node) => VNode::Linked(node), + Some(node) => VNode::Portal(node), None => { todo!() } @@ -873,10 +840,10 @@ impl IntoVNode<'_> for Option { // essentially passing elements through props // just build a new element in place -impl IntoVNode<'_> for &Option { +impl IntoVNode<'_> for &Option { fn into_vnode(self, _cx: NodeFactory) -> VNode { match self { - Some(node) => VNode::Linked(NodeLink { + Some(node) => VNode::Portal(VPortal { link_idx: node.link_idx.clone(), scope_id: node.scope_id.clone(), node: node.node, @@ -889,15 +856,15 @@ impl IntoVNode<'_> for &Option { } } -impl IntoVNode<'_> for NodeLink { +impl IntoVNode<'_> for VPortal { fn into_vnode(self, _cx: NodeFactory) -> VNode { - VNode::Linked(self) + VNode::Portal(self) } } -impl IntoVNode<'_> for &NodeLink { +impl IntoVNode<'_> for &VPortal { fn into_vnode(self, _cx: NodeFactory) -> VNode { - VNode::Linked(NodeLink { + VNode::Portal(VPortal { link_idx: self.link_idx.clone(), scope_id: self.scope_id.clone(), node: self.node, diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 4a2eabd68..debea22de 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -1,7 +1,6 @@ use crate::innerlude::*; use futures_channel::mpsc::UnboundedSender; -use fxhash::FxHashMap; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -44,6 +43,8 @@ pub type Context<'a> = &'a Scope; /// use case they might have. pub struct Scope { pub(crate) parent_scope: Option<*mut Scope>, + + // parent element I think? pub(crate) container: ElementId, pub(crate) our_arena_idx: ScopeId, @@ -62,7 +63,9 @@ pub struct Scope { pub(crate) items: RefCell>, - pub(crate) hooks: HookList, + pub(crate) hook_arena: Bump, + pub(crate) hook_vals: RefCell>, + pub(crate) hook_idx: Cell, pub(crate) shared_contexts: RefCell>>, @@ -72,15 +75,13 @@ pub struct Scope { pub struct SelfReferentialItems<'a> { pub(crate) listeners: Vec<&'a Listener<'a>>, pub(crate) borrowed_props: Vec<&'a VComponent<'a>>, - pub(crate) suspended_nodes: FxHashMap, pub(crate) tasks: Vec>>, - pub(crate) pending_effects: Vec>, } /// A component's unique identifier. /// -/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is -/// unmounted, then the `ScopeId` will be reused for a new component. +/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused +/// once a component has been unmounted. #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ScopeId(pub usize); @@ -103,7 +104,9 @@ impl Scope { /// /// assert_eq!(base.subtree(), 0); /// ``` - pub fn subtree(&self) -> u32 { + /// + /// todo: enable + pub(crate) fn _subtree(&self) -> u32 { self.subtree.get() } @@ -122,14 +125,13 @@ impl Scope { /// rsx!(cx, div { "Subtree {id}"}) /// }; /// ``` - pub fn create_subtree(&self) -> Option { + /// + /// todo: enable subtree + pub(crate) fn _create_subtree(&self) -> Option { if self.is_subtree_root.get() { None } else { todo!() - // let cur = self.subtree().get(); - // self.shared.cur_subtree.set(cur + 1); - // Some(cur) } } @@ -140,7 +142,7 @@ impl Scope { /// # Example /// /// ```rust, ignore - /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props| cx.render(rsx!{ div {} })); /// dom.rebuild(); /// /// let base = dom.base_scope(); @@ -160,7 +162,7 @@ impl Scope { /// # Example /// /// ```rust, ignore - /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props| cx.render(rsx!{ div {} })); /// dom.rebuild(); /// /// let base = dom.base_scope(); @@ -168,6 +170,7 @@ impl Scope { /// assert_eq!(base.parent(), None); /// ``` pub fn parent(&self) -> Option { + // safety: the pointer to our parent is *always* valid thanks to the bump arena self.parent_scope.map(|p| unsafe { &*p }.our_arena_idx) } @@ -178,7 +181,7 @@ impl Scope { /// # Example /// /// ```rust, ignore - /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); + /// let mut dom = VirtualDom::new(|cx, props| cx.render(rsx!{ div {} })); /// dom.rebuild(); /// let base = dom.base_scope(); /// @@ -192,11 +195,8 @@ impl Scope { /// /// ## Notice: you should prefer using prepare_update and get_scope_id pub fn schedule_update(&self) -> Rc { - // pub fn schedule_update(&self) -> Rc { - let chan = self.sender.clone(); - let id = self.scope_id(); + let (chan, id) = (self.sender.clone(), self.scope_id()); Rc::new(move || { - log::debug!("set on channel an update for scope {:?}", id); let _ = chan.unbounded_send(SchedulerMsg::Immediate(id)); }) } @@ -284,25 +284,11 @@ impl Scope { } } - /// Push an effect to be ran after the component has been successfully mounted to the dom - /// Returns the effect's position in the stack - pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize { - // this is some tricker to get around not being able to actually call fnonces - let mut slot = Some(effect); - let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()()); - - // wrap it in a type that will actually drop the contents - let boxed_fut = unsafe { BumpBox::from_raw(fut) }; - - // erase the 'src lifetime for self-referential storage - let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) }; - - let mut items = self.items.borrow_mut(); - items.pending_effects.push(self_ref_fut); - items.pending_effects.len() - 1 - } - - /// Pushes the future onto the poll queue to be polled + /// Pushes the future onto the poll queue to be polled after the component renders. + /// + /// + /// + /// /// The future is forcibly dropped if the component is not ready by the next render pub fn push_task<'src, F: Future>( &'src self, @@ -313,7 +299,7 @@ impl Scope { F: 'src, { self.sender - .unbounded_send(SchedulerMsg::TaskPushed(self.our_arena_idx)) + .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx)) .unwrap(); // allocate the future @@ -347,7 +333,7 @@ impl Scope { /// cx.render(lazy_tree) /// } ///``` - pub fn render<'src>(&'src self, rsx: Option>) -> Option { + pub fn render<'src>(&'src self, rsx: Option>) -> Option { let frame = self.wip_frame(); let bump = &frame.bump; let factory = NodeFactory { bump }; @@ -357,7 +343,7 @@ impl Scope { let node_ptr = node as *mut _; let node_ptr = unsafe { std::mem::transmute(node_ptr) }; - let link = NodeLink { + let link = VPortal { scope_id: Cell::new(Some(self.our_arena_idx)), link_idx: Cell::new(0), node: node_ptr, @@ -366,31 +352,6 @@ impl Scope { Some(link) } - pub fn suspend<'src, F: Future + 'src>( - &'src self, - mut fut: impl FnMut() -> F, - ) -> Option { - let channel = self.sender.clone(); - let node_fut = fut(); - - let scope = self.scope_id(); - - // self.push_task(move || { - // - // async move { - // // - // let r = node_fut.await; - // if let Some(node) = r { - // channel - // .unbounded_send(SchedulerMsg::Suspended { node, scope }) - // .unwrap(); - // } - // } - // }); - - todo!() - } - /// Store a value between renders /// /// This is *the* foundational hook for all other hooks. @@ -417,24 +378,35 @@ impl Scope { initializer: impl FnOnce(usize) -> State, runner: impl FnOnce(&'src mut State) -> Output, ) -> Output { - if self.hooks.at_end() { - self.hooks.push_hook(initializer(self.hooks.len())); + let mut vals = self.hook_vals.borrow_mut(); + let hook_len = vals.len(); + let cur_idx = self.hook_idx.get(); + + if cur_idx >= hook_len { + let val = self.hook_arena.alloc(initializer(hook_len)); + vals.push(val); } - const HOOK_ERR_MSG: &str = r###" -Unable to retrieve the hook that was initialized at this index. -Consult the `rules of hooks` to understand how to use hooks properly. + let state = vals + .get(cur_idx) + .and_then(|inn| { + self.hook_idx.set(cur_idx + 1); + let raw_box = unsafe { &mut **inn }; + raw_box.downcast_mut::() + }) + .expect( + r###" + Unable to retrieve the hook that was initialized at this index. + Consult the `rules of hooks` to understand how to use hooks properly. -You likely used the hook in a conditional. Hooks rely on consistent ordering between renders. -Functions prefixed with "use" should never be called conditionally. -"###; + You likely used the hook in a conditional. Hooks rely on consistent ordering between renders. + Functions prefixed with "use" should never be called conditionally. + "###, + ); - runner(self.hooks.next::().expect(HOOK_ERR_MSG)) + runner(state) } -} -// Important internal methods -impl Scope { /// The "work in progress frame" represents the frame that is currently being worked on. pub(crate) fn wip_frame(&self) -> &BumpFrame { match self.generation.get() & 1 == 0 { @@ -471,25 +443,6 @@ impl Scope { self.generation.set(self.generation.get() + 1); } - // General strategy here is to load up the appropriate suspended task and then run it. - // Suspended nodes cannot be called repeatedly. - pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) { - let mut nodes = &mut self.items.get_mut().suspended_nodes; - - if let Some(suspended) = nodes.remove(&task_id) { - let sus: &'a VSuspended = suspended; - // let mut boxed = sus.callback.borrow_mut().take().unwrap(); - // let new_node: Element = boxed(); - } - } - - // run the list of effects - pub(crate) fn run_effects(&mut self) { - for mut effect in self.items.get_mut().pending_effects.drain(..) { - effect(); - } - } - pub fn root_node(&self) -> &VNode { let node = *self.wip_frame().nodes.borrow().get(0).unwrap(); unsafe { std::mem::transmute(&*node) } @@ -514,11 +467,7 @@ impl BumpFrame { Self { bump, nodes } } - pub fn allocated_bytes(&self) -> usize { - self.bump.allocated_bytes() - } - - pub fn assign_nodelink(&self, node: &NodeLink) { + pub fn assign_nodelink(&self, node: &VPortal) { let mut nodes = self.nodes.borrow_mut(); let len = nodes.len(); @@ -528,78 +477,6 @@ impl BumpFrame { } } -/// An abstraction over internally stored data using a hook-based memory layout. -/// -/// Hooks are allocated using Boxes and then our stored references are given out. -/// -/// It's unsafe to "reset" the hooklist, but it is safe to add hooks into it. -/// -/// Todo: this could use its very own bump arena, but that might be a tad overkill -#[derive(Default)] -pub(crate) struct HookList { - arena: Bump, - vals: RefCell>, - idx: Cell, -} - -impl HookList { - pub fn new(capacity: usize) -> Self { - Self { - arena: Bump::with_capacity(capacity), - ..Default::default() - } - } - - pub(crate) fn next(&self) -> Option<&mut T> { - self.vals.borrow().get(self.idx.get()).and_then(|inn| { - self.idx.set(self.idx.get() + 1); - let raw_box = unsafe { &mut **inn }; - raw_box.downcast_mut::() - }) - } - - /// This resets the internal iterator count - /// It's okay that we've given out each hook, but now we have the opportunity to give it out again - /// Therefore, resetting is considered unsafe - /// - /// This should only be ran by Dioxus itself before "running scope". - /// Dioxus knows how to descend through the tree to prevent mutable aliasing. - pub(crate) unsafe fn reset(&self) { - self.idx.set(0); - } - - pub(crate) fn push_hook(&self, new: T) { - let val = self.arena.alloc(new); - self.vals.borrow_mut().push(val) - } - - pub(crate) fn len(&self) -> usize { - self.vals.borrow().len() - } - - pub(crate) fn cur_idx(&self) -> usize { - self.idx.get() - } - - pub(crate) fn at_end(&self) -> bool { - self.cur_idx() >= self.len() - } - - pub fn clear(&mut self) { - self.vals.borrow_mut().drain(..).for_each(|state| { - let as_mut = unsafe { &mut *state }; - let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) }; - drop(boxed); - }); - } - - /// Get the ammount of memory a hooklist uses - /// Used in heuristics - pub fn get_hook_arena_size(&self) -> usize { - self.arena.allocated_bytes() - } -} - #[test] fn sizeof() { dbg!(std::mem::size_of::()); diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index dd1f555cd..1b2643d20 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -92,15 +92,15 @@ impl ScopeArena { let new_scope_id = ScopeId(self.scope_counter.get()); self.scope_counter.set(self.scope_counter.get() + 1); - log::debug!("new scope {:?} with parent {:?}", new_scope_id, container); + // log::debug!("new scope {:?} with parent {:?}", new_scope_id, container); if let Some(old_scope) = self.free_scopes.borrow_mut().pop() { let scope = unsafe { &mut *old_scope }; - log::debug!( - "reusing scope {:?} as {:?}", - scope.our_arena_idx, - new_scope_id - ); + // log::debug!( + // "reusing scope {:?} as {:?}", + // scope.our_arena_idx, + // new_scope_id + // ); scope.caller = caller; scope.parent_scope = parent_scope; @@ -180,16 +180,17 @@ impl ScopeArena { caller, generation: 0.into(), - hooks: HookList::new(hook_capacity), shared_contexts: Default::default(), items: RefCell::new(SelfReferentialItems { listeners: Default::default(), borrowed_props: Default::default(), - suspended_nodes: Default::default(), tasks: Default::default(), - pending_effects: Default::default(), }), + + hook_arena: Bump::new(), + hook_vals: RefCell::new(smallvec::SmallVec::with_capacity(hook_capacity)), + hook_idx: Default::default(), }); let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope); @@ -202,7 +203,7 @@ impl ScopeArena { pub fn try_remove(&self, id: &ScopeId) -> Option<()> { self.ensure_drop_safety(id); - log::debug!("removing scope {:?}", id); + // log::debug!("removing scope {:?}", id); // Safety: // - ensure_drop_safety ensures that no references to this scope are in use @@ -210,7 +211,14 @@ impl ScopeArena { let scope = unsafe { &mut *self.scopes.borrow_mut().remove(id).unwrap() }; // we're just reusing scopes so we need to clear it out - scope.hooks.clear(); + scope.hook_vals.get_mut().drain(..).for_each(|state| { + let as_mut = unsafe { &mut *state }; + let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) }; + drop(boxed); + }); + scope.hook_idx.set(0); + scope.hook_arena.reset(); + scope.shared_contexts.get_mut().clear(); scope.parent_scope = None; scope.generation.set(0); @@ -226,15 +234,11 @@ impl ScopeArena { let SelfReferentialItems { borrowed_props, listeners, - pending_effects, - suspended_nodes, tasks, } = scope.items.get_mut(); borrowed_props.clear(); listeners.clear(); - pending_effects.clear(); - suspended_nodes.clear(); tasks.clear(); self.free_scopes.borrow_mut().push(scope); @@ -311,12 +315,10 @@ impl ScopeArena { let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") }; - log::debug!("found scope, about to run: {:?}", id); - // Safety: // - We dropped the listeners, so no more &mut T can be used while these are held // - All children nodes that rely on &mut T are replaced with a new reference - unsafe { scope.hooks.reset() }; + scope.hook_idx.set(0); // Safety: // - We've dropped all references to the wip bump frame with "ensure_drop_safety" @@ -326,16 +328,12 @@ impl ScopeArena { let mut items = scope.items.borrow_mut(); // just forget about our suspended nodes while we're at it - items.suspended_nodes.clear(); items.tasks.clear(); - items.pending_effects.clear(); // guarantee that we haven't screwed up - there should be no latent references anywhere debug_assert!(items.listeners.is_empty()); debug_assert!(items.borrowed_props.is_empty()); - debug_assert!(items.suspended_nodes.is_empty()); debug_assert!(items.tasks.is_empty()); - debug_assert!(items.pending_effects.is_empty()); // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders. scope.wip_frame().nodes.borrow_mut().clear(); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index fe1fcac23..1de0e83aa 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -8,12 +8,9 @@ use futures_util::{Future, StreamExt}; use fxhash::FxHashSet; use indexmap::IndexSet; use smallvec::SmallVec; -use std::pin::Pin; -use std::sync::Arc; -use std::task::Poll; -use std::{any::Any, collections::VecDeque}; +use std::{any::Any, collections::VecDeque, pin::Pin, sync::Arc, task::Poll}; -/// A virtual node system that progresses user events and diffs UI trees. +/// A virtual node sΒ ystem that progresses user events and diffs UI trees. /// /// /// ## Guide @@ -286,7 +283,7 @@ impl VirtualDom { if let Some(msg) = self.pending_messages.pop_back() { match msg { // just keep looping, the task is now saved but we should actually poll it - SchedulerMsg::TaskPushed(id) => { + SchedulerMsg::NewTask(id) => { self.scopes.pending_futures.borrow_mut().insert(id); } SchedulerMsg::UiEvent(event) => { @@ -297,7 +294,6 @@ impl VirtualDom { SchedulerMsg::Immediate(s) => { self.dirty_scopes.insert(s); } - SchedulerMsg::Suspended { scope } => todo!(), } } } @@ -352,7 +348,6 @@ impl VirtualDom { let mut committed_mutations = vec![]; while !self.dirty_scopes.is_empty() { - log::debug!("working with deadline"); let scopes = &self.scopes; let mut diff_state = DiffState::new(scopes); @@ -361,6 +356,7 @@ impl VirtualDom { // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height self.dirty_scopes .retain(|id| scopes.get_scope(id).is_some()); + self.dirty_scopes.sort_by(|a, b| { let h1 = scopes.get_scope(a).unwrap().height; let h2 = scopes.get_scope(b).unwrap().height; @@ -386,18 +382,16 @@ impl VirtualDom { } if diff_state.work(&mut deadline) { - let DiffState { - mutations, - seen_scopes, - .. - } = diff_state; + let DiffState { mutations, .. } = diff_state; - for scope in seen_scopes { - self.dirty_scopes.remove(&scope); + for scope in &mutations.dirty_scopes { + self.dirty_scopes.remove(scope); } committed_mutations.push(mutations); } else { + log::debug!("Could not finish work in time"); + // leave the work in an incomplete state return committed_mutations; } @@ -539,6 +533,7 @@ impl VirtualDom { } } +#[derive(Debug)] pub enum SchedulerMsg { // events from the host UiEvent(UserEvent), @@ -547,9 +542,7 @@ pub enum SchedulerMsg { Immediate(ScopeId), // an async task pushed from an event handler (or just spawned) - TaskPushed(ScopeId), - - Suspended { scope: ScopeId }, + NewTask(ScopeId), } /// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel. @@ -678,9 +671,9 @@ impl<'a> Future for PollTasks<'a> { // I think the futures neeed to be pinned using bumpbox or something // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move let task_mut = task.as_mut(); - let unpinned = unsafe { Pin::new_unchecked(task_mut) }; + let pinned = unsafe { Pin::new_unchecked(task_mut) }; - if unpinned.poll(cx).is_ready() { + if pinned.poll(cx).is_ready() { all_pending = false } else { unfinished_tasks.push(task); diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index 1145cbcfa..5811da41d 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -5,7 +5,7 @@ //! //! It does not validated that component lifecycles work properly. This is done in another test file. -use dioxus::{prelude::*, DomEdit, VSuspended}; +use dioxus::{prelude::*, DomEdit}; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; diff --git a/packages/coroutines/Cargo.toml b/packages/coroutines/Cargo.toml new file mode 100644 index 000000000..1b2632366 --- /dev/null +++ b/packages/coroutines/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "dioxus-coroutines" +version = "0.0.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/packages/coroutines/src/lib.rs b/packages/coroutines/src/lib.rs new file mode 100644 index 000000000..1b4a90c93 --- /dev/null +++ b/packages/coroutines/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index a2bffd8de..40ac7b6ed 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -17,7 +17,7 @@ thiserror = "1.0.23" log = "0.4.13" html-escape = "0.2.9" wry = "0.12.2" -futures-channel = "0.3.16" +futures-channel = "0.3.18" tokio = { version = "1.12.0", features = [ "sync", "rt-multi-thread", diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 2888ae223..0c1663488 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -7,7 +7,7 @@ fn main() { dioxus::desktop::launch(App, |c| c) } -static App: FC<()> = |(cx, props)| { +static App: FC<()> = |cx, props| { let (count, set_count) = use_state(cx, || 0); cx.render(rsx!( @@ -34,7 +34,7 @@ Window management, system trays, notifications, and other desktop-related functi Managing windows is done by simply rendering content into a `WebviewWindow` component. ```rust -static App: FC<()> = |(cx, props)| { +static App: FC<()> = |cx, props| { rsx!(cx, WebviewWindow { "hello world" } ) } ``` @@ -46,7 +46,7 @@ Notifications also use a declarative approach. Sending a notification has never The api has been somewhat modeled after https://github.com/mikaelbr/node-notifier ```rust -static Notifications: FC<()> = |(cx, props)| { +static Notifications: FC<()> = |cx, props| { cx.render(rsx!( Notification { title: "title" @@ -78,7 +78,7 @@ static Notifications: FC<()> = |(cx, props)| { Dioxus Desktop supports app trays, which can be built with native menu groups or with a custom window. ```rust -static Tray: FC<()> = |(cx, props)| { +static Tray: FC<()> = |cx, props| { cx.render(rsx!( GlobalTray { MenuGroup { @@ -90,7 +90,7 @@ static Tray: FC<()> = |(cx, props)| { }; // using a builder -static Tray: FC<()> = |(cx, props)| { +static Tray: FC<()> = |cx, props| { let menu = MenuGroup::builder(cx) .with_items([ MenuGroupItem::builder() @@ -107,7 +107,7 @@ static Tray: FC<()> = |(cx, props)| { } // or with a custom window -static Tray: FC<()> = |(cx, props)| { +static Tray: FC<()> = |cx, props| { rsx!(cx, GlobalTray { div { "custom buttons here" } }) }; ``` @@ -116,7 +116,7 @@ static Tray: FC<()> = |(cx, props)| { Declaring menus is convenient and cross-platform. ```rust -static Menu: FC<()> = |(cx, props)| { +static Menu: FC<()> = |cx, props| { cx.render(rsx!( MenuBarMajorItem { title: "File" MenuGroup { diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index 3e8b73d6f..be0b7847b 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -11,14 +11,16 @@ use dioxus_hooks::*; use dioxus_html as dioxus_elements; fn main() { + simple_logger::init().unwrap(); dioxus_desktop::launch(App, |c| c); } static App: FC<()> = |cx, props| { let mut count = use_state(cx, || 0); + log::debug!("count is {:?}", count); cx.push_task(|| async move { - tokio::time::sleep(Duration::from_millis(100)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; count += 1; }); diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 9b74a048a..49d781465 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -1,14 +1,7 @@ -use std::ops::{Deref, DerefMut}; - use dioxus_core::DomEdit; use wry::{ - application::{ - error::OsError, - event_loop::{EventLoop, EventLoopWindowTarget}, - menu::MenuBar, - window::{Fullscreen, Icon, Window, WindowBuilder}, - }, - webview::{RpcRequest, RpcResponse, WebView}, + application::{event_loop::EventLoop, window::WindowBuilder}, + webview::WebView, }; pub struct DesktopConfig<'a> { @@ -22,7 +15,7 @@ impl<'a> DesktopConfig<'a> { /// Initializes a new `WindowBuilder` with default values. #[inline] pub fn new() -> Self { - let mut window = WindowBuilder::new().with_title("Dioxus app"); + let window = WindowBuilder::new().with_title("Dioxus app"); Self { event_handler: None, window, @@ -41,13 +34,24 @@ impl<'a> DesktopConfig<'a> { self } - pub fn with_window(&mut self, f: impl FnOnce(WindowBuilder) -> WindowBuilder) -> &mut Self { + pub fn with_window( + &mut self, + configure: impl FnOnce(WindowBuilder) -> WindowBuilder, + ) -> &mut Self { // gots to do a swap because the window builder only takes itself as muy self // I wish more people knew about returning &mut Self let mut builder = WindowBuilder::default().with_title("Dioxus App"); std::mem::swap(&mut self.window, &mut builder); - builder = f(builder); + builder = configure(builder); std::mem::swap(&mut self.window, &mut builder); self } + + pub fn with_event_handler( + &mut self, + handler: impl Fn(&mut EventLoop<()>, &mut WebView) + 'static, + ) -> &mut Self { + self.event_handler = Some(Box::new(handler)); + self + } } diff --git a/packages/desktop/src/dom.rs b/packages/desktop/src/dom.rs deleted file mode 100644 index 09323d40b..000000000 --- a/packages/desktop/src/dom.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! webview dom - -use dioxus_core::DomEdit; - -// pub struct WebviewRegistry {} - -// impl WebviewRegistry { -// pub fn new() -> Self { -// Self {} -// } -// } - -pub struct WebviewDom<'bump> { - pub edits: Vec>, - pub node_counter: u64, - // pub registry: WebviewRegistry, -} -impl WebviewDom<'_> { - pub fn new() -> Self { - Self { - edits: Vec::new(), - node_counter: 0, - // registry, - } - } - - // // Finish using the dom (for its edit list) and give back the node and event registry - // pub fn consume(self) -> WebviewRegistry { - // self.registry - // } -} diff --git a/packages/desktop/src/err.rs b/packages/desktop/src/err.rs deleted file mode 100644 index 8b1378917..000000000 --- a/packages/desktop/src/err.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/desktop/src/escape.rs b/packages/desktop/src/escape.rs index 654ecbeeb..386a82b44 100644 --- a/packages/desktop/src/escape.rs +++ b/packages/desktop/src/escape.rs @@ -16,7 +16,8 @@ use std::fmt::{self, Write}; /// /// view.eval(&format!("callback({});", web_view::escape(string))); /// ``` -pub fn escape(string: &str) -> Escaper { +#[allow(unused)] +pub fn escape_js_string(string: &str) -> Escaper { Escaper(string) } @@ -76,6 +77,6 @@ impl<'a> fmt::Display for Escaper<'a> { #[test] fn test() { let plain = "ABC \n\r' abc \\ \u{2028} \u{2029}123"; - let escaped = escape(plain).to_string(); + let escaped = escape_js_string(plain).to_string(); assert!(escaped == "'ABC \\n\\r\\' abc \\\\ \\u2028 \\u2029123'"); } diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index ec5c6aebd..8f86c3270 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -1,10 +1,10 @@ //! Convert a serialized event to an event Trigger //! +use std::any::Any; use std::sync::Arc; -use std::{any::Any, rc::Rc}; -use dioxus_core::{ElementId, EventPriority, ScopeId, UserEvent}; +use dioxus_core::{ElementId, EventPriority, UserEvent}; use dioxus_html::on::*; #[derive(serde::Serialize, serde::Deserialize)] @@ -16,11 +16,11 @@ struct ImEvent { } pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent { - let mut ims: Vec = serde_json::from_value(val).unwrap(); + let ims: Vec = serde_json::from_value(val).unwrap(); + let ImEvent { event, mounted_dom_id, - // scope, contents, } = ims.into_iter().next().unwrap(); diff --git a/packages/desktop/src/index.html b/packages/desktop/src/index.html index 428a3b8d8..df3bd6436 100644 --- a/packages/desktop/src/index.html +++ b/packages/desktop/src/index.html @@ -19,10 +19,9 @@ -

+
- + diff --git a/packages/desktop/src/index.js b/packages/desktop/src/index.js index aca3d3139..5d93052c7 100644 --- a/packages/desktop/src/index.js +++ b/packages/desktop/src/index.js @@ -409,7 +409,7 @@ class Interpreter { } function main() { - let root = window.document.getElementById("_dioxusroot"); + let root = window.document.getElementById("main"); window.interpreter = new Interpreter(root); console.log(window.interpreter); diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 817e20499..9fb86b907 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -3,41 +3,31 @@ //! Render the Dioxus VirtualDom using the platform's native WebView implementation. //! -use std::borrow::BorrowMut; -use std::cell::{Cell, RefCell}; -use std::collections::{HashMap, VecDeque}; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; -use std::sync::atomic::AtomicBool; -use std::sync::mpsc::channel; -use std::sync::{Arc, RwLock}; +mod cfg; +mod escape; +mod events; +// mod desktop_context; use cfg::DesktopConfig; use dioxus_core::*; -use serde::{Deserialize, Serialize}; - -pub use wry; - -use wry::application::accelerator::{Accelerator, SysMods}; -use wry::application::event::{ElementState, Event, StartCause, WindowEvent}; -use wry::application::event_loop::{self, ControlFlow, EventLoop, EventLoopWindowTarget}; -use wry::application::keyboard::{Key, KeyCode, ModifiersState}; -use wry::application::menu::{MenuBar, MenuItem, MenuItemAttributes}; -use wry::application::window::{Fullscreen, WindowId}; -use wry::webview::{WebView, WebViewBuilder}; +use std::{ + collections::{HashMap, VecDeque}, + sync::atomic::AtomicBool, + sync::{Arc, RwLock}, +}; +use tokio::task::LocalSet; use wry::{ - application::menu, - application::window::{Window, WindowBuilder}, - webview::{RpcRequest, RpcResponse}, + application::{ + accelerator::{Accelerator, SysMods}, + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, + keyboard::{KeyCode, ModifiersState}, + menu::{MenuBar, MenuItem}, + window::{Window, WindowId}, + }, + webview::RpcRequest, + webview::{WebView, WebViewBuilder}, }; - -mod cfg; -mod desktop_context; -mod dom; -mod escape; -mod events; - -static HTML_CONTENT: &'static str = include_str!("./index.html"); pub fn launch( root: FC<()>, @@ -54,7 +44,7 @@ pub fn launch_with_props( run(root, props, builder) } -#[derive(Serialize)] +#[derive(serde::Serialize)] struct Response<'a> { pre_rendered: Option, edits: Vec>, @@ -65,88 +55,43 @@ pub fn run( props: T, user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, ) { - // Generate the config - let mut cfg = DesktopConfig::new(); - user_builder(&mut cfg); - let DesktopConfig { - window: window_cfg, - manual_edits, - pre_rendered, - .. - } = cfg; - - // All of our webview windows are stored in a way that we can look them up later - // The "DesktopContext" will provide functionality for spawning these windows - let mut webviews = HashMap::::new(); - let event_loop = EventLoop::new(); - - let props_shared = Cell::new(Some(props)); - - // create local modifier state - let modifiers = ModifiersState::default(); + let mut desktop_cfg = DesktopConfig::new(); + user_builder(&mut desktop_cfg); + let mut state = DesktopController::new_on_tokio(root, props); let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ); - - let edit_queue = Arc::new(RwLock::new(VecDeque::new())); - let is_ready: Arc = Default::default(); + let modifiers = ModifiersState::default(); + let event_loop = EventLoop::new(); event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; match window_event { - Event::NewEvents(StartCause::Init) => { - let window = create_window(event_loop, &window_cfg); - let window_id = window.id(); - let sender = - launch_vdom_with_tokio(root, props_shared.take().unwrap(), edit_queue.clone()); - let webview = create_webview(window, is_ready.clone(), sender); - webviews.insert(window_id, webview); - } + Event::NewEvents(StartCause::Init) => state.new_window(&desktop_cfg, event_loop), Event::WindowEvent { event, window_id, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Destroyed { .. } => { - webviews.remove(&window_id); - if webviews.is_empty() { - *control_flow = ControlFlow::Exit; - } - } - WindowEvent::Moved(pos) => { - // - } + WindowEvent::Destroyed { .. } => state.close_window(window_id, control_flow), WindowEvent::KeyboardInput { event, .. } => { if quit_hotkey.matches(&modifiers, &event.physical_key) { - webviews.remove(&window_id); - if webviews.is_empty() { - *control_flow = ControlFlow::Exit; - } + state.close_window(window_id, control_flow); } } WindowEvent::Resized(_) | WindowEvent::Moved(_) => { - if let Some(view) = webviews.get_mut(&window_id) { + if let Some(view) = state.webviews.get_mut(&window_id) { let _ = view.resize(); } } - // TODO: we want to shuttle all of these events into the user's app + + // TODO: we want to shuttle all of these events into the user's app or provide some handler _ => {} }, - Event::MainEventsCleared => { - // I hate this ready hack but it's needed to wait for the "onload" to occur - // We can't run any initializion scripts because the window isn't ready yet? - if is_ready.load(std::sync::atomic::Ordering::Relaxed) { - let mut queue = edit_queue.write().unwrap(); - let (id, view) = webviews.iter_mut().next().unwrap(); - while let Some(edit) = queue.pop_back() { - view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) - .unwrap(); - } - } - } + Event::MainEventsCleared => state.try_load_ready_webviews(), Event::Resumed => {} Event::Suspended => {} Event::LoopDestroyed => {} @@ -156,120 +101,160 @@ pub fn run( }) } -// Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom. -pub(crate) fn launch_vdom_with_tokio( - root: FC

, - props: P, - edit_queue: Arc>>, -) -> futures_channel::mpsc::UnboundedSender { - let (sender, receiver) = futures_channel::mpsc::unbounded::(); - let return_sender = sender.clone(); +pub struct DesktopController { + webviews: HashMap, + sender: futures_channel::mpsc::UnboundedSender, + pending_edits: Arc>>, + quit_app_on_close: bool, + is_ready: Arc, +} - std::thread::spawn(move || { - // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads - let runtime = tokio::runtime::Builder::new_multi_thread() - .enable_all() +impl DesktopController { + // Launch the virtualdom on its own thread managed by tokio + // returns the desktop state + pub fn new_on_tokio(root: FC

, props: P) -> Self { + let edit_queue = Arc::new(RwLock::new(VecDeque::new())); + let pending_edits = edit_queue.clone(); + + let (sender, receiver) = futures_channel::mpsc::unbounded::(); + let return_sender = sender.clone(); + + std::thread::spawn(move || { + // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + + runtime.block_on(async move { + // LocalSet::new().block_on(&runtime, async move { + let mut dom = + VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver); + + let edits = dom.rebuild(); + + edit_queue + .write() + .unwrap() + .push_front(serde_json::to_string(&edits.edits).unwrap()); + + loop { + dom.wait_for_work().await; + let mut muts = dom.work_with_deadline(|| false); + while let Some(edit) = muts.pop() { + log::debug!("found mutations {:?}", muts); + edit_queue + .write() + .unwrap() + .push_front(serde_json::to_string(&edit.edits).unwrap()); + } + } + }) + }); + + Self { + pending_edits, + sender: return_sender, + + webviews: HashMap::new(), + is_ready: Arc::new(AtomicBool::new(false)), + quit_app_on_close: true, + } + } + + pub fn new_window(&mut self, cfg: &DesktopConfig, event_loop: &EventLoopWindowTarget<()>) { + let builder = cfg.window.clone().with_menu({ + // create main menubar menu + let mut menu_bar_menu = MenuBar::new(); + + // create `first_menu` + let mut first_menu = MenuBar::new(); + + first_menu.add_native_item(MenuItem::About("Todos".to_string())); + first_menu.add_native_item(MenuItem::Services); + first_menu.add_native_item(MenuItem::Separator); + first_menu.add_native_item(MenuItem::Hide); + first_menu.add_native_item(MenuItem::HideOthers); + first_menu.add_native_item(MenuItem::ShowAll); + + first_menu.add_native_item(MenuItem::Quit); + first_menu.add_native_item(MenuItem::CloseWindow); + + // create second menu + let mut second_menu = MenuBar::new(); + + // second_menu.add_submenu("Sub menu", true, my_sub_menu); + second_menu.add_native_item(MenuItem::Copy); + second_menu.add_native_item(MenuItem::Paste); + second_menu.add_native_item(MenuItem::SelectAll); + + menu_bar_menu.add_submenu("First menu", true, first_menu); + menu_bar_menu.add_submenu("Second menu", true, second_menu); + + menu_bar_menu + }); + + let window = builder.build(event_loop).unwrap(); + let window_id = window.id(); + + let (is_ready, sender) = (self.is_ready.clone(), self.sender.clone()); + + let webview = WebViewBuilder::new(window) + .unwrap() + .with_url("wry://index.html") + .unwrap() + .with_rpc_handler(move |_window: &Window, req: RpcRequest| { + match req.method.as_str() { + "user_event" => { + let event = events::trigger_from_serialized(req.params.unwrap()); + log::debug!("User event: {:?}", event); + sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap(); + } + "initialize" => { + is_ready.store(true, std::sync::atomic::Ordering::Relaxed); + } + _ => {} + } + // response always driven through eval. + // unfortunately, it seems to be pretty slow, so we might want to look into an RPC form + None + }) + // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case" + // For now, we only serve two pieces of content which get included as bytes into the final binary. + .with_custom_protocol("wry".into(), move |request| { + let path = request.uri().replace("wry://", ""); + let (data, meta) = match path.as_str() { + "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"), + "index.html/index.js" => { + (include_bytes!("./index.js").to_vec(), "text/javascript") + } + _ => unimplemented!("path {}", path), + }; + + wry::http::ResponseBuilder::new().mimetype(meta).body(data) + }) .build() .unwrap(); - runtime.block_on(async move { - let mut dom = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver); + self.webviews.insert(window_id, webview); + } - let edits = dom.rebuild(); + pub fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) { + self.webviews.remove(&window_id); - edit_queue - .write() - .unwrap() - .push_front(serde_json::to_string(&edits.edits).unwrap()); + if self.webviews.is_empty() && self.quit_app_on_close { + *control_flow = ControlFlow::Exit; + } + } - loop { - dom.wait_for_work().await; - let mut muts = dom.work_with_deadline(|| false); - while let Some(edit) = muts.pop() { - edit_queue - .write() - .unwrap() - .push_front(serde_json::to_string(&edit.edits).unwrap()); - } + pub fn try_load_ready_webviews(&mut self) { + if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) { + let mut queue = self.pending_edits.write().unwrap(); + let (_id, view) = self.webviews.iter_mut().next().unwrap(); + while let Some(edit) = queue.pop_back() { + view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) + .unwrap(); } - }) - }); - - return_sender -} - -fn build_menu() -> MenuBar { - // create main menubar menu - let mut menu_bar_menu = MenuBar::new(); - - // create `first_menu` - let mut first_menu = MenuBar::new(); - - first_menu.add_native_item(MenuItem::About("Todos".to_string())); - first_menu.add_native_item(MenuItem::Services); - first_menu.add_native_item(MenuItem::Separator); - first_menu.add_native_item(MenuItem::Hide); - first_menu.add_native_item(MenuItem::HideOthers); - first_menu.add_native_item(MenuItem::ShowAll); - - first_menu.add_native_item(MenuItem::Quit); - first_menu.add_native_item(MenuItem::CloseWindow); - - // create second menu - let mut second_menu = MenuBar::new(); - - // second_menu.add_submenu("Sub menu", true, my_sub_menu); - second_menu.add_native_item(MenuItem::Copy); - second_menu.add_native_item(MenuItem::Paste); - second_menu.add_native_item(MenuItem::SelectAll); - - menu_bar_menu.add_submenu("First menu", true, first_menu); - menu_bar_menu.add_submenu("Second menu", true, second_menu); - - menu_bar_menu -} - -fn create_window(event_loop: &EventLoopWindowTarget<()>, cfg: &WindowBuilder) -> Window { - let builder = cfg.clone().with_menu(build_menu()); - builder.build(event_loop).unwrap() -} - -fn create_webview( - window: Window, - is_ready: Arc, - sender: futures_channel::mpsc::UnboundedSender, -) -> WebView { - WebViewBuilder::new(window) - .unwrap() - .with_url("wry://index.html") - .unwrap() - .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| { - match req.method.as_str() { - "user_event" => { - let event = events::trigger_from_serialized(req.params.unwrap()); - log::debug!("User event: {:?}", event); - sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap(); - } - "initialize" => { - is_ready.store(true, std::sync::atomic::Ordering::Relaxed); - } - _ => {} - } - // always driven through eval - None - }) - // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case" - // For now, we only serve two pieces of content which get included as bytes into the final binary. - .with_custom_protocol("wry".into(), move |request| { - let path = request.uri().replace("wry://", ""); - let (data, meta) = match path.as_str() { - "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"), - "index.html/index.js" => (include_bytes!("./index.js").to_vec(), "text/javascript"), - _ => unimplemented!("path {}", path), - }; - - wry::http::ResponseBuilder::new().mimetype(meta).body(data) - }) - .build() - .unwrap() + } + } } diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index c8c304782..08feb8535 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -7,4 +7,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../../packages/core", version = "0.1.2" } +dioxus-core = { path = "../../packages/core", version = "0.1.3" } +futures = "0.3.18" diff --git a/packages/hooks/README.md b/packages/hooks/README.md index d0602c3dd..39ab816b5 100644 --- a/packages/hooks/README.md +++ b/packages/hooks/README.md @@ -19,7 +19,7 @@ You can always use it "normally" with the `split` method: let value = use_state(cx, || 10); // "Classic" usage: -let (value, set_value) = use_state(cx, || 0).classic(); +let (value, set_value) = use_state(cx, || 0).split(); ``` ## use_ref diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 3c5de0f2b..c09c287bb 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -6,3 +6,6 @@ pub use useref::*; mod use_shared_state; pub use use_shared_state::*; + +mod usecoroutine; +pub use usecoroutine::*; diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index f9856eeaa..12a5702e0 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -173,6 +173,6 @@ pub fn use_provide_state<'a, T: 'static>(cx: Context<'a>, f: impl FnOnce() -> T) }); cx.provide_state(state) }, - |inner| {}, + |_inner| {}, ) } diff --git a/packages/hooks/src/usecollection.rs b/packages/hooks/src/usecollection.rs index c82277291..d0b3b71e7 100644 --- a/packages/hooks/src/usecollection.rs +++ b/packages/hooks/src/usecollection.rs @@ -36,7 +36,7 @@ uses the same memoization on top of the use_context API. Here's a fully-functional todo app using the use_map API: ```rust -static TodoList: FC<()> = |(cx, props)|{ +static TodoList: FC<()> = |cx, props|{ let todos = use_map(cx, || HashMap::new()); let input = use_ref(|| None); diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs new file mode 100644 index 000000000..a8a10ebc2 --- /dev/null +++ b/packages/hooks/src/usecoroutine.rs @@ -0,0 +1,75 @@ +use dioxus_core::Context; +use futures::Future; +use std::{ + cell::{Cell, RefCell}, + pin::Pin, + rc::Rc, +}; + +pub fn use_coroutine<'a, F: Future + 'a>( + cx: Context<'a>, + f: impl FnOnce() -> F + 'a, +) -> CoroutineHandle { + // + cx.use_hook( + move |_| State { + running: Default::default(), + fut: Default::default(), + submit: Default::default(), + }, + |state| { + let fut_slot = state.fut.clone(); + let running = state.running.clone(); + let submit: Box = Box::new(move || { + let g = async move { + running.set(true); + f().await; + running.set(false); + }; + let p: Pin>> = Box::pin(g); + fut_slot + .borrow_mut() + .replace(unsafe { std::mem::transmute(p) }); + }); + + let submit = unsafe { std::mem::transmute(submit) }; + state.submit.get_mut().replace(submit); + + if state.running.get() { + let mut fut = state.fut.borrow_mut(); + cx.push_task(|| fut.as_mut().unwrap().as_mut()); + } else { + // make sure to drop the old future + if let Some(fut) = state.fut.borrow_mut().take() { + drop(fut); + } + } + CoroutineHandle { cx, inner: state } + }, + ) +} + +struct State { + running: Rc>, + submit: RefCell>>, + fut: Rc>>>>>, +} + +pub struct CoroutineHandle<'a> { + cx: Context<'a>, + inner: &'a State, +} + +impl<'a> CoroutineHandle<'a> { + pub fn start(&self) { + if self.inner.running.get() { + return; + } + if let Some(submit) = self.inner.submit.borrow_mut().take() { + submit(); + let mut fut = self.inner.fut.borrow_mut(); + self.cx.push_task(|| fut.as_mut().unwrap().as_mut()); + } + } + pub fn resume(&self) {} +} diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index 75ae9c82b..9ebfbfebf 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -33,6 +33,11 @@ impl<'a, T> UseRef<'a, T> { self.inner.value.borrow() } + pub fn set(&self, new: T) { + *self.inner.value.borrow_mut() = new; + self.needs_update(); + } + pub fn read_write(&self) -> (Ref<'_, T>, &Self) { (self.read(), self) } diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 83db764ee..d215c87a7 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -1,7 +1,7 @@ use dioxus_core::prelude::Context; use std::{ cell::{Cell, Ref, RefCell, RefMut}, - fmt::Display, + fmt::{Debug, Display}, ops::Not, rc::Rc, }; @@ -35,7 +35,7 @@ use std::{ /// /// Usage: /// ```ignore -/// const Example: FC<()> = |(cx, props)|{ +/// const Example: FC<()> = |cx, props|{ /// let counter = use_state(cx, || 0); /// let increment = |_| counter += 1; /// let decrement = |_| counter += 1; @@ -54,11 +54,14 @@ pub fn use_state<'a, T: 'static>( initial_state_fn: impl FnOnce() -> T, ) -> UseState<'a, T> { cx.use_hook( - move |_| UseStateInner { - current_val: initial_state_fn(), - update_callback: cx.schedule_update(), - wip: Rc::new(RefCell::new(None)), - update_scheuled: Cell::new(false), + move |_| { + // + UseStateInner { + current_val: initial_state_fn(), + update_callback: cx.schedule_update(), + wip: Rc::new(RefCell::new(None)), + update_scheuled: Cell::new(false), + } }, move |hook| { hook.update_scheuled.set(false); @@ -91,7 +94,11 @@ where UseState { inner: self.inner } } } - +impl Debug for UseState<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.inner.current_val) + } +} impl<'a, T: 'static> UseState<'a, T> { /// Tell the Dioxus Scheduler that we need to be processed pub fn needs_update(&self) { diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 38797ed6d..db4b3442c 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -717,8 +717,7 @@ builder_constructors! { nonce: Nonce, src: Uri, text: String, - r#async: Bool, - r#type: String, // TODO could be an enum + }; @@ -823,7 +822,6 @@ builder_constructors! { formnovalidate: Bool, formtarget: Target, name: Id, - r#type: ButtonType, value: String, }; @@ -1064,6 +1062,23 @@ impl input { volatile attributes */ +impl script { + // r#async: Bool, + // r#type: String, // TODO could be an enum + pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("type", val, None, false) + } + pub fn r#script<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("script", val, None, false) + } +} + +impl button { + pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("type", val, None, false) + } +} + impl select { pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { cx.attr("value", val, None, true) diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 93a811a76..6eea36766 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -22,7 +22,7 @@ web-sys = { version = "0.3", features = [ "Window", ], optional = true } -serde = "1" +serde = "1.0.130" serde_urlencoded = "0.7" @@ -30,10 +30,19 @@ wasm-bindgen = "0.2" js-sys = "0.3" gloo = "0.4" route-recognizer = "0.3.1" +url = "2.2.2" +url_serde = "0.2.0" [features] -default = ["web"] +default = ["web", "derive"] web = ["web-sys"] desktop = [] mobile = [] +derive = [] + +[dev-dependencies] +console_error_panic_hook = "0.1.7" +dioxus-web = { path = "../web" } +log = "0.4.14" +wasm-logger = "0.2.0" diff --git a/packages/router/README.md b/packages/router/README.md new file mode 100644 index 000000000..598c4f6a9 --- /dev/null +++ b/packages/router/README.md @@ -0,0 +1,40 @@ +# Router hook for Dioxus apps + +Dioxus-router provides a use_router hook that returns a different value depending on the route. +The router is generic over any value, however it makes sense to return a different set of VNodes +and feed them into the App's return VNodes. + +Using the router should feel similar to tide's routing framework where an "address" book is assembled at the head of the app. + +Here's an example of how to use the router hook: + +```rust +#[derive(Clone, PartialEq, Serialize, Deserialize, Routable)] +enum AppRoute { + Home, + Posts, + NotFound +} + +static App: FC<()> = |cx, props| { + let route = use_router(cx, AppRoute::parse); + + match route { + AppRoute::Home => rsx!(cx, Home {}) + AppRoute::Posts => rsx!(cx, Posts {}) + AppRoute::Notfound => rsx!(cx, Notfound {}) + } +}; +``` + +Adding links into your app: + +```rust +static Leaf: FC<()> = |cx, props| { + rsx!(cx, div { + Link { to: AppRoute::Home } + }) +} +``` + +Currently, the router is only supported in a web environment, but we plan to add 1st-party support via the context API when new renderers are available. diff --git a/packages/router/examples/simple.rs b/packages/router/examples/simple.rs new file mode 100644 index 000000000..4a317dd5c --- /dev/null +++ b/packages/router/examples/simple.rs @@ -0,0 +1,39 @@ +use dioxus_core::prelude::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_router::*; + +fn main() { + console_error_panic_hook::set_once(); + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + dioxus_web::launch(App, |c| c); +} + +static App: FC<()> = |cx, props| { + #[derive(Clone, Debug, PartialEq)] + enum Route { + Home, + About, + NotFound, + } + + let route = use_router(cx, |s| match s { + "/" => Route::Home, + "/about" => Route::About, + _ => Route::NotFound, + }); + + cx.render(rsx! { + div { + {match route { + Route::Home => rsx!(h1 { "Home" }), + Route::About => rsx!(h1 { "About" }), + Route::NotFound => rsx!(h1 { "NotFound" }), + }} + nav { + Link { to: Route::Home, href: |_| "/".to_string() } + Link { to: Route::About, href: |_| "/about".to_string() } + } + } + }) +}; diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 3aee5a684..54411239e 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -1,24 +1,29 @@ mod utils; -use std::{cell::RefCell, collections::HashMap, rc::Rc}; +use std::{cell::RefCell, rc::Rc}; use dioxus_core as dioxus; use dioxus_core::prelude::*; -use dioxus_core_macro::{format_args_f, rsx, Props}; +use dioxus_core_macro::{rsx, Props}; use dioxus_html as dioxus_elements; -use wasm_bindgen::JsValue; -use web_sys::Event; +use wasm_bindgen::{JsCast, JsValue}; +use web_sys::{window, Event}; -use crate::utils::fetch_base_url; +use crate::utils::strip_slash_suffix; + +pub trait Routable: 'static + Send + Clone + PartialEq {} +impl Routable for T where T: 'static + Send + Clone + PartialEq {} pub struct RouterService { - history: RefCell>, + historic_routes: Vec, + history_service: RefCell, base_ur: RefCell>, } impl RouterService { fn push_route(&self, r: R) { - self.history.borrow_mut().push(r); + todo!() + // self.historic_routes.borrow_mut().push(r); } fn get_current_route(&self) -> &str { @@ -61,38 +66,71 @@ impl RouterService { /// This hould only be used once per app /// /// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine -pub fn use_router(cx: Context, cfg: impl FnOnce(&str) -> R) -> Option<&R> { +pub fn use_router(cx: Context, mut parse: impl FnMut(&str) -> R + 'static) -> &R { // for the web, attach to the history api cx.use_hook( |f| { // use gloo::events::EventListener; - let base_url = fetch_base_url(); + let base = window() + .unwrap() + .document() + .unwrap() + .query_selector("base[href]") + .ok() + .flatten() + .and_then(|base| { + let base = JsCast::unchecked_into::(base).href(); + let url = web_sys::Url::new(&base).unwrap(); + + if url.pathname() != "/" { + Some(strip_slash_suffix(&base).to_string()) + } else { + None + } + }); + + let location = window().unwrap().location(); + let pathname = location.pathname().unwrap(); + let initial_route = parse(&pathname); let service: RouterService = RouterService { - history: RefCell::new(vec![]), - base_ur: RefCell::new(base_url), + historic_routes: vec![initial_route], + history_service: RefCell::new( + web_sys::window().unwrap().history().expect("no history"), + ), + base_ur: RefCell::new(base), }; - cx.provide_state(service); + // let base = base_url(); + // let url = route.to_path(); + // pending_routes: RefCell::new(vec![]), + // service.history_service.push_state(data, title); + + // cx.provide_state(service); let regenerate = cx.schedule_update(); - // when "back" is called by the user, we want to to re-render the component + // // when "back" is called by the user, we want to to re-render the component let listener = EventListener::new(&web_sys::window().unwrap(), "popstate", move |_| { // regenerate(); }); - }, - |f| { - // - }, - ); - todo!() - // let router = use_router_service::(cx)?; - // Some(cfg(router.get_current_route())) + service + }, + |state| { + let base = state.base_ur.borrow(); + if let Some(base) = base.as_ref() { + // + let path = format!("{}{}", base, state.get_current_route()); + } + let history = state.history_service.borrow(); + + state.historic_routes.last().unwrap() + }, + ) } pub fn use_router_service(cx: Context) -> Option<&Rc>> { @@ -102,33 +140,33 @@ pub fn use_router_service(cx: Context) -> Option<&Rc { to: R, + + /// The url that gets pushed to the history stack + /// + /// You can either put it your own inline method or just autoderive the route using `derive(Routable)` + /// + /// ```rust + /// + /// Link { to: Route::Home, href: |_| "home".to_string() } + /// + /// // or + /// + /// Link { to: Route::Home, href: Route::as_url } + /// + /// ``` + href: fn(&R) -> String, + + #[builder(default)] children: Element, } -pub fn Link<'a, R: Routable>(cx: Context, props: &LinkProps) -> Element { +pub fn Link(cx: Context, props: &LinkProps) -> Element { let service = use_router_service::(cx)?; cx.render(rsx! { a { - href: format_args!("{}", props.to.to_path()), + href: format_args!("{}", (props.href)(&props.to)), onclick: move |_| service.push_route(props.to.clone()), {&props.children}, } }) } - -pub trait Routable: Sized + Clone + 'static { - /// Converts path to an instance of the routes enum. - fn from_path(path: &str, params: &HashMap<&str, &str>) -> Option; - - /// Converts the route to a string that can passed to the history API. - fn to_path(&self) -> String; - - /// Lists all the available routes - fn routes() -> Vec<&'static str>; - - /// The route to redirect to on 404 - fn not_found_route() -> Option; - - /// Match a route based on the path - fn recognize(pathname: &str) -> Option; -} diff --git a/packages/router/src/utils.rs b/packages/router/src/utils.rs index 46764ebab..a8e9a2c35 100644 --- a/packages/router/src/utils.rs +++ b/packages/router/src/utils.rs @@ -1,31 +1,6 @@ use wasm_bindgen::JsCast; use web_sys::window; -pub fn fetch_base_url() -> Option { - match window() - .unwrap() - .document() - .unwrap() - .query_selector("base[href]") - { - Ok(Some(base)) => { - let base = JsCast::unchecked_into::(base).href(); - - let url = web_sys::Url::new(&base).unwrap(); - let base = url.pathname(); - - let base = if base != "/" { - strip_slash_suffix(&base) - } else { - return None; - }; - - Some(base.to_string()) - } - _ => None, - } -} - pub(crate) fn strip_slash_suffix(path: &str) -> &str { path.strip_suffix('/').unwrap_or(path) } diff --git a/packages/ssr/README.md b/packages/ssr/README.md index 7efb5ca92..4d47b426d 100644 --- a/packages/ssr/README.md +++ b/packages/ssr/README.md @@ -5,7 +5,7 @@ Render a Dioxus VirtualDOM to a string. ```rust // Our app: -const App: FC<()> = |(cx, props)| rsx!(cx, div {"hello world!"}); +const App: FC<()> = |cx, props| rsx!(cx, div {"hello world!"}); // Build the virtualdom from our app let mut vdom = VirtualDOM::new(App); diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 4fc696c1a..84215517c 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -110,7 +110,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option { /// /// ## Example /// ```ignore -/// static App: FC<()> = |(cx, props)|cx.render(rsx!(div { "hello world" })); +/// static App: FC<()> = |cx, props|cx.render(rsx!(div { "hello world" })); /// let mut vdom = VirtualDom::new(App); /// vdom.rebuild(); /// @@ -149,7 +149,7 @@ impl<'a> TextRenderer<'a, '_> { } write!(f, "{}", text.text)? } - VNode::Anchor(_anchor) => { + VNode::Placeholder(_anchor) => { // if self.cfg.indent { for _ in 0..il { @@ -158,7 +158,7 @@ impl<'a> TextRenderer<'a, '_> { } write!(f, "")?; } - VNode::Linked(link) => { + VNode::Portal(link) => { todo!(); } VNode::Element(el) => { @@ -252,9 +252,6 @@ impl<'a> TextRenderer<'a, '_> { } else { } } - VNode::Suspended { .. } => { - // we can't do anything with suspended nodes - } } Ok(()) } diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 05c8ce4e3..a45bd7e31 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -11,10 +11,17 @@ license = "MIT/Apache-2.0" dioxus-core = { path = "../core", version = "0.1.2" } dioxus-html = { path = "../html" } js-sys = "0.3" +# wasm-bindgen-shared = { path = "../../../Tinkering/wasm-bindgen/crates/shared" } +# wasm-bindgen-macro-support = { path = "../../../Tinkering/wasm-bindgen/crates/macro-support" } +# wasm-bindgen = { features = [ + + wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } +# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } +# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } lazy_static = "1.4.0" wasm-bindgen-futures = "0.4.20" -log = "0.4.14" +log = { version = "0.4.14", features = ["release_max_level_off"] } fxhash = "0.2.1" wasm-logger = "0.2.0" console_error_panic_hook = "0.1.6" @@ -24,6 +31,7 @@ async-channel = "1.6.1" anyhow = "1.0" gloo-timers = { version = "0.2.1", features = ["futures"] } futures-util = "0.3.15" +smallstr = "0.2.0" [dependencies.web-sys] version = "0.3.51" @@ -65,6 +73,7 @@ features = [ "IdleDeadline", ] + [lib] crate-type = ["cdylib", "rlib"] @@ -76,7 +85,12 @@ serde = { version = "1.0.126", features = ["derive"] } reqwest = { version = "0.11", features = ["json"] } dioxus-hooks = { path = "../hooks" } dioxus-core-macro = { path = "../core-macro" } -# rand = { version="0.8.4", features=["small_rng"] } +rand = { version = "0.8.4", features = ["small_rng"] } + +[dev-dependencies.getrandom] +version = "0.2" +features = ["js"] + # surf = { version = "2.3.1", default-features = false, features = [ # "wasm-client", # ] } diff --git a/packages/web/examples/js_bench.rs b/packages/web/examples/js_bench.rs new file mode 100644 index 000000000..9ce90d841 --- /dev/null +++ b/packages/web/examples/js_bench.rs @@ -0,0 +1,243 @@ +use std::cell::Cell; + +use dioxus::prelude::*; +use dioxus_core as dioxus; +use dioxus_core_macro::*; +use dioxus_hooks::{use_ref, use_state}; +use dioxus_html as dioxus_elements; +use dioxus_web; +use gloo_timers::future::TimeoutFuture; +use rand::prelude::*; + +fn main() { + console_error_panic_hook::set_once(); + if cfg!(debug_assertions) { + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + log::debug!("hello world"); + } + + for a in ADJECTIVES { + wasm_bindgen::intern(*a); + } + for a in COLOURS { + wasm_bindgen::intern(*a); + } + for a in NOUNS { + wasm_bindgen::intern(*a); + } + for a in [ + "container", + "jumbotron", + "row", + "Dioxus", + "col-md-6", + "col-md-1", + "Create 1,000 rows", + "run", + "Create 10,000 rows", + "runlots", + "Append 1,000 rows", + "add", + "Update every 10th row", + "update", + "Clear", + "clear", + "Swap rows", + "swaprows", + "preloadicon glyphicon glyphicon-remove", // + "aria-hidden", + "onclick", + "true", + "false", + "danger", + "type", + "id", + "class", + "glyphicon glyphicon-remove remove", + "dioxus-id", + "dioxus-event-click", + "dioxus", + "click", + "1.10", + "lbl", + "remove", + "dioxus-event", + "col-sm-6 smallpad", + "btn btn-primary btn-block", + "", + " ", + ] { + wasm_bindgen::intern(a); + } + for x in 0..100_000 { + wasm_bindgen::intern(&x.to_string()); + } + + dioxus_web::launch(App, |c| c.rootname("main")); +} + +#[derive(Clone, PartialEq, Copy)] +struct Label { + key: usize, + labels: [&'static str; 3], +} + +static mut Counter: Cell = Cell::new(1); + +impl Label { + fn new_list(num: usize) -> Vec { + let mut rng = SmallRng::from_entropy(); + let mut labels = Vec::with_capacity(num); + + let offset = unsafe { Counter.get() }; + unsafe { Counter.set(offset + num) }; + + for k in offset..(offset + num) { + labels.push(Label { + key: k, + labels: [ + ADJECTIVES.choose(&mut rng).unwrap(), + COLOURS.choose(&mut rng).unwrap(), + NOUNS.choose(&mut rng).unwrap(), + ], + }); + } + + labels + } +} + +static App: FC<()> = |cx, _props| { + let mut items = use_ref(cx, || vec![]); + let mut selected = use_state(cx, || None); + + cx.render(rsx! { + div { class: "container" + div { class: "jumbotron" + div { class: "row" + div { class: "col-md-6", h1 { "Dioxus" } } + div { class: "col-md-6" + div { class: "row" + ActionButton { name: "Create 1,000 rows", id: "run", + onclick: move || items.set(Label::new_list(1_000)), + } + ActionButton { name: "Create 10,000 rows", id: "runlots", + onclick: move || items.set(Label::new_list(10_000)), + } + ActionButton { name: "Append 1,000 rows", id: "add", + onclick: move || items.write().extend(Label::new_list(1_000)), + } + ActionButton { name: "Update every 10th row", id: "update", + onclick: move || items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"), + } + ActionButton { name: "Clear", id: "clear", + onclick: move || items.write().clear(), + } + ActionButton { name: "Swap Rows", id: "swaprows", + onclick: move || items.write().swap(0, 998), + } + } + } + } + } + table { class: "table table-hover table-striped test-data" + tbody { id: "tbody" + {items.read().iter().enumerate().map(|(id, item)| { + let [adj, col, noun] = item.labels; + let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""}; + rsx!(tr { + class: "{is_in_danger}", + key: "{id}", + td { class:"col-md-1" } + td { class:"col-md-1", "{item.key}" } + td { class:"col-md-1", onclick: move |_| selected.set(Some(id)), + a { class: "lbl", "{adj} {col} {noun}" } + } + td { class: "col-md-1" + a { class: "remove", onclick: move |_| { items.write().remove(id); }, + span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } + } + } + td { class: "col-md-6" } + }) + })} + } + } + span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" } + } + }) +}; + +#[derive(Props)] +struct ActionButtonProps<'a> { + name: &'static str, + id: &'static str, + onclick: &'a dyn Fn(), +} + +fn ActionButton(cx: Context, props: &ActionButtonProps) -> Element { + rsx!(cx, div { class: "col-sm-6 smallpad" + button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: move |_| (props.onclick)(), + "{props.name}" + } + }) +} + +static ADJECTIVES: &[&str] = &[ + "pretty", + "large", + "big", + "small", + "tall", + "short", + "long", + "handsome", + "plain", + "quaint", + "clean", + "elegant", + "easy", + "angry", + "crazy", + "helpful", + "mushy", + "odd", + "unsightly", + "adorable", + "important", + "inexpensive", + "cheap", + "expensive", + "fancy", +]; + +static COLOURS: &[&str] = &[ + "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", + "orange", +]; + +static NOUNS: &[&str] = &[ + "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", + "pizza", "mouse", "keyboard", +]; + +// #[derive(PartialEq, Props)] +// struct RowProps<'a> { +// row_id: usize, +// label: &'a Label, +// } + +// fn Row(cx: Context, props: &RowProps) -> Element { +// rsx!(cx, tr { +// td { class:"col-md-1", "{props.row_id}" } +// td { class:"col-md-1", onclick: move |_| { /* run onselect */ } +// a { class: "lbl", {props.label.labels} } +// } +// td { class: "col-md-1" +// a { class: "remove", onclick: move |_| {/* remove */} +// span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } +// } +// } +// td { class: "col-md-6" } +// }) +// } diff --git a/packages/web/examples/simple.rs b/packages/web/examples/simple.rs new file mode 100644 index 000000000..f6a397e00 --- /dev/null +++ b/packages/web/examples/simple.rs @@ -0,0 +1,46 @@ +//! Example: README.md showcase +//! +//! The example from the README.md. + +use dioxus::prelude::*; +use dioxus_core as dioxus; +use dioxus_core_macro::*; +use dioxus_hooks::use_state; +use dioxus_html as dioxus_elements; +use dioxus_web; +use gloo_timers::future::TimeoutFuture; + +fn main() { + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + dioxus_web::launch(App, |c| c); +} + +static App: FC<()> = |cx, props| { + let show = use_state(cx, || true); + + let inner = match *show { + true => { + rsx!( div { + "hello world" + }) + } + false => { + rsx!( div { + // h1 { + "bello world" + // } + }) + } + }; + + rsx!(cx, div { + button { + "toggle" + onclick: move |_| { + let cur = *show; + show.set(!cur); + } + } + {inner} + }) +}; diff --git a/packages/web/src/cache.rs b/packages/web/src/cache.rs index 1cdba7958..61339f918 100644 --- a/packages/web/src/cache.rs +++ b/packages/web/src/cache.rs @@ -7,6 +7,11 @@ /// Eventually we might want to procedurally generate these strings for common words, phrases, and values. pub(crate) fn intern_cached_strings() { let cached_words = [ + // Important tags to dioxus + "dioxus-id", + "dioxus", + "dioxus-event-click", // todo: more events + "click", // All the HTML Tags "a", "abbr", diff --git a/packages/web/src/cfg.rs b/packages/web/src/cfg.rs index 287e81850..09c488022 100644 --- a/packages/web/src/cfg.rs +++ b/packages/web/src/cfg.rs @@ -17,7 +17,7 @@ impl Default for WebConfig { fn default() -> Self { Self { hydrate: false, - rootname: "dioxusroot".to_string(), + rootname: "main".to_string(), } } } diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 630cd2b36..c92dd3400 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -213,7 +213,7 @@ impl WebsysDom { fn create_placeholder(&mut self, id: u64) { self.create_element("pre", None, id); - // self.set_attribute("hidden", "", None); + self.set_attribute("hidden", "", None, id); } fn create_text_node(&mut self, text: &str, id: u64) { @@ -246,8 +246,15 @@ impl WebsysDom { .unwrap(), }; + use smallstr; + use smallstr::SmallString; + use std::fmt::Write; + + let mut s: SmallString<[u8; 8]> = smallstr::SmallString::new(); + write!(s, "{}", id).unwrap(); + let el2 = el.dyn_ref::().unwrap(); - el2.set_attribute("dioxus-id", &format!("{}", id)).unwrap(); + el2.set_attribute("dioxus-id", s.as_str()).unwrap(); self.stack.push(el.clone()); self.nodes[(id as usize)] = Some(el); @@ -263,18 +270,21 @@ impl WebsysDom { let el = self.stack.top(); - let el = el - .dyn_ref::() - .expect(&format!("not an element: {:?}", el)); + let el = el.dyn_ref::().unwrap(); + // let el = el.dyn_ref::().unwrap(); + // .expect(&format!("not an element: {:?}", el)); // let scope_id = scope.data().as_ffi(); - let scope_id = scope.0 as u64; + // let scope_id = scope.0 as u64; + // "dioxus-event-click", + // "1.10" + // &format!("", scope_id, real_id), + // &format!("dioxus-event-{}", event), + // &format!("{}.{}", scope_id, real_id), + // &format!("dioxus-event-{}", event), + // &format!("{}.{}", scope_id, real_id), - el.set_attribute( - &format!("dioxus-event-{}", event), - &format!("{}.{}", scope_id, real_id), - ) - .unwrap(); + el.set_attribute("dioxus-event", event).unwrap(); // el.set_attribute(&format!("dioxus-event"), &format!("{}", event)) // .unwrap(); @@ -488,6 +498,8 @@ unsafe impl Sync for DioxusWebsysEvent {} fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { use dioxus_html::on::*; use dioxus_html::KeyCode; + // event.prevent_default(); + // use dioxus_core::events::on::*; match event.type_().as_str() { "copy" | "cut" | "paste" => Arc::new(ClipboardEvent {}), @@ -682,30 +694,40 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result { use anyhow::Context; + let element_id = target + .get_attribute("dioxus-id") + .context("Could not find element id on event target")? + .parse()?; + // The error handling here is not very descriptive and needs to be replaced with a zero-cost error system let val: String = target - .get_attribute(&format!("dioxus-event-{}", typ)) + .get_attribute("dioxus-event") .context(format!("wrong format - received {:#?}", typ))?; + // .get_attribute(&format!("dioxus-event-{}", typ)) + // .context(format!("wrong format - received {:#?}", typ))?; let mut fields = val.splitn(3, "."); - let gi_id = fields - .next() - .and_then(|f| f.parse::().ok()) - .context("failed to parse gi id")?; + // let gi_id = fields + // .next() + // .and_then(|f| f.parse::().ok()) + // .context("failed to parse gi id")?; - let real_id = fields - .next() - .and_then(|raw_id| raw_id.parse::().ok()) - .context("failed to parse real id")?; + // let real_id = fields + // .next() + // .and_then(|raw_id| raw_id.parse::().ok()) + // .context("failed to parse real id")?; - let triggered_scope = gi_id; + // let triggered_scope = gi_id; Ok(UserEvent { name: event_name_from_typ(&typ), data: virtual_event_from_websys_event(event.clone()), - element: Some(ElementId(real_id as usize)), - scope_id: Some(ScopeId(triggered_scope as usize)), + element: Some(ElementId(element_id)), + scope_id: None, + // scope_id: Some(ScopeId(triggered_scope as usize)), + // element: Some(ElementId(real_id as usize)), + // scope_id: Some(ScopeId(triggered_scope as usize)), priority: dioxus_core::EventPriority::Medium, }) } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 4e5f5fefc..d9e14b6c2 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -85,7 +85,7 @@ mod ric_raf; /// dioxus_web::launch(App, |c| c); /// } /// -/// static App: FC<()> = |(cx, props)| { +/// static App: FC<()> = |cx, props| { /// rsx!(cx, div {"hello world"}) /// } /// ``` @@ -109,7 +109,7 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W /// name: String /// } /// -/// static App: FC = |(cx, props)| { +/// static App: FC = |cx, props| { /// rsx!(cx, div {"hello {props.name}"}) /// } /// ``` @@ -155,7 +155,7 @@ pub async fn run_with_props(root: FC, root_props: T, cfg: // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory if !should_hydrate { - log::info!("Applying rebuild edits..., {:?}", mutations); + // log::info!("Applying rebuild edits..., {:?}", mutations); websys_dom.process_edits(&mut mutations.edits); } @@ -166,17 +166,20 @@ pub async fn run_with_props(root: FC, root_props: T, cfg: // if there is work then this future resolves immediately. dom.wait_for_work().await; - // wait for the mainthread to schedule us in - let mut deadline = work_loop.wait_for_idle_time().await; + // // wait for the mainthread to schedule us in + // let mut deadline = work_loop.wait_for_idle_time().await; // run the virtualdom work phase until the frame deadline is reached - let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some()); + let mutations = dom.work_with_deadline(|| false); + // // run the virtualdom work phase until the frame deadline is reached + // let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some()); // wait for the animation frame to fire so we can apply our changes work_loop.wait_for_raf().await; for mut edit in mutations { // actually apply our changes during the animation frame + // log::info!("Applying change edits..., {:?}", edit); websys_dom.process_edits(&mut edit.edits); } } diff --git a/src/lib.rs b/src/lib.rs index 93c7d33b1..10a22e685 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,166 +4,124 @@ //! A concurrent, functional, virtual DOM for Rust //!

//!
-//! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust. //! -//! This crate aims to maintain a hook-based, renderer-agnostic framework for cross-platform UI development. +//! # Resources //! -//! ## Overview and Goals -//! Dioxus' ultimate goal is to save you from writing new code when bringing your application to new platforms. We forsee -//! a future where WebApps, Mobile Apps, Desktop Apps, and even AR apps can be written in the same language, ecosystem, -//! and leverage the same platform-agnostic libraries. +//! This overview is provides a brief introduction to Dioxus. For a more in-depth guide, make sure to check out: +//! - [Getting Started](https://dioxuslabs.com/getting-started) +//! - [Book](https://dioxuslabs.com/book) +//! - [Reference](https://dioxuslabs.com/refernce-guide) + //! -//! In this aim we chose to use a variety of techniques: -//! - We use a VirtualDOM to abstract the true renderer from application logic. -//! - We use functions as components to limit the API churn for greater stability. -//! - We use hooks as state to allow reusable logic across the whole ecosystem. -//! - We support an extensible and compile-time safe DSL for building interfaces. +//! # Overview and Goals //! -//! Our guiding stars (in order of priority): -//! - Ergonomics -//! - Reusability -//! - Speed and memory efficiency -//! - Safety +//! Dioxus makes it easy to quickly build complex user interfaces with Rust. Any Dioxus app can run in the web browser, +//! as a desktop app, as a mobile app, or anywhere else provided you build the right renderer. //! -//! ## Components -//! The base unit of Dioxus is the `component`. Components can be easily created from just a function - no traits or -//! proc macros required: +//! Dioxus is heavily inspired by React, supporting many of the same concepts: //! -//! ``` +//! - Hooks for state +//! - VirtualDom & diffing +//! - Concurrency & asynchronous rendering +//! - JSX-like templating syntax +//! +//! If you know React, then you know Dioxus. +//! +//! Dioxus is *substantially* faster than many of the other Rust UI libraries (Yew/Percy) and is *significantly* faster +//! than React, competitve with InfernoJS and frameworks like Svelte/SolidJS. +//! +//! ## Brief Overview +//! +//! All Dioxus apps are built by composing functions that take in a `Scope` and `Properties` and return an `Element`. A `Scope` holds +//! relevant state data for the the currently-rendered component. +//! +//! ```rust //! use dioxus::prelude::*; //! -//! fn Example(cx: Context<()>) -> DomTree { -//! html! {
"Hello, world!"
} +//! fn main() { +//! dioxus::desktop::launch(App); //! } -//! ``` -//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used -//! and what properties can be used to specify it in the VNode output. Components without properties may be generic over -//! `()`, and components with properties must declare their properties as a struct: //! -//! ``` -//! #[derive(Props)] -//! struct Props { name: String } +//! fn App(cx: Scope, props: &()) -> Element { +//! let mut count = use_state(cx, || 0); //! -//! fn Example(cx: Context) -> DomTree { -//! html! {
"Hello {cx.props.name}!"
} -//! } -//! ``` -//! -//! Props that are valid for the `'pub static` lifetime automatically get memoized by Diouxs. This means the component won't -//! re-render if its Props didn't change. However, Props that borrow data from their parent cannot be safely memoized, and -//! will always re-render if their parent changes. To borrow data from a parent, your component needs to add explicit lifetimes, -//! otherwise Rust will get confused about whether data is borrowed from either Props or Context. Since Dioxus manages -//! these lifetimes internally, Context and your Props must share the same lifetime: -//! -//! ``` -//! #[derive(Props)] -//! struct Props<'a> { name: &'a str } -//! -//! fn Example<'a>(cx: Context<'a, Props<'a>>) -> DomTree { -//! html! {
"Hello {cx.props.name}!"
} -//! } -//! ``` -//! -//! -//! -//! The lifetimes might look a little messy, but are crucially important for Dioxus's efficiency and overall ergonimics. -//! Components can also be crafted as pub static closures, enabling type inference without all the type signature noise. However, -//! closure-style components cannot work with borrowed data due to limitations in Rust's lifetime system. -//! -//! To use custom properties for components, you'll need to derive the `Props` trait for your properties. This trait -//! exposes a compile-time correct builder pattern (similar to typed-builder) that can be used in the `rsx!` and `html!` -//! macros to build components. Component props may have default fields notated by the `Default` attribute: -//! -//! ``` -//! #[derive(Props)] -//! struct Props { -//! name: String -//! -//! #[props(default = false)] -//! checked: bool, -//! -//! #[props(default, setter(strip_option, into))] -//! title: Option -//! } -//! ``` -//! -//! These flags roughly follow that of typed-builder, though tweaked to support the `Props` usecase. -//! -//! ## Hooks and State -//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component. -//! -//! ``` -//! pub pub static Example: FC<()> = |(cx, props)|{ -//! let (val, set_val) = use_state(cx, || 0); //! cx.render(rsx!( -//! button { onclick: move |_| set_val(val + 1) } +//! div { "Count: {count}" } +//! button { onclick: move |_| count += 1, "Increment" } +//! button { onclick: move |_| count -= 1, "Decrement" } //! )) //! } -//! ```` -//! -//! Instead of using a single struct to represent a component and its state, hooks use the "use_hook" building block -//! which allows the persistence of data between function component renders. This primitive is exposed directly through -//! the `Context` item: //! ``` -//! fn my_hook<'a>(cx: &impl Scoped<'a>) -> &'a String { -//! cx.use_hook( -//! // Initializer stores a value -//! |hook_idx| String::new("stored_data"), //! -//! // Runner returns the hook value every time the component is rendered -//! |hook| &*hook, +//! ## Components //! -//! // Cleanup runs after the component is unmounted -//! |hook| log::debug!("cleaning up hook with value {:#?}", hook) -//! ) +//! We can compose these function components to build a complex app. Each new component we design must take some Properties. +//! For components with no explicit properties, we can use the `()` type. In Dioxus, all properties are memoized by default! +//! +//! ```rust +//! fn App(cx: Scope, props &()) -> Element { +//! cx.render(rsx!( +//! Header { +//! title: "My App", +//! color: "red", +//! } +//! )) //! } //! ``` -//! Under the hood, hooks store their data in a series of "memory cells". The first render defines the layout of these -//! memory cells, and on each subsequent render, each `use_hook` call accesses its corresponding memory cell. If a hook -//! accesses the wrong memory cell, `use_hook` will panic, and your app will crash. You can always use `try_use_hook` but -//! these types of errors can be easily mitigated by following the rules of hooks: -//! -//! - Don’t call Hooks inside loops, conditions, or nested functions -//! - Don't call hooks in changing order between renders -//! -//! Hooks provide a very powerful way to reuse stateful logic between components, simplify large complex components, -//! and adopt more clear context subscription patterns to make components easier to read. The mechanics of hooks in Dioxus -//! shares a great amount of similarity with React's hooks and there are many guides to hooks in React online. -//! -//! ## Supported Renderers -//! Instead of being tightly coupled to a platform, browser, or toolkit, Dioxus implements a VirtualDOM object which -//! can be consumed to draw the UI. The Dioxus VDOM is reactive and easily consumable by 3rd-party renderers via -//! the `RealDom` trait. See [Implementing a Renderer](docs/8-custom-renderer.md), the `StringRenderer`, and `WebSys` render implementations for a template -//! on how to implement your own custom renderer. We provide 1st-class support for these renderers: -//! -//! - dioxus-desktop (via WebView) -//! - dioxus-web (via WebSys) -//! - dioxus-ssr (via StringRenderer) -//! - dioxus-liveview (SSR + WebSys) -//! -//! In the main `dioxus` crate, these are all accessible through configuration flags. -//! -//! ## Rendering to the Web -//! -//! Most dioxus apps will be initialized in roughly the same way. The `launch` method in `web` will immediately start a -//! VirtualDOM and await it using `wasm_bindgen_futures`. -//! -//! An example app that starts a websys app and internally awaits works as follows: //! +//! Our `Header` component takes in a `title` and a `color` property, which we delcare on an explicit `HeaderProps` struct. //! ``` -//! use dioxus::prelude::*; -//! fn main() { -//! dioxus::web::launch(Example); +//! // The `Props` derive macro lets us add additional functionality to how props are interpreted. +//! #[derive(Props, PartialEq)] +//! struct HeaderProps { +//! title: String, +//! color: String, //! } //! -//! pub pub static Example: FC<()> = |(cx, props)|{ -//! cx.render(rsx! { -//! div { "Hello World!" } -//! }) -//! }; +//! fn Header(cx: Scope, props: &HeaderProps) -> Element { +//! cx.render(rsx!( +//! div { +//! background_color: "{props.color}" +//! h1 { "{props.title}" } +//! } +//! )) +//! } //! ``` //! -//! In reality, you'll want to integrate analytics, logging, crash-protection and more. +//! ## Hooks +//! +//! While components are reusable forms of UI elements, hooks are reusable forms of logic. The details of hooks are +//! somewhat complicated. In essence, hooks let us save state between renders of our components and reuse the accompanying +//! logic across different apps. +//! +//! Hooks are simply composition of other hooks. To create our first hook we can create a simple function that takes in +//! an Scope. We can then call `use_hook` on the `Scope` to get a mutable reference to the stored value. +//! +//! ```rust +//! fn use_say_hello(cx: Scope) -> &mut String { +//! cx.use_hook(|_| "Hello".to_string(), |hook| hook) +//! } +//! ``` +//! +//! If you want to extend Dioxus with some new functionality, you'll probably want to implement a new hook. +//! +//! +//! ## Features +//! +//! This overview doesn't cover everything. Make sure to check out the tutorial and reference guide on the official +//! website for more details. +//! +//! Beyond this overview, Dioxus supports: +//! - Server-side rendering +//! - Concurrent rendering (with async support) +//! - Web/Desktop/Mobile support +//! - Pre-rendering and rehydration +//! - Fragments, Portals, and Suspense +//! - Inline-styles +//! - Custom event handlers +//! - Custom elements +//! - Basic fine-grained reactivity (IE SolidJS/Svelte) +//! - and more! // Just a heads-up, the core functionality of dioxus rests in Dioxus-Core. This crate just wraps a bunch of utilities // together and exports their namespaces to something predicatble. @@ -188,10 +146,12 @@ pub use dioxus_desktop as desktop; #[cfg(feature = "router")] pub use dioxus_router as router; -pub mod debug {} +pub mod events { + #[cfg(feature = "html")] + pub use dioxus_html::{on::*, KeyCode}; +} pub mod prelude { - //! A glob import that includes helper types like FC, rsx!, html!, and required traits pub use dioxus_core::prelude::*; pub use dioxus_core_macro::{format_args_f, rsx, Props, Routable}; pub use dioxus_elements::{GlobalAttributes, SvgAttributes};