diff --git a/Cargo.toml b/Cargo.toml index bffbfc9e6..297659d8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,34 +12,28 @@ dioxus-core = { path = "./packages/core" } dioxus-core-macro = { path = "./packages/core-macro", optional = true } dioxus-html = { path = "./packages/html", optional = true } dioxus-web = { path = "./packages/web", optional = true } -dioxus-webview = { path = "./packages/webview", optional = true } +dioxus-desktop = { path = "./packages/desktop", optional = true } dioxus-hooks = { path = "./packages/hooks", optional = true } dioxus-ssr = { path = "./packages/ssr", optional = true } - +dioxus-mobile = { path = "./packages/mobile", optional = true } [features] -default = [ - "core", - "macro", - "hooks", - "ssr", - "html", - "web", - "desktop", - # "atoms", - # "router", -] -atoms = [] +# core +default = ["core"] +core = ["macro", "hooks", "html"] macro = ["dioxus-core-macro"] -ssr = ["dioxus-ssr"] hooks = ["dioxus-hooks"] -router = [] html = ["dioxus-html"] -web = ["dioxus-web"] -desktop = ["dioxus-webview"] -# Core just includes dioxus-core (the only required package) -core = [] +# utilities +atoms = [] +router = [] + +# targets +ssr = ["dioxus-ssr"] +web = ["dioxus-web"] +desktop = ["dioxus-desktop"] +mobile = ["dioxus-mobile"] [dev-dependencies] @@ -64,7 +58,6 @@ members = [ "packages/hooks", "packages/web", "packages/ssr", - "packages/webview", - # "packages/atoms", - # "packages/docsite", + "packages/desktop", + # "packages/mobile", ] diff --git a/packages/atoms/Cargo.toml b/packages/atoms/Cargo.toml deleted file mode 100644 index c6e0e0bd2..000000000 --- a/packages/atoms/Cargo.toml +++ /dev/null @@ -1,23 +0,0 @@ -[package] -name = "atoms" -version = "0.0.0" -authors = ["Jonathan Kelley "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -anyhow = "1.0.40" -dioxus-core = { path="../core" } -thiserror = "1.0.24" -log = "0.4.14" -im-rc = "15.0.0" -futures = "0.3.15" - -[dev-dependencies] -uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] } -dioxus-web = { path="../web" } -wasm-bindgen-futures = "*" - -wasm-logger = "0.2.0" -console_error_panic_hook = "0.1.6" diff --git a/packages/atoms/README.md b/packages/atoms/README.md deleted file mode 100644 index be5a5a1f6..000000000 --- a/packages/atoms/README.md +++ /dev/null @@ -1,164 +0,0 @@ -
-

⚛ Dirac

-

- Global state for Dioxus -

-
- -Dirac is a global state management toolkit for Dioxus, built on the concept of composable atoms of state: - -```rust -const COUNT: Atom = |_| 0; - -const Incr: FC<()> = |cx| { - let (count, set_count) = dirac::use_read_write(&cx, &COUNT); - rsx!(in cx, button { onclick: move |_| set_count(count + 1), "+" } ) -} - -const Decr: FC<()> = |cx| { - let (count, set_count) = dirac::use_read_write(&cx, &COUNT); - rsx!(in cx, button { onclick: move |_| set_count(count - 1), "-" } ) -} - -const App: FC<()> = |cx| { - let count = dirac::use_read(&cx, &COUNT); - cx.render(rsx!{ - "Count is {count}" - Incr {} - Decr {} - }) -} -``` - -Dirac provides a global state management API for Dioxus apps built on the concept of "atomic state." Instead of grouping state together into a single bundle like Redux, Dyon provides individual building blocks of state called atoms. These atoms can be set/get anywhere in the app and combined to craft complex state. Dyon should be easier to learn, more efficient than Redux, and allow you to build complex Dioxus apps with relative ease. - -## Support - -Dyon is officially supported by the Dioxus team. By doing so, we are "planting our flag in the sand" for atomic state management instead of bundled (Redux-style) state management. Atomic state management fits well with the internals of Dioxus and idiomatic Rust, meaning Dirac state management will be faster, more efficient, and less sensitive to data races than Redux-style apps. - -Internally, Dioxus uses batching to speed up linear-style operations. Dirac integrates with this batching optimization, making app-wide state changes extremely fast. This way, Dirac can be pushed significantly harder than Redux without the need to enable/disable debug flags to prevent performance slowdowns. - -# Guide - -## Atoms - -A simple atom of state is defined globally as a const: - -```rust -const LightColor: Atom<&str> = |_| "Green"; -``` - -This atom of state is initialized with a value of `"Green"`. The atom that is returned does not actually contain any values. Instead, the atom's key - which is automatically generated in this instance - is used in the context of a Dioxus App. - -This is then later used in components like so: - -```rust -fn App(cx: Context<()>) -> VNode { - // The Atom root must be initialized at the top of the application before any use_Atom hooks - dirac::intialize_root(&cx, |_| {}); - - let color = dirac::use_read(&cx, &LightColor); - - cx.render(rsx!( h1 {"Color of light: {color}"} )) -} -``` - -Atoms are considered "Writable" objects since any consumer may also set the Atom's value with their own: - -```rust -fn App(cx: Context<()>) -> VNode { - let color = dirac::use_read(&cx, &LightColor); - let set_color = dirac::use_write(&cx, &LightColor); - cx.render(rsx!( - h1 { "Color: {color}" } - button { onclick: move |_| set_color("red"), "red" } - button { onclick: move |_| set_color("yellow"), "yellow" } - button { onclick: move |_| set_color("green"), "green" } - )) -} -``` - -"Reading" a value with use_read subscribes that component to any changes to that value while "Writing" a value does not. It's a good idea to use `write-only` whenever it makes sense to prevent unnecessary evaluations. Both `read` and `write` may be combined together to provide a `use_state` style hook. - -```rust -fn App(cx: Context<()>) -> VNode { - let (color, set_color) = dirac::use_read_write(&cx, &Light); - cx.render(rsx!( - h1 { "Color: {color}" } - button { onclick: move |_| set_color("red"), "red" } - )) -} -``` - -## Selectors - -Selectors are a concept popular in the JS world as a way of narrowing down a selection of state outside the VDOM lifecycle. Selectors have two functions: 1 summarize/narrow down some complex state and 2) memoize calculations. - -Selectors are only `readable` - they cannot be set. This differs from RecoilJS where selectors _are_ `writable`. Selectors, as you might've guessed, "select" some data from atoms and other selectors. - -Selectors provide a `SelectorApi` which essentially exposes a read-only `AtomApi`. They have the `get` method which allows any readable valued to be obtained for the purpose of generating a new value. A `Selector` may only return `'static` data, however `SelectorBorrowed` may return borrowed data. - -returning static data: - -```rust -const Light: Atom<&'static str> = |_| "Green"; -const NumChars: Selector = |api| api.get(&Light).len(); -``` - -Selectors will update their selection and notify subscribed components whenever their dependencies also update. The `get` calls in a selector will subscribe that selector to whatever `Readable` is being `get`-ted. Unlike hooks, you may use `get` in conditionals; an internal algorithm decides when a selector needs to be updated based on what it `get`-ted last time it was ran. - -Selectors may also returned borrowed data: - -```rust -const Light: Atom<&'static str> = |_| "Green"; -const ThreeChars: SelectorBorrowed = |api| api.get(&Light).chars().take(3).unwrap(); -``` - -Unfortunately, Rust tries to coerce `T` (the type in the Selector generics) to the `'static` lifetime because we defined it as a static. `SelectorBorrowed` defines a type for a function that returns `&T` and provides the right lifetime for `T`. If you don't like having to use a dedicated `Borrowed` type, regular functions work too: - -```rust -// An atom -fn Light(api: &mut AtomBuilder) -> &'static str { - "Green" -} - -// A selector -fn ThreeChars(api: &mut SelectorApi) -> &'static str { - api.get(&Light).chars().take(3).unwrap() -} -``` - -Returning borrowed data is generally frowned upon, but may be useful when used wisely. - -- If a selected value equals its previous selection (via PartialEq), the old value must be kept around to avoid evaluating subscribed components. -- It's unlikely that a change in a dependency's data will not change the selector's output. - -In general, borrowed selectors introduce a slight memory overhead as they need to retain previous state to safely memoize downstream subscribers. The amount of state kept around scales with the amount of `gets` in a selector - though as the number of dependencies increase, the less likely the selector actually stays memoized. Dirac tries to optimize this behavior the best it can to balance component evaluations with memory overhead. - -## Families - -You might notice that collections will not be performant with just sets/gets. We don't want to clone our entire HashMap every time we want to insert or remove an entry! That's where `Families` come in. Families are memoized collections (HashMap and OrdMap) that wrap the immutable datastructures library `im-rc`. Families are defined by a function that takes the FamilyApi and returns a function that provides a default value given a key. In this example, we insert a value into the collection when initialized, and then return a function that takes a key and returns a default value. - -```rust -const CloudRatings: AtomFamily<&str, i32> = |api| { - api.insert("Oracle", -1); - |key| match key { - "AWS" => 1, - "Azure" => 2, - "GCP" => 3, - _ => 0 - } -} -``` - -Whenever you `select` on a `Family`, the ID of the entry is tracked. Other subscribers will only be updated if they too select the same family with the same key and that value is updated. Otherwise, these subscribers will never re-render on an "insert", "remove", or "update" of the collection. You could easily implement this yourself with Atoms, immutable datastructures, and selectors, but our implementation is more efficient and first-class. - -```rust -fn App(cx: Context<()>) -> VNode { - let (rating, set_rating) = dirac::use_read_write(cx, CloudRatings.select("AWS")); - cx.render(rsx!( - h1 { "AWS rating: {rating}" } - button { onclick: move |_| set_rating((rating + 1) % 5), "incr" } - )) -} -``` diff --git a/packages/atoms/_examples/api_mvc.rs b/packages/atoms/_examples/api_mvc.rs deleted file mode 100644 index c473dd6e5..000000000 --- a/packages/atoms/_examples/api_mvc.rs +++ /dev/null @@ -1,45 +0,0 @@ -//! RecoilAPI Pattern -//! ---------------- -//! This example demonstrates how the use_recoil_callback hook can be used to build view controllers. -//! These view controllers are cheap to make and can be easily shared across the app to provide global -//! state logic to many components. -//! -//! This pattern is meant to replace the typical use_dispatch pattern used in Redux apps. - -use dioxus_core::prelude::*; -use recoil::*; - -const TITLE: Atom = |_| format!("Heading"); -const SUBTITLE: Atom = |_| format!("Subheading"); - -struct TitleController(RecoilApi); -impl TitleController { - fn new(api: RecoilApi) -> Self { - Self(api) - } - fn uppercase(&self) { - self.0.modify(&TITLE, |f| *f = f.to_uppercase()); - self.0.modify(&SUBTITLE, |f| *f = f.to_uppercase()); - } - fn lowercase(&self) { - self.0.modify(&TITLE, |f| *f = f.to_lowercase()); - self.0.modify(&SUBTITLE, |f| *f = f.to_lowercase()); - } -} - -fn main() { - wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|cx| { - let title = use_read(&cx, &TITLE); - let subtitle = use_read(&cx, &SUBTITLE); - let controller = TitleController::new(use_recoil_api(&cx)); - - rsx! { in cx, - div { - "{title}" - "{subtitle}" - button { onclick: move |_| controller.uppercase(), "Uppercase" } - button { onclick: move |_| controller.lowercase(), "Lowercase" } - } - } - })) -} diff --git a/packages/atoms/_examples/callback.rs b/packages/atoms/_examples/callback.rs deleted file mode 100644 index 222e63e4d..000000000 --- a/packages/atoms/_examples/callback.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Example: RecoilCallback -//! ------------------------ -//! This example shows how use_recoil_callback can be used to abstract over sets/gets. -//! This hook provides a way to capture the RecoilApi object. In this case, we capture -//! it in a closure an abstract the set/get functionality behind the update_title function. -//! -//! It should be straightforward to build a complex app with recoil_callback. -use dioxus_core::prelude::*; -use recoil::*; - -const TITLE: Atom<&str> = |_| "red"; - -fn update_title(api: &RecoilApi) { - match *api.get(&TITLE) { - "green" => api.set(&TITLE, "yellow"), - "yellow" => api.set(&TITLE, "red"), - "red" => api.set(&TITLE, "green"), - _ => {} - } -} - -static App: FC<()> = |cx| { - let title = use_read(&cx, &TITLE); - let next_light = use_recoil_api(&cx, |api| move |_| update_title(&api)); - - rsx! { in cx, - div { - "{title}" - button { onclick: {next_light}, "Next light" } - } - } -}; - -fn main() { - wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)) -} diff --git a/packages/atoms/_examples/ecs.rs b/packages/atoms/_examples/ecs.rs deleted file mode 100644 index f0fa9a427..000000000 --- a/packages/atoms/_examples/ecs.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Example: ECS Architecture for very list selectors -//! -------------------------------------------------- -//! Sometimes, you need *peak* performance. Cloning and Rc might simply be too much overhead for your app. -//! If you're building CPU intense apps like graphics editors, simulations, or advanced visualizations, -//! slicing up your state beyond atoms might be desirable. -//! -//! Instead of storing groups of entities in a collection of structs, the ECS Architecture instead stores -//! an array for each field in of a struct. This tends to improve performance for batch operations on -//! individual fields at the cost of complexity. Fortunately, this ECS model is built right into Recoil, -//! making it easier than ever to enable sharded datastructures in your app. -//! -//! Instead of defining a struct for our primary datastructure, we'll instead use a type tuple, and then later -//! index that tuple to get the value we care about. Unfortunately, we lose name information wrt to each -//! type in the type tuple. This can be solved with an associated module, the derive EcsMacro, or just -//! by good documentation. -//! -//! This approach is best suited for applications where individual entries in families are very large -//! and updates to neighbors are costly in terms of Clone or field comparisons for memoization. - -use dioxus::prelude::*; -use dioxus_core as dioxus; -use recoil::*; - -type TodoModel = ( - bool, // checked - String, // name - String, // contents -); - -const TODOS: EcsModel = |builder| {}; -// const SELECT_TITLE: SelectorBorrowed = |s, k| TODOS.field(0).select(k); -// const SELECT_SUBTITLE: SelectorBorrowed = |s, k| TODOS.field(1).select(k); - -static App: FC<()> = |cx| { - use_init_recoil_root(cx, |_| {}); - - // let title = use_recoil_value(cx, &C_SELECTOR); - - let title = ""; - rsx! { in cx, - div { - "{title}" - // button { onclick: {next_light}, "Next light" } - } - } -}; - -fn main() { - wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)) -} diff --git a/packages/atoms/_examples/family.rs b/packages/atoms/_examples/family.rs deleted file mode 100644 index 7ac623ca6..000000000 --- a/packages/atoms/_examples/family.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::{collections::HashMap, rc::Rc}; - -use dioxus_core::prelude::*; -use recoil::*; -use uuid::Uuid; - -const TODOS: AtomHashMap> = |map| {}; - -#[derive(PartialEq)] -struct Todo { - checked: bool, - title: String, - content: String, -} - -static App: FC<()> = |cx| { - use_init_recoil_root(cx, move |cfg| {}); - - let todos = use_read_family(cx, &TODOS); - - // rsx! { in cx, - // div { - // h1 {"Basic Todolist with AtomFamilies in Recoil.rs"} - // ul { { todos.keys().map(|id| rsx! { Child { id: *id } }) } } - // } - // } - cx.render(html! { - - Jade - - }) -}; - -#[derive(Props, PartialEq)] -struct ChildProps { - id: Uuid, -} - -static Child: FC = |cx| { - let (todo, set_todo) = use_read_write(cx, &TODOS.select(&cx.id)); - - rsx! { in cx, - li { - h1 {"{todo.title}"} - input { type: "checkbox", name: "scales", checked: "{todo.checked}" } - label { "{todo.content}", for: "scales" } - p {"{todo.content}"} - } - } -}; - -fn main() { - wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)) -} diff --git a/packages/atoms/_examples/familypoc.rs b/packages/atoms/_examples/familypoc.rs deleted file mode 100644 index cbec169f5..000000000 --- a/packages/atoms/_examples/familypoc.rs +++ /dev/null @@ -1,47 +0,0 @@ -use dioxus_core::prelude::*; -use im_rc::HashMap as ImMap; -use recoil::*; -use uuid::Uuid; - -const TODOS: Atom> = |_| ImMap::new(); - -#[derive(PartialEq)] -struct Todo { - checked: bool, - title: String, - contents: String, -} - -static App: FC<()> = |cx| { - use_init_recoil_root(cx, |_| {}); - let todos = use_read(&cx, &TODOS); - - rsx! { in cx, - div { - "Basic Todolist with AtomFamilies in Recoil.rs" - } - } -}; - -#[derive(Props, PartialEq)] -struct ChildProps { - id: Uuid, -} - -static Child: FC = |cx| { - let todo = use_read(cx, &TODOS).get(&cx.id).unwrap(); - // let (todo, set_todo) = use_read_write(cx, &TODOS); - - rsx! { in cx, - div { - h1 {"{todo.title}"} - input { type: "checkbox", name: "scales", checked: "{todo.checked}" } - label { "{todo.contents}", for: "scales" } - p {"{todo.contents}"} - } - } -}; - -fn main() { - wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)) -} diff --git a/packages/atoms/_examples/hellorecoil.rs b/packages/atoms/_examples/hellorecoil.rs deleted file mode 100644 index d9558fa24..000000000 --- a/packages/atoms/_examples/hellorecoil.rs +++ /dev/null @@ -1,29 +0,0 @@ -use dioxus_core::prelude::*; -use recoil::*; - -const COUNT: Atom = |_| 0; - -static App: FC<()> = |cx| { - use_init_recoil_root(cx, |_| {}); - - let (count, set_count) = use_read_write(&cx, &COUNT); - - rsx! { in cx, - div { - "Count: {count}" - br {} - button { onclick: move |_| set_count(count + 1), "___<" - button { onclick: move |_| set_count(count - 1), "Decr>" } - } - } -}; - -fn main() { - // Setup logging - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); - console_error_panic_hook::set_once(); - - log::debug!("asd"); - wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)); -} diff --git a/packages/atoms/_examples/selectorlist.rs b/packages/atoms/_examples/selectorlist.rs deleted file mode 100644 index cc12bf06c..000000000 --- a/packages/atoms/_examples/selectorlist.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::collections::HashMap; - -use dioxus_core::prelude::*; -use recoil::*; - -const A_ITEMS: AtomHashMap = |_| HashMap::new(); -const B_ITEMS: AtomHashMap = |_| HashMap::new(); - -const C_SELECTOR: SelectorFamily = |api, key| { - let a = api.get(&A_ITEMS.select(&key)); - let b = api.get(&B_ITEMS.select(&key)); - a + b -}; - -const D_SELECTOR: SelectorFamilyBorrowed = |api, key| -> &i32 { - let a = api.get(&A_ITEMS.select(&key)); - a -}; - -static App: FC<()> = |cx| { - use_init_recoil_root(cx, |_| {}); - - let title = use_read(cx, &C_SELECTOR); - - rsx! { in cx, - div { - "{title}" - // button { onclick: {next_light}, "Next light" } - } - } -}; - -fn main() { - wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)) -} diff --git a/packages/atoms/_examples/selectors.rs b/packages/atoms/_examples/selectors.rs deleted file mode 100644 index ab4e85e76..000000000 --- a/packages/atoms/_examples/selectors.rs +++ /dev/null @@ -1,46 +0,0 @@ -use dioxus_core::prelude::*; -use recoil::*; - -const A: Atom = |_| 0; -const B: Atom = |_| 0; -const C: Selector = |api| api.get(&A) + api.get(&B); - -static App: FC<()> = |cx| { - use_init_recoil_root(cx, |_| {}); - rsx! { in cx, - div { - Banner {} - BtnA {} - BtnB {} - } - } -}; - -static Banner: FC<()> = |cx| { - let count = use_read(&cx, &C); - cx.render(rsx! { h1 { "Count: {count}" } }) -}; - -static BtnA: FC<()> = |cx| { - let (a, set) = use_read_write(&cx, &A); - rsx! { in cx, - div { "a" - button { "+", onclick: move |_| set(a + 1) } - button { "-", onclick: move |_| set(a - 1) } - } - } -}; - -static BtnB: FC<()> = |cx| { - let (b, set) = use_read_write(&cx, &B); - rsx! { in cx, - div { "b" - button { "+", onclick: move |_| set(b + 1) } - button { "-", onclick: move |_| set(b - 1) } - } - } -}; - -fn main() { - wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)) -} diff --git a/packages/atoms/_examples/structured_app.rs b/packages/atoms/_examples/structured_app.rs deleted file mode 100644 index afbe4c615..000000000 --- a/packages/atoms/_examples/structured_app.rs +++ /dev/null @@ -1,10 +0,0 @@ -fn main() {} - -use dioxus_core::prelude::*; - -static App: FC<()> = |cx| { - // - use_initialize_app() -}; - -fn use_initialize_app(cx: &impl Scoped) {} diff --git a/packages/atoms/_examples/supense_integration.rs b/packages/atoms/_examples/supense_integration.rs deleted file mode 100644 index f0f523ace..000000000 --- a/packages/atoms/_examples/supense_integration.rs +++ /dev/null @@ -1,50 +0,0 @@ -// const Posts: AtomFamily = |family| { -// family.on_get(|key| { - -// // -// }) -// }; - -fn main() { - wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(App)) -} - -use std::future::Future; - -use dioxus_core::prelude::*; - -static App: FC<()> = |cx| { - // - - let title = use_async_atom(); - let title_card = suspend(&cx, title, move |val| { - // - rsx!(in cx, div { - h3 { "{val}" } - }) - }); - - // let fut = (use_async_atom(), use_async_atom()); - // let title_card2 = cx.suspend(fut, move |(text, text2)| { - // cx.render(rsx!( h3 { "{text}" } )) - // }); - - cx.render(rsx! { - div { - {title_card} - // {title_card2} - } - }) -}; - -async fn use_async_atom() -> String { - todo!() -} - -fn suspend<'a, O>( - c: &impl Scoped<'a>, - f: impl Future, - g: impl FnOnce(O) -> VNode<'a> + 'a, -) -> VNode<'a> { - todo!() -} diff --git a/packages/atoms/_examples/test.html b/packages/atoms/_examples/test.html deleted file mode 100644 index 5a664c9f8..000000000 --- a/packages/atoms/_examples/test.html +++ /dev/null @@ -1 +0,0 @@ -println!("asd") diff --git a/packages/atoms/notes/Architecture.md b/packages/atoms/notes/Architecture.md deleted file mode 100644 index 18b7b9502..000000000 --- a/packages/atoms/notes/Architecture.md +++ /dev/null @@ -1,112 +0,0 @@ -# Architecture - -## ECS - -It's often ideal to represent list-y state as an SoA (struct of arrays) instead of an AoS (array of structs). In 99% of apps, normal clone-y performance is fine. If you need more performance on top of cloning on update, IM.rs will provide fast immutable data structures. - -But, if you need **extreme performance** consider the ECS (SoA) model. With ECS model, we can modify fields of an entry without invalidating selectors on neighboring fields. - -This approach is for that 0.1% of apps that need peak performance. Our philosophy is that these tools should be available when needed, but you shouldn't need to reach for them often. An example use case might be a graphics editor or simulation engine where thousands of entities with many fields are rendered to the screen in realtime. - -Recoil will even help you: - -```rust -type TodoModel = ( - String, // title - String // subtitle -); -const TODOS: RecoilEcs = |builder| { - builder.push("SomeTitle".to_string(), "SomeSubtitle".to_string()); -}; -const SELECT_TITLE: SelectorBorrowed = |s, k| TODOS.field(0).select(k); -const SELECT_SUBTITLE: SelectorBorrowed = |s, k| TODOS.field(1).select(k); -``` - -Or with a custom derive macro to take care of some boilerplate and maintain readability. This macro simply generates the type tuple from the model fields and then some associated constants for indexing them. - -```rust -#[derive(EcsModel)] -struct TodoModel { - title: String, - subtitle: String -} - -// derives these impl (you don't need to write this yourself, but can if you want): -mod TodoModel { - type Layout = (String, String); - const title: u8 = 1; - const subtitle: u8 = 2; -} - - -const TODOS: RecoilEcs = |builder| {}; -const SELECT_TITLE: SelectorBorrowed = |s, k| TODOS.field(TodoModel::title).select(k); -const SELECT_SUBTITLE: SelectorBorrowed = |s, k| TODOS.field(TodoModel::subtitle).select(k); -``` - -## Optimization - -## Selectors and references. - -Because new values are inserted without touching the original, we can keep old values around. As such, it makes sense to allow borrowed data since we can continue to reference old data if the data itself hasn't changed. -However, this does lead to a greater memory overhead, so occasionally we'll want to sacrifice an a component render in order to evict old values. This will be done automatically for us due to the nature of Rc. - -Also, we want selectors to have internal dependencies. - -## Async Atoms - -- make it so "waiting" data can be provided -- someone updates via a future IE `atom.set(|| async {})` -- RecoilJS handles this with a "loadable" that has different states (ready, waiting, err just like Rust's poll method on future) - - I think the old value is provided until the new value is ready(?) - - Integrates with suspense to directly await the contents - -## Async atoms - -```rust -let (atom, set_atom, modify_atom) = (ATOM.use_read(cx), ATOM.use_write(cx), ATOM.use_modify(cx)); - -const Title: AsyncAtom = |builder| { - builder.on_set(|api, new_val| async { - - }) - builder.on_get(|api| async { - - }) -} -``` - -## Async selectors - -```rust -struct ContentCard { - title: String, - content: String, -} -const ROOT_URL: Atom<&str> = |_| "localhost:8080"; - -// Since we don't plan on changing any content during the lifetime of the app, a selector works fine -const ContentCards: SelectorFamily = |api, key| api.on_get_async(async { - // Whenever the root_url changes, this atom will be re-evaluated - let root_url = api.get(&ROOT_URL); - let data: ContentCard = fetch(format!("{}/{}", root_url, key).await?.json().await?; - data -}) - -static ContentCard: FC<()> = |cx| { - let body = async match use_recoil_value()(cx.id).await { - Ok(content) => rsx!{in cx, p {"{content}"} } - Err(e) => rsx!{in cx, p {"Failed to load"}} - }; - - rsx!{ - div { - h1{ "Non-async data here" }} - Suspense { - content: {body} - fallback: { rsx!{ div {"Loading..."} } } - } - } - } -}; -``` diff --git a/packages/atoms/notes/async.md b/packages/atoms/notes/async.md deleted file mode 100644 index ff507206d..000000000 --- a/packages/atoms/notes/async.md +++ /dev/null @@ -1,58 +0,0 @@ -# Asynchronous state management - -Async is pervasive in web frontends. The web heavily relies on external resources like http, requests, storage, etc. - -## How does Redux do it? - -Thunks - -- call a synchronous dispatch function -- queues an asynchronous "thunk" -- thunks may not modify state, may only call reducers - -## How does Recoil do it? - -useRecoilCallback - -- call an async function -- get/set any atom with the Recoil API -- synchronously set values - -async atoms - -- atom's "get" returns a promise that resolves to the atom's value -- if a request fails - -## How should we do it? - -```rust -const USER_ID: Atom = |atom| String::from("ThisIsDummyData"); -const TITLE: Selector> = |atom| { - atom.async_get(|api| async { - let user_id = api.get(ID); - let resp: Result = surf::get(format!("https://myapi.com/users/{}", user_id)) - .recv_json() - .await; - }); -} - -#[derive(Deref)] -struct UserManager(RecoilApi); -impl UserManager { - fn load_user_data() { - - } -} - -async fn Example(cx: Context<()>) -> Result { - let title = use_read(cx, TITLE)?; -} -``` - -```rust -fn use_initialize_app() { - // initialize theme systems - // initialize websocket connection - // create task queue -} -``` diff --git a/packages/atoms/src/lib.rs b/packages/atoms/src/lib.rs deleted file mode 100644 index a9db0bbf3..000000000 --- a/packages/atoms/src/lib.rs +++ /dev/null @@ -1,573 +0,0 @@ -use std::{ - cell::{Ref, RefCell}, - collections::HashMap, - hash::Hash, - marker::PhantomData, - rc::Rc, -}; - -pub use atomfamily::*; -pub use atoms::*; -pub use ecs::*; -use error::*; -pub use hooks::*; -pub use hooks::*; -pub use root::*; -pub use selector::*; -pub use selectorfamily::*; -pub use traits::*; -pub use utils::*; -mod tracingimmap; - -mod traits { - use dioxus_core::prelude::Context; - - use super::*; - pub trait MapKey: PartialEq + Hash + 'static {} - impl MapKey for T {} - - pub trait AtomValue: PartialEq + 'static {} - impl AtomValue for T {} - - // Atoms, selectors, and their family variants are readable - pub trait Readable: Sized + Copy { - fn use_read<'a, P: 'static>(self, cx: Context<'a, P>) -> &'a T { - hooks::use_read(cx, self) - } - - // This returns a future of the value - // If the atom is currently pending, that future will resolve to pending - // If the atom is currently ready, the future will immediately resolve - // if the atom switches from ready to pending, the component will re-run, returning a pending future - fn use_read_async<'a, P>(self, cx: Context<'a, P>) -> &'a T { - todo!() - } - - fn initialize(self, api: &RecoilRoot) -> T; - - // We use the Raw Ptr to the atom - // TODO: Make sure atoms with the same definitions don't get merged together. I don't think they do, but double check - fn static_id(self) -> u32; - } - - // Only atoms and atom families are writable - // Selectors and selector families are not - pub trait Writable: Readable + Sized { - fn use_read_write<'a, P>(self, cx: Context<'a, P>) -> (&'a T, &'a Rc) { - todo!() - } - - fn use_write<'a, P>(self, cx: Context<'a, P>) -> &'a Rc { - todo!() - } - } -} - -mod atoms { - use super::*; - - // Currently doesn't do anything, but will eventually add effects, id, serialize/deserialize keys, etc - // Doesn't currently allow async values, but the goal is to eventually enable them - pub struct AtomBuilder {} - - pub type Atom = fn(&mut AtomBuilder) -> T; - - // impl Readable for Atom {} - impl Readable for &'static Atom { - fn static_id(self) -> u32 { - self as *const _ as u32 - } - - fn initialize(self, api: &RecoilRoot) -> T { - let mut builder = AtomBuilder {}; - let p = self(&mut builder); - p - } - } - - impl Writable for &'static Atom {} - - mod compilests { - use super::*; - use dioxus_core::prelude::Context; - - fn _test(cx: Context<()>) { - const EXAMPLE_ATOM: Atom = |_| 10; - - // ensure that atoms are both read and write - let _ = use_read(cx, &EXAMPLE_ATOM); - let _ = use_read_write(cx, &EXAMPLE_ATOM); - let _ = use_write(cx, &EXAMPLE_ATOM); - } - } -} - -mod atomfamily { - use super::*; - pub trait FamilyCollection {} - impl FamilyCollection for HashMap {} - - use im_rc::HashMap as ImHashMap; - - /// AtomHashMaps provide an efficient way of maintaing collections of atoms. - /// - /// Under the hood, AtomHashMaps uses [IM](https://www.rust-lang.org)'s immutable HashMap implementation to lazily - /// clone data as it is modified. - /// - /// - /// - /// - /// - /// - pub type AtomHashMap = fn(&mut ImHashMap); - - pub trait AtomFamilySelector { - fn select(&'static self, k: &K) -> AtomMapSelection { - todo!() - } - } - - impl AtomFamilySelector for AtomHashMap { - fn select(&'static self, k: &K) -> AtomMapSelection { - todo!() - } - } - - pub struct AtomMapSelection<'a, K: MapKey, V: AtomValue> { - root: &'static AtomHashMap, - key: &'a K, - } - - impl<'a, K: MapKey, V: AtomValue> Readable for &AtomMapSelection<'a, K, V> { - fn static_id(self) -> u32 { - todo!() - } - - fn initialize(self, api: &RecoilRoot) -> V { - todo!() - // let mut builder = AtomBuilder {}; - // let p = self(&mut builder); - // p - } - } - - impl<'a, K: MapKey, T: AtomValue> Writable for &AtomMapSelection<'a, K, T> {} - - mod compiletests { - use dioxus_core::prelude::Context; - - use super::*; - const Titles: AtomHashMap = |map| {}; - - fn test(cx: Context<()>) { - let title = Titles.select(&10).use_read(cx); - let t2 = use_read(cx, &Titles.select(&10)); - } - } -} - -mod selector { - use super::*; - pub struct SelectorBuilder {} - impl SelectorBuilder { - pub fn get(&self, t: impl Readable) -> &T { - todo!() - } - } - pub type Selector = fn(&mut SelectorBuilder) -> T; - impl Readable for &'static Selector { - fn static_id(self) -> u32 { - todo!() - } - - fn initialize(self, api: &RecoilRoot) -> T { - todo!() - } - } - - pub struct SelectorFamilyBuilder {} - - impl SelectorFamilyBuilder { - pub fn get(&self, t: impl Readable) -> &T { - todo!() - } - } -} -mod selectorfamily { - use super::*; - // pub trait SelectionSelector { - // fn select(&self, k: &K) -> CollectionSelection { - // todo!() - // } - // } - // impl SelectionSelector for AtomFamily {} - - /// Create a new value as a result of a combination of previous values - /// If you need to return borrowed data, check out [`SelectorFamilyBorrowed`] - pub type SelectorFamily = fn(&mut SelectorFamilyBuilder, Key) -> Value; - - impl Readable for &'static SelectorFamily { - fn static_id(self) -> u32 { - todo!() - } - - fn initialize(self, api: &RecoilRoot) -> V { - todo!() - } - } - - /// Borrowed selector families are – surprisingly - discouraged. - /// This is because it's not possible safely memoize these values without keeping old versions around. - /// - /// However, it does come in handy to borrow the contents of an item without re-rendering child components. - pub type SelectorFamilyBorrowed = - for<'a> fn(&'a mut SelectorFamilyBuilder, Key) -> &'a Value; - - // impl<'a, K, V: 'a> SelectionSelector for fn(&'a mut SelectorFamilyBuilder, K) -> V {} -} - -mod root { - use std::{ - any::{Any, TypeId}, - collections::{HashSet, VecDeque}, - iter::FromIterator, - sync::atomic::{AtomicU32, AtomicUsize}, - }; - - use super::*; - // use generational_arena::Index as ConsumerId; - type AtomId = u32; - type ConsumerId = u32; - - pub type RecoilContext = RefCell; - - // Sometimes memoization means we don't need to re-render components that holds "correct values" - // IE we consider re-render more expensive than keeping the old value around. - // We *could* unsafely overwrite this slot, but that's just **asking** for UB (holding a &mut while & is held in components) - // - // Instead, we choose to let the hook itself hold onto the Rc by not forcing a render when T is the same. - // Whenever the component needs to be re-rendered for other reasons, the "get" method will automatically update the Rc to the most recent one. - pub struct RecoilRoot { - nodes: RefCell>, - consumer_map: HashMap, - } - - struct Slot { - type_id: TypeId, - source: AtomId, - value: Rc, - consumers: HashMap>, - dependents: HashSet, - } - - static NEXT_ID: AtomicU32 = AtomicU32::new(0); - fn next_consumer_id() -> u32 { - NEXT_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed) - } - - impl RecoilRoot { - pub(crate) fn new() -> Self { - Self { - nodes: Default::default(), - consumer_map: Default::default(), - } - } - - pub fn subscribe( - &mut self, - readable: impl Readable, - receiver_fn: Rc, - ) -> ConsumerId { - let consumer_id = next_consumer_id(); - let atom_id = readable.static_id(); - log::debug!("Subscribing consumer to atom {} {}", consumer_id, atom_id); - - let mut nodes = self.nodes.borrow_mut(); - let slot = nodes.get_mut(&atom_id).unwrap(); - slot.consumers.insert(consumer_id, receiver_fn); - self.consumer_map.insert(consumer_id, atom_id); - consumer_id - } - - pub fn unsubscribe(&mut self, consumer_id: ConsumerId) { - let atom_id = self.consumer_map.get(&consumer_id).unwrap(); - let mut nodes = self.nodes.borrow_mut(); - let slot = nodes.get_mut(&atom_id).unwrap(); - slot.consumers.remove(&consumer_id); - } - - /// Directly get the *slot* - /// All Atoms are held in slots (an Rc) - /// - /// - pub fn try_get_raw(&self, readable: impl Readable) -> Result> { - let atom_id = readable.static_id(); - let mut nodes = self.nodes.borrow_mut(); - if !nodes.contains_key(&atom_id) { - let value = Slot { - type_id: TypeId::of::(), - source: atom_id, - value: Rc::new(readable.initialize(self)), - consumers: Default::default(), - dependents: Default::default(), - }; - nodes.insert(atom_id, value); - } - let out = nodes - .get(&atom_id) - .unwrap() - .value - .clone() - .downcast::() - .unwrap(); - - Ok(out) - } - - pub fn try_set( - &mut self, - writable: impl Writable, - new_val: T, - ) -> crate::error::Result<()> { - let atom_id = writable.static_id(); - - self.set_by_id(atom_id, new_val); - - Ok(()) - } - - // A slightly dangerous method to manually overwrite any slot given an AtomId - pub(crate) fn set_by_id(&mut self, atom_id: AtomId, new_val: T) { - let mut nodes = self.nodes.borrow_mut(); - let consumers = match nodes.get_mut(&atom_id) { - Some(slot) => { - slot.value = Rc::new(new_val); - &slot.consumers - } - None => { - let value = Slot { - type_id: TypeId::of::(), - source: atom_id, - value: Rc::new(new_val), - // value: Rc::new(writable.initialize(self)), - consumers: Default::default(), - dependents: Default::default(), - }; - nodes.insert(atom_id, value); - &nodes.get(&atom_id).unwrap().consumers - } - }; - - for (id, consumer_fn) in consumers { - log::debug!("triggering selector {}", id); - consumer_fn(); - } - } - } -} - -mod hooks { - use super::*; - use dioxus_core::prelude::Context; - - pub fn use_init_recoil_root

(cx: Context

, cfg: impl Fn(())) { - cx.use_create_context(move || RefCell::new(RecoilRoot::new())) - } - - /// Gain access to the recoil API directly - set, get, modify, everything - /// This is the foundational hook in which read/write/modify are built on - /// - /// This does not subscribe the component to *any* updates - /// - /// You can use this method to create controllers that perform much more complex actions than set/get - /// However, be aware that "getting" values through this hook will not subscribe the component to any updates. - pub fn use_recoil_api<'a, P>(cx: Context<'a, P>) -> &'a Rc { - cx.use_context::() - } - - pub fn use_write<'a, P, T: AtomValue>( - cx: Context<'a, P>, - // todo: this shouldn't need to be static - writable: impl Writable, - ) -> &'a Rc { - let api = use_recoil_api(cx); - cx.use_hook( - move |_| { - let api = api.clone(); - let raw_id = writable.static_id(); - Rc::new(move |new_val| { - // - log::debug!("setting new value "); - let mut api = api.as_ref().borrow_mut(); - - // api.try_set(writable, new_val).expect("failed to set"); - api.set_by_id(raw_id, new_val); - }) as Rc - }, - move |hook| &*hook, - |hook| {}, - ) - } - - /// Read the atom and get the Rc directly to the Atom's slot - /// This is useful if you need the memoized Atom value. However, Rc is not as easy to - /// work with as - pub fn use_read_raw<'a, P, T: AtomValue>( - cx: Context<'a, P>, - readable: impl Readable, - ) -> &'a Rc { - struct ReadHook { - value: Rc, - consumer_id: u32, - } - - let api = use_recoil_api(cx); - cx.use_hook( - move |_| { - let mut api = api.as_ref().borrow_mut(); - - let update = cx.schedule_update(); - let val = api.try_get_raw(readable).unwrap(); - let id = api.subscribe(readable, update); - ReadHook { - value: val, - consumer_id: id, - } - }, - move |hook| { - let api = api.as_ref().borrow(); - - let val = api.try_get_raw(readable).unwrap(); - hook.value = val; - &hook.value - }, - move |hook| { - let mut api = api.as_ref().borrow_mut(); - api.unsubscribe(hook.consumer_id); - }, - ) - } - - /// - pub fn use_read<'a, P, T: AtomValue>(cx: Context<'a, P>, readable: impl Readable) -> &'a T { - use_read_raw(cx, readable).as_ref() - } - - /// # Use an atom in both read and write - /// - /// This method is only available for atoms and family selections (not selectors). - /// - /// This is equivalent to calling both `use_read` and `use_write`, but saves you the hassle and repitition - /// - /// ## Example - /// - /// ``` - /// const Title: Atom<&str> = |_| "hello"; - /// let (title, set_title) = use_read_write(cx, &Title); - /// - /// // equivalent to: - /// let (title, set_title) = (use_read(cx, &Title), use_write(cx, &Title)); - /// ``` - pub fn use_read_write<'a, P, T: AtomValue + 'static>( - cx: Context<'a, P>, - writable: impl Writable, - ) -> (&'a T, &'a Rc) { - (use_read(cx, writable), use_write(cx, writable)) - } - - /// # Modify an atom without using `use_read`. - /// - /// Occasionally, a component might want to write to an atom without subscribing to its changes. `use_write` does not - /// provide this functionality, so `use_modify` exists to gain access to the current atom value while setting it. - /// - /// ## Notes - /// - /// Do note that this hook can only be used with Atoms where T: Clone since we actually clone the current atom to make - /// it mutable. - /// - /// Also note that you need to stack-borrow the closure since the modify closure expects an &dyn Fn. If we made it - /// a static type, it wouldn't be possible to use the `modify` closure more than once (two closures always are different) - /// - /// ## Example - /// - /// ```ignore - /// let modify_atom = use_modify(cx, Atom); - /// - /// modify_atom(&|a| *a += 1) - /// ``` - pub fn use_modify<'a, T: AtomValue + 'static + Clone, P>( - cx: Context<'a, P>, - writable: impl Writable, - ) -> impl Fn(&dyn Fn()) { - |_| {} - } - - /// Use a family collection directly - /// !! Any changes to the family will cause this subscriber to update - /// Try not to put this at the very top-level of your app. - pub fn use_read_family<'a, K, V, P>( - cx: Context<'a, P>, - t: &AtomHashMap, - ) -> &'a im_rc::HashMap { - todo!() - } -} - -mod ecs { - use super::*; - pub struct Blah { - _p: PhantomData<(K, V)>, - } - pub type EcsModel = fn(Blah); -} - -mod utils { - use super::*; - use dioxus_core::prelude::*; - - pub struct RecoilApi {} - - /// This tiny util wraps your main component with the initializer for the recoil root. - /// This is useful for small programs and the examples in this crate - pub fn RecoilApp( - root: impl for<'a> Fn(Context<'a, T>) -> VNode, - ) -> impl for<'a> Fn(Context<'a, T>) -> VNode { - move |cx| { - use_init_recoil_root(cx, |_| {}); - root(cx) - } - } -} - -mod compiletests {} - -pub mod error { - use thiserror::Error as ThisError; - pub type Result = std::result::Result; - - #[derive(ThisError, Debug)] - pub enum Error { - #[error("Fatal Internal Error: {0}")] - FatalInternal(&'static str), - // #[error("Context is missing")] - // MissingSharedContext, - - // #[error("No event to progress")] - // NoEvent, - - // #[error("Wrong Properties Type")] - // WrongProps, - - // #[error("Base scope has not been mounted yet")] - // NotMounted, - // #[error("I/O Error: {0}")] - // BorrowMut(#[from] std::), - // #[error("eee Error: {0}")] - // IO(#[from] core::result::), - #[error("I/O Error: {0}")] - IO(#[from] std::io::Error), - - #[error(transparent)] - Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error - } -} diff --git a/packages/atoms/src/tracingimmap.rs b/packages/atoms/src/tracingimmap.rs deleted file mode 100644 index 05d3db9bc..000000000 --- a/packages/atoms/src/tracingimmap.rs +++ /dev/null @@ -1,29 +0,0 @@ -//! Tracing immap -//! Traces modifications since last generation -//! To reconstruct the history, you will need *all* the generations between the start and end points - -use im_rc::HashMap as ImMap; - -pub struct TracedHashMap { - inner: ImMap, - generation: u32, - mods_since_last_gen: Vec, -} - -impl TracedHashMap { - fn next_generation(&self) -> Self { - Self { - generation: self.generation + 1, - inner: self.inner.clone(), - mods_since_last_gen: vec![], - } - } -} - -#[test] -fn compare_dos() { - let map1 = im_rc::hashmap! {3 => 2, 2 => 3}; - let map2 = im_rc::hashmap! {2 => 3, 3 => 2}; - - assert_eq!(map1, map2); -} diff --git a/packages/atoms/.vscode/settings.json b/packages/desktop/.vscode/settings.json similarity index 100% rename from packages/atoms/.vscode/settings.json rename to packages/desktop/.vscode/settings.json diff --git a/packages/webview/ARCHITECTURE.md b/packages/desktop/ARCHITECTURE.md similarity index 100% rename from packages/webview/ARCHITECTURE.md rename to packages/desktop/ARCHITECTURE.md diff --git a/packages/webview/Cargo.toml b/packages/desktop/Cargo.toml similarity index 97% rename from packages/webview/Cargo.toml rename to packages/desktop/Cargo.toml index 4d961d5ad..4249d4826 100644 --- a/packages/webview/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dioxus-webview" +name = "dioxus-desktop" version = "0.0.0" authors = ["Jonathan Kelley "] edition = "2018" diff --git a/packages/webview/README.md b/packages/desktop/README.md similarity index 100% rename from packages/webview/README.md rename to packages/desktop/README.md diff --git a/packages/webview/client/Cargo.toml b/packages/desktop/client/Cargo.toml similarity index 100% rename from packages/webview/client/Cargo.toml rename to packages/desktop/client/Cargo.toml diff --git a/packages/webview/client/src/main.rs b/packages/desktop/client/src/main.rs similarity index 100% rename from packages/webview/client/src/main.rs rename to packages/desktop/client/src/main.rs diff --git a/packages/webview/examples/test.rs b/packages/desktop/examples/test.rs similarity index 82% rename from packages/webview/examples/test.rs rename to packages/desktop/examples/test.rs index 65787b97c..0d06acc78 100644 --- a/packages/webview/examples/test.rs +++ b/packages/desktop/examples/test.rs @@ -2,7 +2,7 @@ use dioxus_core as dioxus; use dioxus_core::prelude::*; fn main() { - dioxus_webview::launch(App, |f| f.with_focus().with_maximized(true)).expect("Failed"); + dioxus_webview::launch(App, |f| f.with_maximized(true)).expect("Failed"); } static App: FC<()> = |cx| { diff --git a/packages/webview/src/dom.rs b/packages/desktop/src/dom.rs similarity index 100% rename from packages/webview/src/dom.rs rename to packages/desktop/src/dom.rs diff --git a/packages/webview/src/escape.rs b/packages/desktop/src/escape.rs similarity index 100% rename from packages/webview/src/escape.rs rename to packages/desktop/src/escape.rs diff --git a/packages/webview/src/index.html b/packages/desktop/src/index.html similarity index 99% rename from packages/webview/src/index.html rename to packages/desktop/src/index.html index 51d53b731..2f1fe3174 100644 --- a/packages/webview/src/index.html +++ b/packages/desktop/src/index.html @@ -132,9 +132,6 @@

-
- -
diff --git a/packages/webview/src/lib.rs b/packages/desktop/src/lib.rs similarity index 86% rename from packages/webview/src/lib.rs rename to packages/desktop/src/lib.rs index 60f495562..addd8aeaa 100644 --- a/packages/webview/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -4,6 +4,7 @@ use std::sync::mpsc::channel; use std::sync::{Arc, RwLock}; use dioxus_core::*; +pub use wry; use wry::application::event::{Event, WindowEvent}; use wry::application::event_loop::{ControlFlow, EventLoop}; @@ -72,6 +73,8 @@ impl WebviewRenderer { let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new()))); let webview = WebViewBuilder::new(window)? + // .with_visible(false) + // .with_transparent(true) .with_url(&format!("data:text/html,{}", HTML_CONTENT))? .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| { match req.method.as_str() { @@ -126,19 +129,33 @@ impl WebviewRenderer { .build()?; event_loop.run(move |event, _, control_flow| { - *control_flow = ControlFlow::Wait; + *control_flow = ControlFlow::Poll; match event { - Event::WindowEvent { event, .. } => { - // - match event { - WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - _ => {} + Event::WindowEvent { + event, window_id, .. + } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(_) | WindowEvent::Moved(_) => { + let _ = webview.resize(); } + _ => {} + }, + Event::MainEventsCleared => { + webview.resize(); + // window.request_redraw(); } - _ => { - // let _ = webview.resize(); - } + + _ => {} // Event::WindowEvent { event, .. } => { + // // + // match event { + // WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + // _ => {} + // } + // } + // _ => { + // // let _ = webview.resize(); + // } } }); } diff --git a/packages/docsite/.vscode/settings.json b/packages/docsite/.vscode/settings.json deleted file mode 100644 index 80f51cff8..000000000 --- a/packages/docsite/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.inlayHints.enable": false -} \ No newline at end of file diff --git a/packages/docsite/Cargo.toml b/packages/docsite/Cargo.toml deleted file mode 100644 index 5da740b5e..000000000 --- a/packages/docsite/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "dioxus-docs-site" -version = "0.0.0" -authors = ["Jonathan Kelley "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -dioxus-ssr = { path="../ssr" } -pulldown-cmark = "0.8.0" -atoms = { path="../atoms" } -codeblocks = { path="../../../ecosystem-dioxus/syntec-dioxus/" } diff --git a/packages/docsite/README.md b/packages/docsite/README.md deleted file mode 100644 index 0fb5e4848..000000000 --- a/packages/docsite/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Dioxus docs site - -Generate the liveview site powering diouxslabs.com. - -We use the dioxus SSR crate and the use_router hook to generate all the routes - - - - - diff --git a/packages/docsite/src/blah.html b/packages/docsite/src/blah.html deleted file mode 100644 index a47e6caa4..000000000 --- a/packages/docsite/src/blah.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - diff --git a/packages/docsite/src/components/blog.rs b/packages/docsite/src/components/blog.rs deleted file mode 100644 index 623011ce1..000000000 --- a/packages/docsite/src/components/blog.rs +++ /dev/null @@ -1,9 +0,0 @@ -use dioxus_ssr::prelude::*; - -pub static Blog: FC<()> = |cx| { - rsx! { in cx, - div { - - } - } -}; diff --git a/packages/docsite/src/components/community.rs b/packages/docsite/src/components/community.rs deleted file mode 100644 index 7182a5a35..000000000 --- a/packages/docsite/src/components/community.rs +++ /dev/null @@ -1,9 +0,0 @@ -use dioxus_ssr::prelude::*; - -pub static Community: FC<()> = |cx| { - rsx! { in cx, - div { - - } - } -}; diff --git a/packages/docsite/src/components/docs.rs b/packages/docsite/src/components/docs.rs deleted file mode 100644 index 33f62b9a4..000000000 --- a/packages/docsite/src/components/docs.rs +++ /dev/null @@ -1,9 +0,0 @@ -use dioxus_ssr::prelude::*; - -pub static Docs: FC<()> = |cx| { - rsx! { in cx, - div { - - } - } -}; diff --git a/packages/docsite/src/components/homepage.rs b/packages/docsite/src/components/homepage.rs deleted file mode 100644 index 83535e516..000000000 --- a/packages/docsite/src/components/homepage.rs +++ /dev/null @@ -1,42 +0,0 @@ -use dioxus_ssr::prelude::*; - -const HeroContent: [(&'static str, &'static str); 3] = [ - ("Declarative", - "React makes it painless to create interactive UIs. Design simple views for each state in your application, and React will efficiently update and render just the right components when your data changes.\nDeclarative views make your code more predictable and easier to debug."), - - ("Component-Based", "Build encapsulated components that manage their own state, then compose them to make complex UIs.\nSince component logic is written in JavaScript instead of templates, you can easily pass rich data through your app and keep state out of the DOM."), - - ("Learn Once, Write Anywhere", "We don’t make assumptions about the rest of your technology stack, so you can develop new features in React without rewriting existing code.\nReact can also render on the server using Node and power mobile apps using React Native."), -]; - -const SnippetHighlights: &'static str = include_str!("./../snippets.md"); - -pub static Home: FC<()> = |cx| { - let hero = HeroContent.iter().map(|(title, body)| { - rsx! { - div { - h3 { "{title}" } - div { {body.split("\n").map(|paragraph| rsx!( p{"{paragraph}"} ))} } - } - } - }); - let snippets: Vec = crate::utils::markdown_to_snippet(cx, SnippetHighlights); - - rsx! { in cx, - div { - header { - // Hero - section { - div { {hero} } - } - hr {} - // Highlighted Snippets - section { - {snippets} - } - } - div {} - section {} - } - } -}; diff --git a/packages/docsite/src/components/tutorial.rs b/packages/docsite/src/components/tutorial.rs deleted file mode 100644 index 302bfa248..000000000 --- a/packages/docsite/src/components/tutorial.rs +++ /dev/null @@ -1,9 +0,0 @@ -use dioxus_ssr::prelude::*; - -pub static Tutorial: FC<()> = |cx| { - rsx! { in cx, - div { - - } - } -}; diff --git a/packages/docsite/src/main.rs b/packages/docsite/src/main.rs deleted file mode 100644 index c97a813d3..000000000 --- a/packages/docsite/src/main.rs +++ /dev/null @@ -1,152 +0,0 @@ -#![allow(non_upper_case_globals)] - -use dioxus_ssr::{prelude::*, TextRenderer}; -pub mod utils; -mod components { - mod community; - pub use community::*; - mod docs; - pub use docs::*; - mod blog; - pub use blog::*; - mod homepage; - pub use homepage::*; - mod tutorial; - pub use tutorial::*; -} -use components::*; - -fn main() { - let renderer = TextRenderer::new(App); - /* - renderer.render_at("community") - - */ -} - -static App: FC<()> = |cx| { - let (url, set_url) = use_state(cx, || ""); - - let body = match *url { - "community" => rsx!(in cx, Community {}), - "tutorial" => rsx!(in cx, Tutorial {}), - "blog" => rsx!(in cx, Blog {}), - "docs" => rsx!(in cx, Docs {}), - _ => rsx!(in cx, Home {}), - }; - - cx.render(rsx! { - div { - NavBar {} - {body} - Footer {} - } - }) -}; - -static NavBar: FC<()> = |cx| { - cx.render(rsx! { - header { - a { - href: "/" - img { /*logo*/ } - span {} - } - nav { - a { href: "/community/support", "Community" } - a { href: "/docs/getting-started", "Docs" } - a { href: "/tutorial/tutorial", "Tutorial" } - a { href: "/blog/", "Blog" } - } - form {} - div {} - } - }) -}; - -static SECTIONS: &[(&str, &[(&str, &str)])] = &[ - ( - "Docs", - &[ - ("Installation", "docs/installation"), - ("Main Concepts", "docs/main"), - ("Advanced Guides", "docs/advanced"), - ("Hooks", "docs/hooks"), - ("Testing", "docs/testing"), - ("Contributing", "docs/contributing"), - ("FAQ", "docs/faq"), - ], - ), - ( - "Channels", - &[("Github", "https://github.com/jkelleyrtp/dioxus")], - ), - ( - "Community", - &[ - ("Code of Conduct", "docs/installation"), - ("Community Resources", "docs/main"), - ], - ), - ( - "More", - &[ - ("Tutorial", "docs/installation"), - ("Blog", "docs/main"), - ("Privacy", "docs/advanced"), - ("Terms", "docs/hooks"), - ], - ), -]; - -fn Footer(cx: Context<()>) -> VNode { - let sections = SECTIONS.iter().map(|(section, raw_links)| { - let links = raw_links.iter().map(|(link_name, href)| { - rsx! ( - a { href: "{href}", - "{link_name}", - {href.starts_with("http").then(|| rsx!( ExternalLinkIcon {} ))} - } - ) - }); - rsx! { - div { - div { "{section}" } - {links} - } - } - }); - - cx.render(rsx! { - footer { - div { - div { - div { - {sections} - } - section { - a { - img {} - } - p {} - } - } - } - } - }) -} - -const ExternalLinkIcon: FC<()> = |cx| { - cx.render(html! { - - - - - }) -}; diff --git a/packages/docsite/src/snippets.md b/packages/docsite/src/snippets.md deleted file mode 100644 index 31dc496b9..000000000 --- a/packages/docsite/src/snippets.md +++ /dev/null @@ -1,56 +0,0 @@ -# A Simple Component - -```rust -#[derive(PartialEq, Properties)] -struct Props { name: &'static str } - -static HelloMessage: FC = |cx| { - cx.render(rsx!{ - div { "Hello {cx.props.name}" } - }) -} -``` - -# Two syntaxes: html! and rsx! - -Choose from a close-to-html syntax or the standard rsx! syntax - -```rust -static HelloMessage: FC<()> = |cx| { - cx.render(html!{ -
Hello World!
- }) -} -``` - -# A Stateful Component - -Store state with hooks! - -```rust -enum LightState { - Green - Yellow, - Red, -} -static HelloMessage: FC<()> = |cx| { - let (color, set_color) = use_state(cx, || LightState::Green); - - let title = match color { - Green => "Green means go", - Yellow => "Yellow means slow down", - Red => "Red means stop", - }; - - cx.render(rsx!{ - h1 { "{title}" } - button { "tick" - onclick: move |_| set_color(match color { - Green => Yellow, - Yellow => Red, - Red => Green, - }) - } - }) -} -``` diff --git a/packages/docsite/src/utils.rs b/packages/docsite/src/utils.rs deleted file mode 100644 index 6f23514d6..000000000 --- a/packages/docsite/src/utils.rs +++ /dev/null @@ -1,31 +0,0 @@ -use dioxus_ssr::prelude::{Context, VNode}; - -// Parse a snippet into -pub fn markdown_to_snippet<'a, P>(cx: Context<'a, P>, text: &str) -> Vec> { - let snips = Vec::new(); - use pulldown_cmark::{Options, Parser}; - let mut options = Options::empty(); - let mut parser = Parser::new_ext(text, options); - - while let Some(evt) = parser.next() { - match evt { - pulldown_cmark::Event::Start(tag) => { - // take until the end - let r = parser.next(); - } - - // push a p{} tag with the contents - pulldown_cmark::Event::Text(text) => todo!(), - - // Code delinates an end - pulldown_cmark::Event::Code(code) => todo!(), - - // not supported - pulldown_cmark::Event::Html(ht) => {} - _ => {} - } - } - - // - snips -} diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 4be73dcdd..a08686d3c 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -1207,6 +1207,7 @@ builder_constructors! { srcset: String, // FIXME this is much more complicated usemap: String, // FIXME should be a fragment starting with '#' width: usize, + referrerpolicy: String, // sizes: SpacedList, // FIXME it's not really just a string }; diff --git a/packages/mobile/.cargo/config.toml b/packages/mobile/.cargo/config.toml new file mode 100644 index 000000000..bef33769e --- /dev/null +++ b/packages/mobile/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "aarch64-apple-ios" diff --git a/packages/mobile/.vscode/settings.json b/packages/mobile/.vscode/settings.json new file mode 100644 index 000000000..433a1b335 --- /dev/null +++ b/packages/mobile/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.inlayHints.enable": true, + "rust-analyzer.checkOnSave.allTargets": true, + "rust-analyzer.cargo.target": "aarch64-apple-ios" +} diff --git a/packages/mobile/Cargo.toml b/packages/mobile/Cargo.toml index 1bc55d02c..07e6fe7a3 100644 --- a/packages/mobile/Cargo.toml +++ b/packages/mobile/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "dioxus-ios" +name = "dioxus-mobile" version = "0.0.0" authors = ["Jonathan Kelley "] edition = "2018" @@ -7,6 +7,19 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cacao = { git = "https://github.com/ryanmcgrath/cacao" } +anyhow = "1.0.42" +# cacao = { git = "https://github.com/ryanmcgrath/cacao" } dioxus-core = { path = "../core", version = "0.1.2" } log = "0.4.14" +serde = "1.0.126" +serde_json = "1.0.64" +wry = "0.11.0" + + +[target.'cfg(target_os = "android")'.dependencies] +android_logger = "0.9.0" +log = "0.4.11" +ndk-glue = "0.2.1" + +[target.'cfg(not(target_os = "android"))'.dependencies] +simple_logger = "1.11.0" diff --git a/packages/mobile/src/dom.rs b/packages/mobile/src/dom.rs new file mode 100644 index 000000000..72aa87d68 --- /dev/null +++ b/packages/mobile/src/dom.rs @@ -0,0 +1,43 @@ +//! webview dom + +use dioxus_core::{DomEdit, RealDom, RealDomNode, ScopeId}; +use 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(registry: WebviewRegistry) -> 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 + } +} +impl<'bump> RealDom<'bump> for WebviewDom<'bump> { + fn raw_node_as_any(&self) -> &mut dyn std::any::Any { + todo!() + // self.edits.push(PushRoot { root }); + } + + fn request_available_node(&mut self) -> RealDomNode { + self.node_counter += 1; + RealDomNode::from_u64(self.node_counter) + } +} diff --git a/packages/mobile/src/lib.rs b/packages/mobile/src/lib.rs index c4784d596..aeae63ff3 100644 --- a/packages/mobile/src/lib.rs +++ b/packages/mobile/src/lib.rs @@ -1,70 +1,230 @@ -use dioxus::virtual_dom::VirtualDom; -pub use dioxus_core as dioxus; -use dioxus_core::{events::EventTrigger, prelude::FC}; +use dioxus_core::*; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; +use wry::application::event::{Event, WindowEvent}; +use wry::application::event_loop::{ControlFlow, EventLoop}; +use wry::application::window::Fullscreen; +use wry::application::{ + dpi::LogicalSize, + event::StartCause, + platform::ios::{ScreenEdge, WindowBuilderExtIOS, WindowExtIOS}, +}; +use wry::webview::WebViewBuilder; +use wry::{ + application::window::{Window, WindowBuilder}, + webview::{RpcRequest, RpcResponse}, +}; +mod dom; +use dom::*; -pub struct IosRenderer { - internal_dom: VirtualDom, +fn init_logging() { + simple_logger::SimpleLogger::new().init().unwrap(); } -impl IosRenderer { - pub async fn run(&mut self) -> dioxus_core::error::Result<()> { - let (sender, mut receiver) = async_channel::unbounded::(); +static HTML_CONTENT: &'static str = include_str!("./../../webview/src/index.html"); - // let body_element = prepare_websys_dom(); +pub fn launch(root: FC<()>, builder: fn(WindowBuilder) -> WindowBuilder) -> anyhow::Result<()> { + launch_with_props(root, (), builder) +} +pub fn launch_with_props( + root: FC

, + props: P, + builder: fn(WindowBuilder) -> WindowBuilder, +) -> anyhow::Result<()> { + WebviewRenderer::run(root, props, builder) +} - let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| { - log::debug!("Event trigger! {:#?}", ev); - let mut c = sender.clone(); - // wasm_bindgen_futures::spawn_local(async move { - // c.send(ev).await.unwrap(); - // }); - }); - let root_node = body_element.first_child().unwrap(); - patch_machine.stack.push(root_node.clone()); +/// The `WebviewRenderer` provides a way of rendering a Dioxus Virtual DOM through a bridge to a Webview instance. +/// Components used in WebviewRenderer instances can directly use system libraries, access the filesystem, and multithread with ease. +pub struct WebviewRenderer { + /// The root component used to render the Webview + root: FC, +} +enum RpcEvent<'a> { + Initialize { + // + edits: Vec>, + }, +} - // todo: initialize the event registry properly on the root +impl WebviewRenderer { + pub fn run( + root: FC, + props: T, + user_builder: fn(WindowBuilder) -> WindowBuilder, + ) -> anyhow::Result<()> { + Self::run_with_edits(root, props, user_builder, None) + } - let edits = self.internal_dom.rebuild()?; - log::debug!("Received edits: {:#?}", edits); - edits.iter().for_each(|edit| { - log::debug!("patching with {:?}", edit); - patch_machine.handle_edit(edit); - }); + pub fn run_with_edits( + root: FC, + props: T, + user_builder: fn(WindowBuilder) -> WindowBuilder, + redits: Option>>, + ) -> anyhow::Result<()> { + // pub fn run_with_edits( + // root: FC, + // props: T, + // user_builder: fn(WindowBuilder) -> WindowBuilder, + // redits: Option>>, + // ) -> anyhow::Result<()> { + let mut weviews = HashMap::new(); - patch_machine.reset(); - let root_node = body_element.first_child().unwrap(); - patch_machine.stack.push(root_node.clone()); + let vir = VirtualDom::new_with_props(root, props); - // log::debug!("patch stack size {:?}", patch_machine.stack); + let vdom = Arc::new(RwLock::new(vir)); - // Event loop waits for the receiver to finish up - // TODO! Connect the sender to the virtual dom's suspense system - // Suspense is basically an external event that can force renders to specific nodes - while let Ok(event) = receiver.recv().await { - log::debug!("Stack before entrance {:#?}", patch_machine.stack.top()); - // log::debug!("patch stack size before {:#?}", patch_machine.stack); - // patch_machine.reset(); - // patch_machine.stack.push(root_node.clone()); - let edits = self.internal_dom.progress_with_event(event)?; - log::debug!("Received edits: {:#?}", edits); + let event_loop = EventLoop::new(); + event_loop.run(move |event, event_loop, control_flow| { + *control_flow = ControlFlow::Wait; - for edit in &edits { - log::debug!("edit stream {:?}", edit); - // log::debug!("Stream stack {:#?}", patch_machine.stack.top()); - patch_machine.handle_edit(edit); + match event { + Event::NewEvents(StartCause::Init) => { + println!("Init"); + + let window = user_builder(WindowBuilder::new()) + .build(&event_loop) + .unwrap(); + + let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new()))); + + let window = WindowBuilder::new().build(&event_loop).unwrap(); + let window_id = window.id(); + + let vdom = vdom.clone(); + let weview = WebViewBuilder::new(window) + .unwrap() + // .with_visible(false) + // .with_transparent(true) + .with_url(&format!("data:text/html,{}", HTML_CONTENT)) + .unwrap() + .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| { + match req.method.as_str() { + "initiate" => { + // let edits = if let Some(edits) = &redits { + // serde_json::to_value(edits).unwrap() + // } else + let edits = { + let mut lock = vdom.write().unwrap(); + let mut reg_lock = registry.write().unwrap(); + + // Create the thin wrapper around the registry to collect the edits into + let mut real = + dom::WebviewDom::new(reg_lock.take().unwrap()); + + // Serialize the edit stream + let edits = { + let mut edits = Vec::new(); + lock.rebuild(&mut real, &mut edits).unwrap(); + serde_json::to_value(edits).unwrap() + }; + + // Give back the registry into its slot + *reg_lock = Some(real.consume()); + edits + }; + + // Return the edits into the webview runtime + Some(RpcResponse::new_result(req.id.take(), Some(edits))) + } + "user_event" => { + let mut lock = vdom.write().unwrap(); + let mut reg_lock = registry.write().unwrap(); + + // Create the thin wrapper around the registry to collect the edits into + let mut real = dom::WebviewDom::new(reg_lock.take().unwrap()); + + // Serialize the edit stream + let edits = { + let mut edits = Vec::new(); + lock.rebuild(&mut real, &mut edits).unwrap(); + serde_json::to_value(edits).unwrap() + }; + + // Give back the registry into its slot + *reg_lock = Some(real.consume()); + + // Return the edits into the webview runtime + Some(RpcResponse::new_result(req.id.take(), Some(edits))) + } + _ => todo!("this message failed"), + } + }) + .build() + .unwrap(); + + // let weview = WebViewBuilder::new(window) + // .unwrap() + // .with_url("https://tauri.studio") + // .unwrap() + // .build() + // .unwrap(); + weviews.insert(window_id, weview); + } + Event::Resumed => { + println!("applicationDidBecomeActive"); + } + Event::Suspended => { + println!("applicationWillResignActive"); + } + Event::LoopDestroyed => { + println!("applicationWillTerminate"); + } + Event::WindowEvent { + window_id, + event: WindowEvent::Touch(touch), + .. + } => { + println!("touch on {:?} {:?}", window_id, touch); + } + _ => {} } - - // log::debug!("patch stack size after {:#?}", patch_machine.stack); - patch_machine.reset(); - // our root node reference gets invalidated - // not sure why - // for now, just select the first child again. - // eventually, we'll just make our own root element instead of using body - // or just use body directly IDEK - let root_node = body_element.first_child().unwrap(); - patch_machine.stack.push(root_node.clone()); - } - - Ok(()) // should actually never return from this, should be an error, rustc just cant see it + }); } } + +fn main() { + init_logging(); + let event_loop = EventLoop::new(); + + let mut weviews = HashMap::new(); + + event_loop.run(move |event, event_loop, control_flow| { + *control_flow = ControlFlow::Wait; + match event { + Event::NewEvents(StartCause::Init) => { + println!("Init"); + + let window = WindowBuilder::new().build(&event_loop).unwrap(); + let window_id = window.id(); + + let weview = WebViewBuilder::new(window) + .unwrap() + .with_url("https://tauri.studio") + .unwrap() + .build() + .unwrap(); + weviews.insert(window_id, weview); + } + Event::Resumed => { + println!("applicationDidBecomeActive"); + } + Event::Suspended => { + println!("applicationWillResignActive"); + } + Event::LoopDestroyed => { + println!("applicationWillTerminate"); + } + Event::WindowEvent { + window_id, + event: WindowEvent::Touch(touch), + .. + } => { + println!("touch on {:?} {:?}", window_id, touch); + } + _ => {} + } + }); +} diff --git a/packages/mobile/src/oldlib.rs b/packages/mobile/src/oldlib.rs new file mode 100644 index 000000000..c4784d596 --- /dev/null +++ b/packages/mobile/src/oldlib.rs @@ -0,0 +1,70 @@ +use dioxus::virtual_dom::VirtualDom; +pub use dioxus_core as dioxus; +use dioxus_core::{events::EventTrigger, prelude::FC}; + +pub struct IosRenderer { + internal_dom: VirtualDom, +} + +impl IosRenderer { + pub async fn run(&mut self) -> dioxus_core::error::Result<()> { + let (sender, mut receiver) = async_channel::unbounded::(); + + // let body_element = prepare_websys_dom(); + + let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| { + log::debug!("Event trigger! {:#?}", ev); + let mut c = sender.clone(); + // wasm_bindgen_futures::spawn_local(async move { + // c.send(ev).await.unwrap(); + // }); + }); + let root_node = body_element.first_child().unwrap(); + patch_machine.stack.push(root_node.clone()); + + // todo: initialize the event registry properly on the root + + let edits = self.internal_dom.rebuild()?; + log::debug!("Received edits: {:#?}", edits); + edits.iter().for_each(|edit| { + log::debug!("patching with {:?}", edit); + patch_machine.handle_edit(edit); + }); + + patch_machine.reset(); + let root_node = body_element.first_child().unwrap(); + patch_machine.stack.push(root_node.clone()); + + // log::debug!("patch stack size {:?}", patch_machine.stack); + + // Event loop waits for the receiver to finish up + // TODO! Connect the sender to the virtual dom's suspense system + // Suspense is basically an external event that can force renders to specific nodes + while let Ok(event) = receiver.recv().await { + log::debug!("Stack before entrance {:#?}", patch_machine.stack.top()); + // log::debug!("patch stack size before {:#?}", patch_machine.stack); + // patch_machine.reset(); + // patch_machine.stack.push(root_node.clone()); + let edits = self.internal_dom.progress_with_event(event)?; + log::debug!("Received edits: {:#?}", edits); + + for edit in &edits { + log::debug!("edit stream {:?}", edit); + // log::debug!("Stream stack {:#?}", patch_machine.stack.top()); + patch_machine.handle_edit(edit); + } + + // log::debug!("patch stack size after {:#?}", patch_machine.stack); + patch_machine.reset(); + // our root node reference gets invalidated + // not sure why + // for now, just select the first child again. + // eventually, we'll just make our own root element instead of using body + // or just use body directly IDEK + let root_node = body_element.first_child().unwrap(); + patch_machine.stack.push(root_node.clone()); + } + + Ok(()) // should actually never return from this, should be an error, rustc just cant see it + } +} diff --git a/packages/ssr/examples/tofile.rs b/packages/ssr/examples/tofile.rs new file mode 100644 index 000000000..bf8872c58 --- /dev/null +++ b/packages/ssr/examples/tofile.rs @@ -0,0 +1,142 @@ +use dioxus::virtual_dom::VirtualDom; +use dioxus_core as dioxus; +use dioxus_core::prelude::*; +use dioxus_hooks::use_state; +use dioxus_html as dioxus_elements; +use dioxus_html::{GlobalAttributes, SvgAttributes}; +use dioxus_ssr::TextRenderer; + +fn main() { + use std::fs::File; + use std::io::Write; + + let mut file = File::create("example.html").unwrap(); + + let mut dom = VirtualDom::new(App); + dom.rebuild_in_place().expect("failed to run virtualdom"); + + file.write_fmt(format_args!("{}", TextRenderer::new(&dom))) + .unwrap(); +} + +pub const App: FC<()> = |cx| { + cx.render(rsx!( + div { class: "overflow-hidden" + link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" } + Header {} + // Entry {} + Hero {} + Hero {} + Hero {} + Hero {} + Hero {} + } + )) +}; + +pub const Header: FC<()> = |cx| { + cx.render(rsx! { + div { + header { class: "text-gray-400 bg-gray-900 body-font" + div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center" + a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0" + StacksIcon {} + span { class: "ml-3 text-xl" "Hello Dioxus!"} + } + nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" + a { class: "mr-5 hover:text-white" "First Link"} + a { class: "mr-5 hover:text-white" "Second Link"} + a { class: "mr-5 hover:text-white" "Third Link"} + a { class: "mr-5 hover:text-white" "Fourth Link"} + } + button { + class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0" + "Button" + RightArrowIcon {} + } + } + } + } + }) +}; + +pub const Hero: FC<()> = |cx| { + // + cx.render(rsx! { + section{ class: "text-gray-400 bg-gray-900 body-font" + div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" + div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center" + h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white" + br { class: "hidden lg:inline-block" } + "Dioxus Sneak Peek" + } + p { + class: "mb-8 leading-relaxed" + + "Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web + technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and + on mobile and embedded platforms." + + } + div { class: "flex justify-center" + button { + class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg" + "Learn more" + } + button { + class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg" + "Build an app" + } + } + } + div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6" + img { class: "object-cover object-center rounded" alt: "hero" src: "https://i.imgur.com/oK6BLtw.png" + referrerpolicy:"no-referrer" + } + } + } + } + }) +}; +pub const Entry: FC<()> = |cx| { + // + cx.render(rsx! { + section{ class: "text-gray-400 bg-gray-900 body-font" + div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" + textarea { + + } + } + } + }) +}; + +pub const StacksIcon: FC<()> = |cx| { + cx.render(rsx!( + svg { + // xmlns: "http://www.w3.org/2000/svg" + fill: "none" + stroke: "currentColor" + stroke_linecap: "round" + stroke_linejoin: "round" + stroke_width: "2" + // class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" + viewBox: "0 0 24 24" + path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"} + } + )) +}; +pub const RightArrowIcon: FC<()> = |cx| { + cx.render(rsx!( + svg { + fill: "none" + stroke: "currentColor" + stroke_linecap: "round" + stroke_linejoin: "round" + stroke_width: "2" + // class: "w-4 h-4 ml-1" + viewBox: "0 0 24 24" + path { d: "M5 12h14M12 5l7 7-7 7"} + } + )) +}; diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index b6838515b..47c949016 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -61,7 +61,7 @@ pub struct TextRenderer<'a> { } impl<'a> TextRenderer<'a> { - fn new(vdom: &'a VirtualDom) -> Self { + pub fn new(vdom: &'a VirtualDom) -> Self { Self { vdom, cfg: SsrConfig::default(), diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 7d0d5f32c..be2584f4f 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -22,7 +22,7 @@ console_error_panic_hook = "0.1.6" generational-arena = "0.2.8" wasm-bindgen-test = "0.3.21" once_cell = "1.7.2" -atoms = { path = "../atoms" } +# atoms = { path = "../atoms" } async-channel = "1.6.1" anyhow = "1.0.41" slotmap = "1.0.3" diff --git a/packages/web/examples/demo.rs b/packages/web/examples/demo.rs new file mode 100644 index 000000000..875f3b2b0 --- /dev/null +++ b/packages/web/examples/demo.rs @@ -0,0 +1,148 @@ +//! Basic example that renders a simple VNode to the browser. + +use dioxus::events::on::MouseEvent; +use dioxus_core as dioxus; +use dioxus_core::prelude::*; +use dioxus_hooks::*; +use dioxus_html as dioxus_elements; +// use wasm_timer; + +use std::future::Future; + +use std::{pin::Pin, time::Duration}; + +use dioxus::prelude::*; +use dioxus_html::SvgAttributes; + +use dioxus_web::*; + +fn main() { + // Setup logging + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + console_error_panic_hook::set_once(); + + // Run the app + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); +} + +pub const App: FC<()> = |cx| { + cx.render(rsx!( + div { class: "overflow-hidden" + link { href:"https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel:"stylesheet" } + Header {} + // Entry {} + Hero {} + Hero {} + Hero {} + Hero {} + Hero {} + } + )) +}; + +pub const Header: FC<()> = |cx| { + cx.render(rsx! { + div { + header { class: "text-gray-400 bg-gray-900 body-font" + div { class: "container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center" + a { class: "flex title-font font-medium items-center text-white mb-4 md:mb-0" + StacksIcon {} + span { class: "ml-3 text-xl" "Hello Dioxus!"} + } + nav { class: "md:ml-auto flex flex-wrap items-center text-base justify-center" + a { class: "mr-5 hover:text-white" "First Link"} + a { class: "mr-5 hover:text-white" "Second Link"} + a { class: "mr-5 hover:text-white" "Third Link"} + a { class: "mr-5 hover:text-white" "Fourth Link"} + } + button { + class: "inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0" + "Button" + RightArrowIcon {} + } + } + } + } + }) +}; + +pub const Hero: FC<()> = |cx| { + // + cx.render(rsx! { + section{ class: "text-gray-400 bg-gray-900 body-font" + div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" + div { class: "lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center" + h1 { class: "title-font sm:text-4xl text-3xl mb-4 font-medium text-white" + br { class: "hidden lg:inline-block" } + "Dioxus Sneak Peek" + } + p { + class: "mb-8 leading-relaxed" + + "Dioxus is a new UI framework that makes it easy and simple to write cross-platform apps using web + technologies! It is functional, fast, and portable. Dioxus can run on the web, on the desktop, and + on mobile and embedded platforms." + + } + div { class: "flex justify-center" + button { + class: "inline-flex text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg" + "Learn more" + } + button { + class: "ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg" + "Build an app" + } + } + } + div { class: "lg:max-w-lg lg:w-full md:w-1/2 w-5/6" + img { class: "object-cover object-center rounded" alt: "hero" src: "https://i.imgur.com/oK6BLtw.png" + referrerpolicy:"no-referrer" + } + } + } + } + }) +}; +pub const Entry: FC<()> = |cx| { + // + cx.render(rsx! { + section{ class: "text-gray-400 bg-gray-900 body-font" + div { class: "container mx-auto flex px-5 py-24 md:flex-row flex-col items-center" + textarea { + + } + } + } + }) +}; + +pub const StacksIcon: FC<()> = |cx| { + cx.render(rsx!( + svg { + // xmlns: "http://www.w3.org/2000/svg" + fill: "none" + stroke: "currentColor" + stroke_linecap: "round" + stroke_linejoin: "round" + stroke_width: "2" + // class: "w-10 h-10 text-white p-2 bg-indigo-500 rounded-full" + viewBox: "0 0 24 24" + path { d: "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"} + } + )) +}; +pub const RightArrowIcon: FC<()> = |cx| { + cx.render(rsx!( + svg { + fill: "none" + stroke: "currentColor" + stroke_linecap: "round" + stroke_linejoin: "round" + stroke_width: "2" + // class: "w-4 h-4 ml-1" + viewBox: "0 0 24 24" + path { d: "M5 12h14M12 5l7 7-7 7"} + } + )) +}; diff --git a/packages/webview/.vscode/settings.json b/packages/webview/.vscode/settings.json deleted file mode 100644 index 80f51cff8..000000000 --- a/packages/webview/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "rust-analyzer.inlayHints.enable": false -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 57cd13958..45fe67b57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -176,6 +176,9 @@ pub use dioxus_core::events; #[cfg(feature = "web")] pub use dioxus_web as web; +#[cfg(feature = "mobile")] +pub use dioxus_mobile as mobile; + #[cfg(feature = "ssr")] pub use dioxus_ssr as ssr;