Browse Source

Merge branch 'DioxusLabs:master' into master

YuKun Liu 3 năm trước cách đây
mục cha
commit
65fe7fd114
40 tập tin đã thay đổi với 741 bổ sung871 xóa
  1. 4 5
      .github/workflows/macos.yml
  2. 27 23
      .github/workflows/main.yml
  3. 4 5
      .github/workflows/windows.yml
  4. 8 17
      Cargo.toml
  5. 1 0
      docs/fermi/.gitignore
  6. 6 0
      docs/fermi/book.toml
  7. 3 0
      docs/fermi/src/SUMMARY.md
  8. 1 0
      docs/fermi/src/chapter_1.md
  9. 12 12
      docs/reference/src/platforms/ssr.md
  10. 30 0
      examples/fermi.rs
  11. 1 0
      packages/core-macro/Cargo.toml
  12. 0 443
      packages/core-macro/src/htm.rs
  13. 4 36
      packages/core-macro/src/lib.rs
  14. 0 214
      packages/core-macro/src/router.rs
  15. 0 84
      packages/core-macro/src/rsxtemplate.rs
  16. 1 1
      packages/desktop/src/events.rs
  17. 4 0
      packages/desktop/src/lib.rs
  18. 14 0
      packages/fermi/Cargo.toml
  19. 92 0
      packages/fermi/README.md
  20. 28 0
      packages/fermi/src/atoms/atom.rs
  21. 25 0
      packages/fermi/src/atoms/atomfamily.rs
  22. 25 0
      packages/fermi/src/atoms/atomref.rs
  23. 1 0
      packages/fermi/src/atoms/selector.rs
  24. 1 0
      packages/fermi/src/atoms/selectorfamily.rs
  25. 53 0
      packages/fermi/src/callback.rs
  26. 62 0
      packages/fermi/src/hooks/atom_ref.rs
  27. 11 0
      packages/fermi/src/hooks/atom_root.rs
  28. 11 0
      packages/fermi/src/hooks/init_atom_root.rs
  29. 36 0
      packages/fermi/src/hooks/read.rs
  30. 13 0
      packages/fermi/src/hooks/set.rs
  31. 58 0
      packages/fermi/src/lib.rs
  32. 103 0
      packages/fermi/src/root.rs
  33. 13 0
      packages/rsx/Cargo.toml
  34. 4 4
      packages/rsx/src/component.rs
  35. 27 23
      packages/rsx/src/element.rs
  36. 4 2
      packages/rsx/src/lib.rs
  37. 3 1
      packages/rsx/src/node.rs
  38. 1 0
      packages/rsx/src/pretty.rs
  39. 4 1
      src/lib.rs
  40. 46 0
      tests/fermi.rs

+ 4 - 5
.github/workflows/macos.yml

@@ -1,7 +1,10 @@
 name: macOS tests
 
 on:
-  push:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    branches:
+      - master
     paths:
       - packages/**
       - examples/**
@@ -9,10 +12,6 @@ on:
       - .github/**
       - lib.rs
       - Cargo.toml
-  pull_request:
-    types: [opened, synchronize, reopened, ready_for_review]
-    branches:
-      - master
 
 jobs:
   test:

+ 27 - 23
.github/workflows/main.yml

@@ -1,7 +1,10 @@
 name: Rust CI
 
 on:
-  push:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    branches:
+      - master
     paths:
       - packages/**
       - examples/**
@@ -9,10 +12,6 @@ on:
       - .github/**
       - lib.rs
       - Cargo.toml
-  pull_request:
-    types: [opened, synchronize, reopened, ready_for_review]
-    branches:
-      - master
 
 jobs:
   check:
@@ -34,6 +33,7 @@ jobs:
           command: check
 
   test:
+    if: github.event.pull_request.draft == false
     name: Test Suite
     runs-on: ubuntu-latest
     steps:
@@ -55,6 +55,7 @@ jobs:
           args: tests
 
   fmt:
+    if: github.event.pull_request.draft == false
     name: Rustfmt
     runs-on: ubuntu-latest
     steps:
@@ -72,6 +73,7 @@ jobs:
           args: --all -- --check
 
   clippy:
+    if: github.event.pull_request.draft == false
     name: Clippy
     runs-on: ubuntu-latest
     steps:
@@ -90,21 +92,23 @@ jobs:
           command: clippy
           args: -- -D warnings
 
-  coverage:
-    name: Coverage
-    runs-on: ubuntu-latest
-    container:
-      image: xd009642/tarpaulin:develop-nightly
-      options: --security-opt seccomp=unconfined
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v2
-      - name: Generate code coverage
-        run: |
-          apt-get update &&\
-          apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
-          cargo +nightly tarpaulin --verbose --tests --all-features --workspace --timeout 120 --out Xml
-      - name: Upload to codecov.io
-        uses: codecov/codecov-action@v2
-        with:
-          fail_ci_if_error: false
+  # Coverage is disabled until we can fix it
+  # coverage:
+  #   name: Coverage
+  #   runs-on: ubuntu-latest
+  #   container:
+  #     image: xd009642/tarpaulin:develop-nightly
+  #     options: --security-opt seccomp=unconfined
+  #   steps:
+  #     - name: Checkout repository
+  #       uses: actions/checkout@v2
+  #     - name: Generate code coverage
+  #       run: |
+  #         apt-get update &&\
+  #         apt-get install build-essential &&\
+  #         apt install libwebkit2gtk-4.0-dev libappindicator3-dev libgtk-3-dev -y &&\
+  #         cargo +nightly tarpaulin --verbose --all-features --workspace --timeout 120 --out Xml
+  #     - name: Upload to codecov.io
+  #       uses: codecov/codecov-action@v2
+  #       with:
+  #         fail_ci_if_error: false

+ 4 - 5
.github/workflows/windows.yml

@@ -1,7 +1,10 @@
 name: windows
 
 on:
-  push:
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+    branches:
+      - master
     paths:
       - packages/**
       - examples/**
@@ -9,10 +12,6 @@ on:
       - .github/**
       - lib.rs
       - Cargo.toml
-  pull_request:
-    types: [opened, synchronize, reopened, ready_for_review]
-    branches:
-      - master
 
 jobs:
   test:

+ 8 - 17
Cargo.toml

@@ -2,19 +2,22 @@
 name = "dioxus"
 version = "0.1.8"
 authors = ["Jonathan Kelley"]
-edition = "2018"
+edition = "2021"
 description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
 license = "MIT OR Apache-2.0"
 repository = "https://github.com/DioxusLabs/dioxus/"
 homepage = "https://dioxuslabs.com"
 documentation = "https://dioxuslabs.com"
 keywords = ["dom", "ui", "gui", "react", "wasm"]
+rust-version = "1.56.0"
 
 [dependencies]
 dioxus-core = { path = "./packages/core", version = "^0.1.9" }
 dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
 dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
 dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
+dioxus-rsx = { path = "./packages/rsx", optional = true }
+fermi = { path = "./packages/fermi", version = "^0.1.0", optional = true }
 
 dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
 dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
@@ -28,7 +31,7 @@ dioxus-interpreter-js = { path = "./packages/interpreter", version = "^0.0.0", o
 [features]
 default = ["macro", "hooks", "html"]
 
-macro = ["dioxus-core-macro"]
+macro = ["dioxus-core-macro", "dioxus-rsx"]
 hooks = ["dioxus-hooks"]
 html = ["dioxus-html"]
 ssr = ["dioxus-ssr"]
@@ -36,24 +39,11 @@ web = ["dioxus-web"]
 desktop = ["dioxus-desktop"]
 router = ["dioxus-router"]
 
-devtool = ["dioxus-desktop/devtool"]
-fullscreen = ["dioxus-desktop/fullscreen"]
-transparent = ["dioxus-desktop/transparent"]
-
-tray = ["dioxus-desktop/tray"]
-ayatana = ["dioxus-desktop/ayatana"]
-
-# "dioxus-router/web"
-# "dioxus-router/desktop"
-# desktop = ["dioxus-desktop", "dioxus-router/desktop"]
-# mobile = ["dioxus-mobile"]
-# liveview = ["dioxus-liveview"]
-
-
 [workspace]
 members = [
     "packages/core",
     "packages/core-macro",
+    "packages/rsx",
     "packages/html",
     "packages/hooks",
     "packages/web",
@@ -61,6 +51,7 @@ members = [
     "packages/desktop",
     "packages/mobile",
     "packages/interpreter",
+    "packages/fermi",
 ]
 
 [dev-dependencies]
@@ -75,4 +66,4 @@ serde_json = "1.0.79"
 rand = { version = "0.8.4", features = ["small_rng"] }
 tokio = { version = "1.16.1", features = ["full"] }
 reqwest = { version = "0.11.9", features = ["json"] }
-dioxus = { path = ".", features = ["desktop", "ssr", "router"] }
+dioxus = { path = ".", features = ["desktop", "ssr", "router", "fermi"] }

+ 1 - 0
docs/fermi/.gitignore

@@ -0,0 +1 @@
+book

+ 6 - 0
docs/fermi/book.toml

@@ -0,0 +1,6 @@
+[book]
+authors = ["Jonathan Kelley"]
+language = "en"
+multilingual = false
+src = "src"
+title = "Fermi Guide"

+ 3 - 0
docs/fermi/src/SUMMARY.md

@@ -0,0 +1,3 @@
+# Summary
+
+- [Chapter 1](./chapter_1.md)

+ 1 - 0
docs/fermi/src/chapter_1.md

@@ -0,0 +1 @@
+# Chapter 1

+ 12 - 12
docs/reference/src/platforms/ssr.md

@@ -2,11 +2,8 @@
 
 The Dioxus VirtualDom can be rendered to a string by traversing the Element Tree. This is implemented in the `ssr` crate where your Dioxus app can be directly rendered to HTML to be served by a web server.
 
-
-
 ## Setup
 
-
 If you just want to render `rsx!` or a VirtualDom to HTML, check out the API docs. It's pretty simple:
 
 ```rust
@@ -19,8 +16,7 @@ println!("{}", dioxus::ssr::render_vdom(&vdom));
 println!( "{}", dioxus::ssr::render_lazy(rsx! { h1 { "Hello, world!" } } );
 ```
 
-
-However, for this guide, we're going to show how to use Dioxus SSR with `Axum`. 
+However, for this guide, we're going to show how to use Dioxus SSR with `Axum`.
 
 Make sure you have Rust and Cargo installed, and then create a new project:
 
@@ -29,15 +25,16 @@ $ cargo new --bin demo
 $ cd app
 ```
 
-Add Dioxus with the `desktop` feature:
+Add Dioxus with the `ssr` feature:
 
 ```shell
 $ cargo add dioxus --features ssr
 ```
 
 Next, add all the Axum dependencies. This will be different if you're using a different Web Framework
+
 ```
-$ cargo add dioxus tokio --features full
+$ cargo add tokio --features full
 $ cargo add axum
 ```
 
@@ -45,12 +42,11 @@ Your dependencies should look roughly like this:
 
 ```toml
 [dependencies]
-axum = "0.4.3"
+axum = "0.4.5"
 dioxus = { version = "*", features = ["ssr"] }
 tokio = { version = "1.15.0", features = ["full"] }
 ```
 
-
 Now, setup your Axum app to respond on an endpoint.
 
 ```rust
@@ -63,7 +59,11 @@ async fn main() {
     println!("listening on http://{}", addr);
 
     axum::Server::bind(&addr)
-        .serve(Router::new().route("/", get(app_endpoint)))
+        .serve(
+            Router::new()
+                .route("/", get(app_endpoint))
+                .into_make_service(),
+        )
         .await
         .unwrap();
 }
@@ -88,14 +88,14 @@ async fn app_endpoint() -> Html<String> {
     }
     let mut app = VirtualDom::new(app);
     let _ = app.rebuild();
-    
+
     Html(dioxus::ssr::render_vdom(&app))
 }
 ```
 
 And that's it!
 
-> You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it *must* remain on the thread it started. We are working on loosening this requirement.
+> You might notice that you cannot hold the VirtualDom across an await point. Dioxus is currently not ThreadSafe, so it _must_ remain on the thread it started. We are working on loosening this requirement.
 
 ## Future Steps
 

+ 30 - 0
examples/fermi.rs

@@ -0,0 +1,30 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use fermi::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(app)
+}
+
+static NAME: Atom<String> = |_| "world".to_string();
+
+fn app(cx: Scope) -> Element {
+    let name = use_read(&cx, NAME);
+
+    cx.render(rsx! {
+        div { "hello {name}!" }
+        Child {}
+    })
+}
+
+fn Child(cx: Scope) -> Element {
+    let set_name = use_set(&cx, NAME);
+
+    cx.render(rsx! {
+        button {
+            onclick: move |_| set_name("dioxus".to_string()),
+            "reset name"
+        }
+    })
+}

+ 1 - 0
packages/core-macro/Cargo.toml

@@ -15,6 +15,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 proc-macro = true
 
 [dependencies]
+dioxus-rsx = { path = "../rsx" }
 proc-macro-error = "1.0.4"
 proc-macro2 = { version = "1.0.6" }
 quote = "1.0"

+ 0 - 443
packages/core-macro/src/htm.rs

@@ -1,443 +0,0 @@
-//!
-//! TODO:
-//! - [ ] Support for VComponents
-//! - [ ] Support for inline format in text
-//! - [ ] Support for expressions in attribute positions
-//! - [ ] Support for iterators
-//! - [ ] support for inline html!
-//!
-//!
-//!
-//!
-//!
-//!
-//!
-
-use {
-    proc_macro2::TokenStream as TokenStream2,
-    quote::{quote, ToTokens, TokenStreamExt},
-    syn::{
-        ext::IdentExt,
-        parse::{Parse, ParseStream},
-        token, Error, Expr, ExprClosure, Ident, LitStr, Result, Token,
-    },
-};
-
-// ==============================================
-// Parse any stream coming from the html! macro
-// ==============================================
-pub struct HtmlRender {
-    kind: NodeOrList,
-}
-
-impl Parse for HtmlRender {
-    fn parse(input: ParseStream) -> Result<Self> {
-        if input.peek(LitStr) {
-            return input.parse::<LitStr>()?.parse::<HtmlRender>();
-        }
-
-        // let __cx: Ident = s.parse()?;
-        // s.parse::<Token![,]>()?;
-        // if elements are in an array, return a bumpalo::collections::Vec rather than a Node.
-        let kind = if input.peek(token::Bracket) {
-            let nodes_toks;
-            syn::bracketed!(nodes_toks in input);
-            let mut nodes: Vec<MaybeExpr<Node>> = vec![nodes_toks.parse()?];
-            while nodes_toks.peek(Token![,]) {
-                nodes_toks.parse::<Token![,]>()?;
-                nodes.push(nodes_toks.parse()?);
-            }
-            NodeOrList::List(NodeList(nodes))
-        } else {
-            NodeOrList::Node(input.parse()?)
-        };
-        Ok(HtmlRender { kind })
-    }
-}
-
-impl ToTokens for HtmlRender {
-    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        let new_toks = ToToksCtx::new(&self.kind).to_token_stream();
-
-        // create a lazy tree that accepts a bump allocator
-        let final_tokens = quote! {
-            dioxus::prelude::LazyNodes::new(move |__cx| {
-                let bump = __cx.bump();
-
-                #new_toks
-            })
-        };
-
-        final_tokens.to_tokens(out_tokens);
-    }
-}
-
-/// =============================================
-/// Parse any child as a node or list of nodes
-/// =============================================
-/// - [ ] Allow iterators
-///
-///
-enum NodeOrList {
-    Node(Node),
-    List(NodeList),
-}
-
-impl ToTokens for ToToksCtx<&NodeOrList> {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        match self.inner {
-            NodeOrList::Node(node) => self.recurse(node).to_tokens(tokens),
-            NodeOrList::List(list) => self.recurse(list).to_tokens(tokens),
-        }
-    }
-}
-
-struct NodeList(Vec<MaybeExpr<Node>>);
-
-impl ToTokens for ToToksCtx<&NodeList> {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let nodes = self.inner.0.iter().map(|node| self.recurse(node));
-        tokens.append_all(quote! {
-            dioxus::bumpalo::vec![in bump;
-                #(#nodes),*
-            ]
-        });
-    }
-}
-
-enum Node {
-    Element(Element),
-    Text(TextNode),
-}
-
-impl ToTokens for ToToksCtx<&Node> {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        match &self.inner {
-            Node::Element(el) => self.recurse(el).to_tokens(tokens),
-            Node::Text(txt) => self.recurse(txt).to_tokens(tokens),
-        }
-    }
-}
-
-impl Node {
-    fn _peek(s: ParseStream) -> bool {
-        (s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr)
-    }
-}
-
-impl Parse for Node {
-    fn parse(s: ParseStream) -> Result<Self> {
-        Ok(if s.peek(Token![<]) {
-            Node::Element(s.parse()?)
-        } else {
-            Node::Text(s.parse()?)
-        })
-    }
-}
-
-/// =======================================
-/// Parse the VNode::Element type
-/// =======================================
-/// - [ ] Allow VComponent
-///
-///
-struct Element {
-    name: Ident,
-    attrs: Vec<Attr>,
-    children: MaybeExpr<Vec<Node>>,
-}
-
-impl ToTokens for ToToksCtx<&Element> {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        // let __cx = self.__cx;
-        let name = &self.inner.name;
-        // let name = &self.inner.name.to_string();
-        tokens.append_all(quote! {
-            __cx.element(dioxus_elements::#name)
-            // dioxus::builder::ElementBuilder::new( #name)
-        });
-        for attr in self.inner.attrs.iter() {
-            self.recurse(attr).to_tokens(tokens);
-        }
-
-        // if is_valid_svg_tag(&name.to_string()) {
-        //     tokens.append_all(quote! {
-        //         .namespace(Some("http://www.w3.org/2000/svg"))
-        //     });
-        // }
-
-        match &self.inner.children {
-            MaybeExpr::Expr(expr) => tokens.append_all(quote! {
-                .children(#expr)
-            }),
-            MaybeExpr::Literal(nodes) => {
-                let mut children = nodes.iter();
-                if let Some(child) = children.next() {
-                    let mut inner_toks = TokenStream2::new();
-                    self.recurse(child).to_tokens(&mut inner_toks);
-                    for child in children {
-                        quote!(,).to_tokens(&mut inner_toks);
-                        self.recurse(child).to_tokens(&mut inner_toks);
-                    }
-                    tokens.append_all(quote! {
-                        .children([#inner_toks])
-                    });
-                }
-            }
-        }
-        tokens.append_all(quote! {
-            .finish()
-        });
-    }
-}
-
-impl Parse for Element {
-    fn parse(s: ParseStream) -> Result<Self> {
-        s.parse::<Token![<]>()?;
-        let name = Ident::parse_any(s)?;
-        let mut attrs = vec![];
-        let _children: Vec<Node> = vec![];
-
-        // keep looking for attributes
-        while !s.peek(Token![>]) {
-            // self-closing
-            if s.peek(Token![/]) {
-                s.parse::<Token![/]>()?;
-                s.parse::<Token![>]>()?;
-                return Ok(Self {
-                    name,
-                    attrs,
-                    children: MaybeExpr::Literal(vec![]),
-                });
-            }
-            attrs.push(s.parse()?);
-        }
-        s.parse::<Token![>]>()?;
-
-        // Contents of an element can either be a brace (in which case we just copy verbatim), or a
-        // sequence of nodes.
-        let children = if s.peek(token::Brace) {
-            // expr
-            let content;
-            syn::braced!(content in s);
-            MaybeExpr::Expr(content.parse()?)
-        } else {
-            // nodes
-            let mut children = vec![];
-            while !(s.peek(Token![<]) && s.peek2(Token![/])) {
-                children.push(s.parse()?);
-            }
-            MaybeExpr::Literal(children)
-        };
-
-        // closing element
-        s.parse::<Token![<]>()?;
-        s.parse::<Token![/]>()?;
-        let close = Ident::parse_any(s)?;
-        if close != name {
-            return Err(Error::new_spanned(
-                close,
-                "closing element does not match opening",
-            ));
-        }
-        s.parse::<Token![>]>()?;
-
-        Ok(Self {
-            name,
-            attrs,
-            children,
-        })
-    }
-}
-
-/// =======================================
-/// Parse a VElement's Attributes
-/// =======================================
-/// - [ ] Allow expressions as attribute
-///
-///
-struct Attr {
-    name: Ident,
-    ty: AttrType,
-}
-
-impl Parse for Attr {
-    fn parse(s: ParseStream) -> Result<Self> {
-        let mut name = Ident::parse_any(s)?;
-        let name_str = name.to_string();
-        s.parse::<Token![=]>()?;
-
-        // Check if this is an event handler
-        // If so, parse into literal tokens
-        let ty = if name_str.starts_with("on") {
-            // remove the "on" bit
-            name = Ident::new(name_str.trim_start_matches("on"), name.span());
-            let content;
-            syn::braced!(content in s);
-            // AttrType::Value(content.parse()?)
-            AttrType::Event(content.parse()?)
-        // AttrType::Event(content.parse()?)
-        } else {
-            let lit_str = if name_str == "style" && s.peek(token::Brace) {
-                // special-case to deal with literal styles.
-                let outer;
-                syn::braced!(outer in s);
-                // double brace for inline style.
-                // todo!("Style support not ready yet");
-
-                // if outer.peek(token::Brace) {
-                //     let inner;
-                //     syn::braced!(inner in outer);
-                //     let styles: Styles = inner.parse()?;
-                //     MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
-                // } else {
-                // just parse as an expression
-                MaybeExpr::Expr(outer.parse()?)
-            // }
-            } else {
-                s.parse()?
-            };
-            AttrType::Value(lit_str)
-        };
-        Ok(Attr { name, ty })
-    }
-}
-
-impl ToTokens for ToToksCtx<&Attr> {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let name = self.inner.name.to_string();
-        let _attr_stream = TokenStream2::new();
-        match &self.inner.ty {
-            AttrType::Value(value) => {
-                let value = self.recurse(value);
-                if name == "xmlns" {
-                    tokens.append_all(quote! {
-                        .namespace(Some(#value))
-                    });
-                } else {
-                    tokens.append_all(quote! {
-                        .attr(#name, format_args_f!(#value))
-                    });
-                }
-            }
-            AttrType::Event(event) => {
-                tokens.append_all(quote! {
-                    .on(#name, #event)
-                });
-            }
-        }
-    }
-}
-
-enum AttrType {
-    Value(MaybeExpr<LitStr>),
-    Event(ExprClosure),
-    // todo Bool(MaybeExpr<LitBool>)
-}
-
-/// =======================================
-/// Parse just plain text
-/// =======================================
-/// - [ ] Perform formatting automatically
-///
-///
-struct TextNode(MaybeExpr<LitStr>);
-
-impl Parse for TextNode {
-    fn parse(s: ParseStream) -> Result<Self> {
-        Ok(Self(s.parse()?))
-    }
-}
-
-impl ToTokens for ToToksCtx<&TextNode> {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let mut token_stream = TokenStream2::new();
-        self.recurse(&self.inner.0).to_tokens(&mut token_stream);
-        tokens.append_all(quote! {
-            __cx.text(format_args_f!(#token_stream))
-        });
-    }
-}
-
-#[allow(clippy::large_enum_variant)]
-enum MaybeExpr<T> {
-    Literal(T),
-    Expr(Expr),
-}
-
-impl<T: Parse> Parse for MaybeExpr<T> {
-    fn parse(s: ParseStream) -> Result<Self> {
-        if s.peek(token::Brace) {
-            let content;
-            syn::braced!(content in s);
-            Ok(MaybeExpr::Expr(content.parse()?))
-        } else {
-            Ok(MaybeExpr::Literal(s.parse()?))
-        }
-    }
-}
-
-impl<'a, T> ToTokens for ToToksCtx<&'a MaybeExpr<T>>
-where
-    T: 'a,
-    ToToksCtx<&'a T>: ToTokens,
-{
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        match &self.inner {
-            MaybeExpr::Literal(v) => self.recurse(v).to_tokens(tokens),
-            MaybeExpr::Expr(expr) => expr.to_tokens(tokens),
-        }
-    }
-}
-
-/// ToTokens context
-struct ToToksCtx<T> {
-    inner: T,
-}
-
-impl<'a, T> ToToksCtx<T> {
-    fn new(inner: T) -> Self {
-        ToToksCtx { inner }
-    }
-
-    fn recurse<U>(&self, inner: U) -> ToToksCtx<U> {
-        ToToksCtx { inner }
-    }
-}
-
-impl ToTokens for ToToksCtx<&LitStr> {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        self.inner.to_tokens(tokens)
-    }
-}
-
-#[cfg(test)]
-mod test {
-    fn parse(input: &str) -> super::Result<super::HtmlRender> {
-        syn::parse_str(input)
-    }
-
-    #[test]
-    fn div() {
-        parse("bump, <div class=\"test\"/>").unwrap();
-    }
-
-    #[test]
-    fn nested() {
-        parse("bump, <div class=\"test\"><div />\"text\"</div>").unwrap();
-    }
-
-    #[test]
-    fn complex() {
-        parse(
-            "bump,
-            <section style={{
-                display: flex;
-                flex-direction: column;
-                max-width: 95%;
-            }} class=\"map-panel\">{contact_details}</section>
-        ",
-        )
-        .unwrap();
-    }
-}

+ 4 - 36
packages/core-macro/src/lib.rs

@@ -2,11 +2,9 @@ use proc_macro::TokenStream;
 use quote::ToTokens;
 use syn::parse_macro_input;
 
-pub(crate) mod ifmt;
-pub(crate) mod inlineprops;
-pub(crate) mod props;
-pub(crate) mod router;
-pub(crate) mod rsx;
+mod ifmt;
+mod inlineprops;
+mod props;
 
 #[proc_macro]
 pub fn format_args_f(input: TokenStream) -> TokenStream {
@@ -180,42 +178,12 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
 #[proc_macro_error::proc_macro_error]
 #[proc_macro]
 pub fn rsx(s: TokenStream) -> TokenStream {
-    match syn::parse::<rsx::CallBody>(s) {
+    match syn::parse::<dioxus_rsx::CallBody>(s) {
         Err(err) => err.to_compile_error().into(),
         Ok(stream) => stream.to_token_stream().into(),
     }
 }
 
-/// Derive macro used to mark an enum as Routable.
-///
-/// This macro can only be used on enums. Every varient of the macro needs to be marked
-/// with the `at` attribute to specify the URL of the route. It generates an implementation of
-///  `yew_router::Routable` trait and `const`s for the routes passed which are used with `Route`
-/// component.
-///
-/// # Example
-///
-/// ```
-/// # use yew_router::Routable;
-/// #[derive(Debug, Clone, Copy, PartialEq, Routable)]
-/// enum Routes {
-///     #[at("/")]
-///     Home,
-///     #[at("/secure")]
-///     Secure,
-///     #[at("/profile/{id}")]
-///     Profile(u32),
-///     #[at("/404")]
-///     NotFound,
-/// }
-/// ```
-#[proc_macro_derive(Routable, attributes(at, not_found))]
-pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    use router::{routable_derive_impl, Routable};
-    let input = parse_macro_input!(input as Routable);
-    routable_derive_impl(input).into()
-}
-
 /// Derive props for a component within the component definition.
 ///
 /// This macro provides a simple transformation from `Scope<{}>` to `Scope<P>`,

+ 0 - 214
packages/core-macro/src/router.rs

@@ -1,214 +0,0 @@
-#![allow(dead_code)]
-
-use proc_macro2::TokenStream;
-use quote::quote;
-use syn::parse::{Parse, ParseStream};
-use syn::punctuated::Punctuated;
-use syn::spanned::Spanned;
-use syn::{Data, DeriveInput, Fields, Ident, LitStr, Variant};
-
-const AT_ATTR_IDENT: &str = "at";
-const NOT_FOUND_ATTR_IDENT: &str = "not_found";
-
-pub struct Routable {
-    ident: Ident,
-    ats: Vec<LitStr>,
-    variants: Punctuated<Variant, syn::token::Comma>,
-    not_found_route: Option<Ident>,
-}
-
-impl Parse for Routable {
-    fn parse(input: ParseStream) -> syn::Result<Self> {
-        let DeriveInput { ident, data, .. } = input.parse()?;
-
-        let data = match data {
-            Data::Enum(data) => data,
-            Data::Struct(s) => {
-                return Err(syn::Error::new(
-                    s.struct_token.span(),
-                    "expected enum, found struct",
-                ))
-            }
-            Data::Union(u) => {
-                return Err(syn::Error::new(
-                    u.union_token.span(),
-                    "expected enum, found union",
-                ))
-            }
-        };
-
-        let (not_found_route, ats) = parse_variants_attributes(&data.variants)?;
-
-        Ok(Self {
-            ident,
-            variants: data.variants,
-            ats,
-            not_found_route,
-        })
-    }
-}
-
-fn parse_variants_attributes(
-    variants: &Punctuated<Variant, syn::token::Comma>,
-) -> syn::Result<(Option<Ident>, Vec<LitStr>)> {
-    let mut not_founds = vec![];
-    let mut ats: Vec<LitStr> = vec![];
-
-    let mut not_found_attrs = vec![];
-
-    for variant in variants.iter() {
-        if let Fields::Unnamed(ref field) = variant.fields {
-            return Err(syn::Error::new(
-                field.span(),
-                "only named fields are supported",
-            ));
-        }
-
-        let attrs = &variant.attrs;
-        let at_attrs = attrs
-            .iter()
-            .filter(|attr| attr.path.is_ident(AT_ATTR_IDENT))
-            .collect::<Vec<_>>();
-
-        let attr = match at_attrs.len() {
-            1 => *at_attrs.first().unwrap(),
-            0 => {
-                return Err(syn::Error::new(
-                    variant.span(),
-                    format!(
-                        "{} attribute must be present on every variant",
-                        AT_ATTR_IDENT
-                    ),
-                ))
-            }
-            _ => {
-                return Err(syn::Error::new_spanned(
-                    quote! { #(#at_attrs)* },
-                    format!("only one {} attribute must be present", AT_ATTR_IDENT),
-                ))
-            }
-        };
-
-        let lit = attr.parse_args::<LitStr>()?;
-        ats.push(lit);
-
-        for attr in attrs.iter() {
-            if attr.path.is_ident(NOT_FOUND_ATTR_IDENT) {
-                not_found_attrs.push(attr);
-                not_founds.push(variant.ident.clone())
-            }
-        }
-    }
-
-    if not_founds.len() > 1 {
-        return Err(syn::Error::new_spanned(
-            quote! { #(#not_found_attrs)* },
-            format!("there can only be one {}", NOT_FOUND_ATTR_IDENT),
-        ));
-    }
-
-    Ok((not_founds.into_iter().next(), ats))
-}
-
-impl Routable {
-    // fn build_from_path(&self) -> TokenStream {
-    //     let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
-    //         let ident = &variant.ident;
-    //         let right = match &variant.fields {
-    //             Fields::Unit => quote! { Self::#ident },
-    //             Fields::Named(field) => {
-    //                 let fields = field.named.iter().map(|it| {
-    //                     //named fields have idents
-    //                     it.ident.as_ref().unwrap()
-    //                 });
-    //                 quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } }
-    //             }
-    //             Fields::Unnamed(_) => unreachable!(), // already checked
-    //         };
-
-    //         let left = self.ats.get(i).unwrap();
-    //         quote! {
-    //             #left => ::std::option::Option::Some(#right)
-    //         }
-    //     });
-
-    //     quote! {
-    //         fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option<Self> {
-    //             match path {
-    //                 #(#from_path_matches),*,
-    //                 _ => ::std::option::Option::None,
-    //             }
-    //         }
-    //     }
-    // }
-
-    // fn build_to_path(&self) -> TokenStream {
-    //     let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
-    //         let ident = &variant.ident;
-    //         let mut right = self.ats.get(i).unwrap().value();
-
-    //         match &variant.fields {
-    //             Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) },
-    //             Fields::Named(field) => {
-    //                 let fields = field
-    //                     .named
-    //                     .iter()
-    //                     .map(|it| it.ident.as_ref().unwrap())
-    //                     .collect::<Vec<_>>();
-
-    //                 for field in fields.iter() {
-    //                     // :param -> {param}
-    //                     // so we can pass it to `format!("...", param)`
-    //                     right = right.replace(&format!(":{}", field), &format!("{{{}}}", field))
-    //                 }
-
-    //                 quote! {
-    //                     Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*)
-    //                 }
-    //             }
-    //             Fields::Unnamed(_) => unreachable!(), // already checked
-    //         }
-    //     });
-
-    //     quote! {
-    //         fn to_path(&self) -> ::std::string::String {
-    //             match self {
-    //                 #(#to_path_matches),*,
-    //             }
-    //         }
-    //     }
-    // }
-}
-
-pub fn routable_derive_impl(input: Routable) -> TokenStream {
-    let Routable {
-        // ats,
-        // not_found_route,
-        // ident,
-        ..
-    } = &input;
-
-    // let from_path = input.build_from_path();
-    // let to_path = input.build_to_path();
-
-    quote! {
-        // #[automatically_derived]
-        // impl ::dioxus::router::Routable for #ident {
-
-        //     fn recognize(pathname: &str) -> ::std::option::Option<Self> {
-        //         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
-        //     }
-        // }
-    }
-}

+ 0 - 84
packages/core-macro/src/rsxtemplate.rs

@@ -1,84 +0,0 @@
-use crate::{rsx::RsxBody, util::is_valid_svg_tag};
-
-use {
-    proc_macro::TokenStream,
-    proc_macro2::{Span, TokenStream as TokenStream2},
-    quote::{quote, ToTokens, TokenStreamExt},
-    syn::{
-        ext::IdentExt,
-        parse::{Parse, ParseStream},
-        token, Error, Expr, ExprClosure, Ident, LitBool, LitStr, Path, Result, Token,
-    },
-};
-
-// ==============================================
-// Parse any stream coming from the html! macro
-// ==============================================
-pub struct RsxTemplate {
-    inner: RsxBody,
-}
-
-impl Parse for RsxTemplate {
-    fn parse(s: ParseStream) -> Result<Self> {
-        if s.peek(LitStr) {
-            use std::str::FromStr;
-
-            let lit = s.parse::<LitStr>()?;
-            let g = lit.span();
-            let mut value = lit.value();
-            if value.ends_with('\n') {
-                value.pop();
-                if value.ends_with('\r') {
-                    value.pop();
-                }
-            }
-            let lit = LitStr::new(&value, lit.span());
-
-            // panic!("{:#?}", lit);
-            match lit.parse::<crate::rsx::RsxBody>() {
-                Ok(r) => Ok(Self { inner: r }),
-                Err(e) => Err(e),
-            }
-        } else {
-            panic!("Not a str lit")
-        }
-        // let t = s.parse::<LitStr>()?;
-
-        // let new_stream = TokenStream::from(t.to_s)
-
-        // let cx: Ident = s.parse()?;
-        // s.parse::<Token![,]>()?;
-        // if elements are in an array, return a bumpalo::collections::Vec rather than a Node.
-        // let kind = if s.peek(token::Bracket) {
-        //     let nodes_toks;
-        //     syn::bracketed!(nodes_toks in s);
-        //     let mut nodes: Vec<MaybeExpr<Node>> = vec![nodes_toks.parse()?];
-        //     while nodes_toks.peek(Token![,]) {
-        //         nodes_toks.parse::<Token![,]>()?;
-        //         nodes.push(nodes_toks.parse()?);
-        //     }
-        //     NodeOrList::List(NodeList(nodes))
-        // } else {
-        //     NodeOrList::Node(s.parse()?)
-        // };
-        // Ok(HtmlRender { kind })
-    }
-}
-
-impl ToTokens for RsxTemplate {
-    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        self.inner.to_tokens(out_tokens);
-        // let new_toks = ToToksCtx::new(&self.kind).to_token_stream();
-
-        // // create a lazy tree that accepts a bump allocator
-        // let final_tokens = quote! {
-        //     dioxus::prelude::LazyNodes::new(move |cx| {
-        //         let bump = &cx.bump();
-
-        //         #new_toks
-        //     })
-        // };
-
-        // final_tokens.to_tokens(out_tokens);
-    }
-}

+ 1 - 1
packages/desktop/src/events.rs

@@ -26,7 +26,7 @@ pub(crate) fn parse_ipc_message(payload: &str) -> Option<IpcMessage> {
     match serde_json::from_str(payload) {
         Ok(message) => Some(message),
         Err(e) => {
-            log::error!("could not parse IPC message, error: {e}");
+            log::error!("could not parse IPC message, error: {}", e);
             None
         }
     }

+ 4 - 0
packages/desktop/src/lib.rs

@@ -173,6 +173,10 @@ pub fn launch_with_props<P: 'static + Send>(
                     webview = webview.with_custom_protocol(name, handler)
                 }
 
+                if cfg!(debug_assertions) {
+                    webview = webview.with_dev_tool(true);
+                }
+
                 desktop.webviews.insert(window_id, webview.build().unwrap());
             }
 

+ 14 - 0
packages/fermi/Cargo.toml

@@ -0,0 +1,14 @@
+[package]
+name = "fermi"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+dioxus-core = { path = "../core" }
+im-rc = { version = "15.0.0", features = ["serde"] }
+log = "0.4.14"
+
+[dev-dependencies]
+closure = "0.3.0"

+ 92 - 0
packages/fermi/README.md

@@ -0,0 +1,92 @@
+
+<div align="center">
+  <h1>Fermi ⚛</h1>
+  <p>
+    <strong>Atom-based global state management solution for Dioxus</strong>
+  </p>
+</div>
+
+
+<div align="center">
+  <!-- Crates version -->
+  <a href="https://crates.io/crates/dioxus">
+    <img src="https://img.shields.io/crates/v/dioxus.svg?style=flat-square"
+    alt="Crates.io version" />
+  </a>
+  <!-- Downloads -->
+  <a href="https://crates.io/crates/dioxus">
+    <img src="https://img.shields.io/crates/d/dioxus.svg?style=flat-square"
+      alt="Download" />
+  </a>
+  <!-- docs -->
+  <a href="https://docs.rs/dioxus">
+    <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
+      alt="docs.rs docs" />
+  </a>
+  <!-- CI -->
+  <a href="https://github.com/jkelleyrtp/dioxus/actions">
+    <img src="https://github.com/dioxuslabs/dioxus/actions/workflows/main.yml/badge.svg"
+      alt="CI status" />
+  </a>
+</div>
+
+-----
+
+Fermi is a global state management solution for Dioxus that's as easy as `use_state`.
+
+Inspired by atom-based state management solutions, all state in Fermi starts as an `atom`:
+
+```rust
+static NAME: Atom<&str> = |_| "Dioxus";
+```
+
+From anywhere in our app, we can read our the value of our atom:
+
+```rust
+fn NameCard(cx: Scope) -> Element {      
+    let name = use_read(&cx, NAME);
+    cx.render(rsx!{ h1 { "Hello, {name}"} })
+}
+```
+
+We can also set the value of our atom, also from anywhere in our app:
+
+```rust
+fn NameCard(cx: Scope) -> Element {      
+    let set_name = use_set(&cx, NAME);
+    cx.render(rsx!{
+        button {
+            onclick: move |_| set_name("Fermi"),
+            "Set name to fermi"
+        }
+    })
+}
+```
+
+It's that simple!
+
+## Installation
+Fermi is currently under construction, so you have to use the `master` branch to get started.
+
+```rust
+[depdencies]
+fermi = { git = "https://github.com/dioxuslabs/fermi" }
+```
+
+
+## Running examples
+
+The examples here use Dioxus Desktop to showcase their functionality. To run an example, use
+```
+$ cargo run --example EXAMPLE
+```
+
+## Features
+
+Broadly our feature set to required to be released includes:
+- [x] Support for Atoms
+- [x] Support for AtomRef (for values that aren't clone)
+- [ ] Support for Atom Families
+- [ ] Support for memoized Selectors
+- [ ] Support for memoized SelectorFamilies
+- [ ] Support for UseFermiCallback for access to fermi from async 

+ 28 - 0
packages/fermi/src/atoms/atom.rs

@@ -0,0 +1,28 @@
+use crate::{AtomId, AtomRoot, Readable, Writable};
+
+pub type Atom<T> = fn(AtomBuilder) -> T;
+pub struct AtomBuilder;
+
+impl<V> Readable<V> for Atom<V> {
+    fn read(&self, _root: AtomRoot) -> Option<V> {
+        todo!()
+    }
+    fn init(&self) -> V {
+        (*self)(AtomBuilder)
+    }
+    fn unique_id(&self) -> AtomId {
+        *self as *const ()
+    }
+}
+
+impl<V> Writable<V> for Atom<V> {
+    fn write(&self, _root: AtomRoot, _value: V) {
+        todo!()
+    }
+}
+
+#[test]
+fn atom_compiles() {
+    static TEST_ATOM: Atom<&str> = |_| "hello";
+    dbg!(TEST_ATOM.init());
+}

+ 25 - 0
packages/fermi/src/atoms/atomfamily.rs

@@ -0,0 +1,25 @@
+use crate::{AtomId, AtomRoot, Readable, Writable};
+use im_rc::HashMap as ImMap;
+
+pub struct AtomFamilyBuilder;
+pub type AtomFamily<K, V> = fn(AtomFamilyBuilder) -> ImMap<K, V>;
+
+impl<K, V> Readable<ImMap<K, V>> for AtomFamily<K, V> {
+    fn read(&self, _root: AtomRoot) -> Option<ImMap<K, V>> {
+        todo!()
+    }
+
+    fn init(&self) -> ImMap<K, V> {
+        (*self)(AtomFamilyBuilder)
+    }
+
+    fn unique_id(&self) -> AtomId {
+        *self as *const ()
+    }
+}
+
+impl<K, V> Writable<ImMap<K, V>> for AtomFamily<K, V> {
+    fn write(&self, _root: AtomRoot, _value: ImMap<K, V>) {
+        todo!()
+    }
+}

+ 25 - 0
packages/fermi/src/atoms/atomref.rs

@@ -0,0 +1,25 @@
+use crate::{AtomId, AtomRoot, Readable};
+use std::cell::RefCell;
+
+pub struct AtomRefBuilder;
+pub type AtomRef<T> = fn(AtomRefBuilder) -> T;
+
+impl<V> Readable<RefCell<V>> for AtomRef<V> {
+    fn read(&self, _root: AtomRoot) -> Option<RefCell<V>> {
+        todo!()
+    }
+
+    fn init(&self) -> RefCell<V> {
+        RefCell::new((*self)(AtomRefBuilder))
+    }
+
+    fn unique_id(&self) -> AtomId {
+        *self as *const ()
+    }
+}
+
+#[test]
+fn atom_compiles() {
+    static TEST_ATOM: AtomRef<Vec<String>> = |_| vec![];
+    dbg!(TEST_ATOM.init());
+}

+ 1 - 0
packages/fermi/src/atoms/selector.rs

@@ -0,0 +1 @@
+

+ 1 - 0
packages/fermi/src/atoms/selectorfamily.rs

@@ -0,0 +1 @@
+

+ 53 - 0
packages/fermi/src/callback.rs

@@ -0,0 +1,53 @@
+#![allow(clippy::all, unused)]
+
+use std::rc::Rc;
+
+use dioxus_core::prelude::*;
+
+use crate::{AtomRoot, Readable, Writable};
+
+#[derive(Clone)]
+pub struct CallbackApi {
+    root: Rc<AtomRoot>,
+}
+
+impl CallbackApi {
+    // get the current value of the atom
+    pub fn get<V>(&self, atom: impl Readable<V>) -> &V {
+        todo!()
+    }
+
+    // get the current value of the atom in its RC container
+    pub fn get_rc<V>(&self, atom: impl Readable<V>) -> &Rc<V> {
+        todo!()
+    }
+
+    // set the current value of the atom
+    pub fn set<V>(&self, atom: impl Writable<V>, value: V) {
+        todo!()
+    }
+}
+
+pub fn use_atom_context(cx: &ScopeState) -> &CallbackApi {
+    todo!()
+}
+
+macro_rules! use_callback {
+    (&$cx:ident, [$($cap:ident),*],  move || $body:expr) => {
+        move || {
+            $(
+                #[allow(unused_mut)]
+                let mut $cap = $cap.to_owned();
+            )*
+            $cx.spawn($body);
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! to_owned {
+    ($($es:ident),+) => {$(
+        #[allow(unused_mut)]
+        let mut $es = $es.to_owned();
+    )*}
+}

+ 62 - 0
packages/fermi/src/hooks/atom_ref.rs

@@ -0,0 +1,62 @@
+use crate::{use_atom_root, AtomId, AtomRef, AtomRoot, Readable};
+use dioxus_core::{ScopeId, ScopeState};
+use std::{
+    cell::{Ref, RefCell, RefMut},
+    rc::Rc,
+};
+
+///
+///
+///
+///
+///
+///
+///
+///
+pub fn use_atom_ref<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
+    let root = use_atom_root(cx);
+
+    cx.use_hook(|_| {
+        root.initialize(atom);
+        UseAtomRef {
+            ptr: atom.unique_id(),
+            root: root.clone(),
+            scope_id: cx.scope_id(),
+            value: root.register(atom, cx.scope_id()),
+        }
+    })
+}
+
+pub struct UseAtomRef<T> {
+    ptr: AtomId,
+    value: Rc<RefCell<T>>,
+    root: Rc<AtomRoot>,
+    scope_id: ScopeId,
+}
+
+impl<T: 'static> UseAtomRef<T> {
+    pub fn read(&self) -> Ref<T> {
+        self.value.borrow()
+    }
+
+    pub fn write(&self) -> RefMut<T> {
+        self.root.force_update(self.ptr);
+        self.value.borrow_mut()
+    }
+
+    pub fn write_silent(&self) -> RefMut<T> {
+        self.root.force_update(self.ptr);
+        self.value.borrow_mut()
+    }
+
+    pub fn set(&self, new: T) {
+        self.root.force_update(self.ptr);
+        self.root.set(self.ptr, new);
+    }
+}
+
+impl<T> Drop for UseAtomRef<T> {
+    fn drop(&mut self) {
+        self.root.unsubscribe(self.ptr, self.scope_id)
+    }
+}

+ 11 - 0
packages/fermi/src/hooks/atom_root.rs

@@ -0,0 +1,11 @@
+use crate::AtomRoot;
+use dioxus_core::ScopeState;
+use std::rc::Rc;
+
+// Returns the atom root, initiaizing it at the root of the app if it does not exist.
+pub fn use_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
+    cx.use_hook(|_| match cx.consume_context::<AtomRoot>() {
+        Some(root) => root,
+        None => cx.provide_root_context(AtomRoot::new(cx.schedule_update_any())),
+    })
+}

+ 11 - 0
packages/fermi/src/hooks/init_atom_root.rs

@@ -0,0 +1,11 @@
+use crate::AtomRoot;
+use dioxus_core::ScopeState;
+use std::rc::Rc;
+
+// Initializes the atom root and retuns it;
+pub fn use_init_atom_root(cx: &ScopeState) -> &Rc<AtomRoot> {
+    cx.use_hook(|_| match cx.consume_context::<AtomRoot>() {
+        Some(ctx) => ctx,
+        None => cx.provide_context(AtomRoot::new(cx.schedule_update_any())),
+    })
+}

+ 36 - 0
packages/fermi/src/hooks/read.rs

@@ -0,0 +1,36 @@
+use crate::{use_atom_root, AtomId, AtomRoot, Readable};
+use dioxus_core::{ScopeId, ScopeState};
+use std::rc::Rc;
+
+pub fn use_read<'a, V: 'static>(cx: &'a ScopeState, f: impl Readable<V>) -> &'a V {
+    use_read_rc(cx, f).as_ref()
+}
+
+pub fn use_read_rc<'a, V: 'static>(cx: &'a ScopeState, f: impl Readable<V>) -> &'a Rc<V> {
+    let root = use_atom_root(cx);
+
+    struct UseReadInner<V> {
+        root: Rc<AtomRoot>,
+        id: AtomId,
+        scope_id: ScopeId,
+        value: Option<Rc<V>>,
+    }
+
+    impl<V> Drop for UseReadInner<V> {
+        fn drop(&mut self) {
+            self.root.unsubscribe(self.id, self.scope_id)
+        }
+    }
+
+    let inner = cx.use_hook(|_| UseReadInner {
+        value: None,
+        root: root.clone(),
+        scope_id: cx.scope_id(),
+        id: f.unique_id(),
+    });
+
+    let value = inner.root.register(f, cx.scope_id());
+
+    inner.value = Some(value);
+    inner.value.as_ref().unwrap()
+}

+ 13 - 0
packages/fermi/src/hooks/set.rs

@@ -0,0 +1,13 @@
+use crate::{use_atom_root, Writable};
+use dioxus_core::ScopeState;
+use std::rc::Rc;
+
+pub fn use_set<'a, T: 'static>(cx: &'a ScopeState, f: impl Writable<T>) -> &'a Rc<dyn Fn(T)> {
+    let root = use_atom_root(cx);
+    cx.use_hook(|_| {
+        let id = f.unique_id();
+        let root = root.clone();
+        root.initialize(f);
+        Rc::new(move |new| root.set(id, new)) as Rc<dyn Fn(T)>
+    })
+}

+ 58 - 0
packages/fermi/src/lib.rs

@@ -0,0 +1,58 @@
+#![doc = include_str!("../README.md")]
+
+pub mod prelude {
+    pub use crate::*;
+}
+
+mod callback;
+mod root;
+
+pub use atoms::*;
+pub use callback::*;
+pub use hooks::*;
+pub use root::*;
+
+mod atoms {
+    mod atom;
+    mod atomfamily;
+    mod atomref;
+    mod selector;
+    mod selectorfamily;
+
+    pub use atom::*;
+    pub use atomfamily::*;
+    pub use atomref::*;
+    pub use selector::*;
+    pub use selectorfamily::*;
+}
+
+pub mod hooks {
+    mod atom_ref;
+    mod atom_root;
+    mod init_atom_root;
+    mod read;
+    mod set;
+    pub use atom_ref::*;
+    pub use atom_root::*;
+    pub use init_atom_root::*;
+    pub use read::*;
+    pub use set::*;
+}
+
+/// All Atoms are `Readable` - they support reading their value.
+///
+/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors.
+/// It is not very useful for your own code, but could be used to build new Atom primitives.
+pub trait Readable<V> {
+    fn read(&self, root: AtomRoot) -> Option<V>;
+    fn init(&self) -> V;
+    fn unique_id(&self) -> AtomId;
+}
+
+/// All Atoms are `Writable` - they support writing their value.
+///
+/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors.
+/// This trait lets Dioxus abstract over Atoms, AtomFamilies, AtomRefs, and Selectors
+pub trait Writable<V>: Readable<V> {
+    fn write(&self, root: AtomRoot, value: V);
+}

+ 103 - 0
packages/fermi/src/root.rs

@@ -0,0 +1,103 @@
+use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc};
+
+use dioxus_core::ScopeId;
+use im_rc::HashSet;
+
+use crate::Readable;
+
+pub type AtomId = *const ();
+
+pub struct AtomRoot {
+    pub atoms: RefCell<HashMap<AtomId, Slot>>,
+    pub update_any: Rc<dyn Fn(ScopeId)>,
+}
+
+pub struct Slot {
+    pub value: Rc<dyn Any>,
+    pub subscribers: HashSet<ScopeId>,
+}
+
+impl AtomRoot {
+    pub fn new(update_any: Rc<dyn Fn(ScopeId)>) -> Self {
+        Self {
+            update_any,
+            atoms: RefCell::new(HashMap::new()),
+        }
+    }
+
+    pub fn initialize<V: 'static>(&self, f: impl Readable<V>) {
+        let id = f.unique_id();
+        if self.atoms.borrow().get(&id).is_none() {
+            self.atoms.borrow_mut().insert(
+                id,
+                Slot {
+                    value: Rc::new(f.init()),
+                    subscribers: HashSet::new(),
+                },
+            );
+        }
+    }
+
+    pub fn register<V: 'static>(&self, f: impl Readable<V>, scope: ScopeId) -> Rc<V> {
+        log::trace!("registering atom {:?}", f.unique_id());
+
+        let mut atoms = self.atoms.borrow_mut();
+
+        // initialize the value if it's not already initialized
+        if let Some(slot) = atoms.get_mut(&f.unique_id()) {
+            slot.subscribers.insert(scope);
+            slot.value.clone().downcast().unwrap()
+        } else {
+            let value = Rc::new(f.init());
+            let mut subscribers = HashSet::new();
+            subscribers.insert(scope);
+
+            atoms.insert(
+                f.unique_id(),
+                Slot {
+                    value: value.clone(),
+                    subscribers,
+                },
+            );
+            value
+        }
+    }
+
+    pub fn set<V: 'static>(&self, ptr: AtomId, value: V) {
+        let mut atoms = self.atoms.borrow_mut();
+
+        if let Some(slot) = atoms.get_mut(&ptr) {
+            slot.value = Rc::new(value);
+            log::trace!("found item with subscribers {:?}", slot.subscribers);
+
+            for scope in &slot.subscribers {
+                log::trace!("updating subcsriber");
+                (self.update_any)(*scope);
+            }
+        } else {
+            log::trace!("no atoms found for {:?}", ptr);
+        }
+    }
+
+    pub fn unsubscribe(&self, ptr: AtomId, scope: ScopeId) {
+        let mut atoms = self.atoms.borrow_mut();
+
+        if let Some(slot) = atoms.get_mut(&ptr) {
+            slot.subscribers.remove(&scope);
+        }
+    }
+
+    // force update of all subscribers
+    pub fn force_update(&self, ptr: AtomId) {
+        if let Some(slot) = self.atoms.borrow_mut().get(&ptr) {
+            for scope in slot.subscribers.iter() {
+                log::trace!("updating subcsriber");
+                (self.update_any)(*scope);
+            }
+        }
+    }
+
+    pub fn read<V>(&self, _f: impl Readable<V>) -> &V {
+        todo!()
+    }
+}

+ 13 - 0
packages/rsx/Cargo.toml

@@ -0,0 +1,13 @@
+[package]
+name = "dioxus-rsx"
+version = "0.0.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+once_cell = "1.8"
+proc-macro-error = "1.0.4"
+proc-macro2 = { version = "1.0.6" }
+quote = "1.0"
+syn = { version = "1.0.11", features = ["full", "extra-traits"] }

+ 4 - 4
packages/core-macro/src/rsx/component.rs → packages/rsx/src/component.rs

@@ -23,10 +23,10 @@ use syn::{
 };
 
 pub struct Component {
-    name: syn::Path,
-    body: Vec<ComponentField>,
-    children: Vec<BodyNode>,
-    manual_props: Option<Expr>,
+    pub name: syn::Path,
+    pub body: Vec<ComponentField>,
+    pub children: Vec<BodyNode>,
+    pub manual_props: Option<Expr>,
 }
 
 impl Parse for Component {

+ 27 - 23
packages/core-macro/src/rsx/element.rs → packages/rsx/src/element.rs

@@ -11,12 +11,11 @@ use syn::{
 // Parse the VNode::Element type
 // =======================================
 pub struct Element {
-    name: Ident,
-    key: Option<LitStr>,
-    attributes: Vec<ElementAttrNamed>,
-    listeners: Vec<ElementAttrNamed>,
-    children: Vec<BodyNode>,
-    _is_static: bool,
+    pub name: Ident,
+    pub key: Option<LitStr>,
+    pub attributes: Vec<ElementAttrNamed>,
+    pub children: Vec<BodyNode>,
+    pub _is_static: bool,
 }
 
 impl Parse for Element {
@@ -28,7 +27,6 @@ impl Parse for Element {
         syn::braced!(content in stream);
 
         let mut attributes: Vec<ElementAttrNamed> = vec![];
-        let mut listeners: Vec<ElementAttrNamed> = vec![];
         let mut children: Vec<BodyNode> = vec![];
         let mut key = None;
         let mut _el_ref = None;
@@ -54,6 +52,7 @@ impl Parse for Element {
                     });
                 } else {
                     let value = content.parse::<Expr>()?;
+
                     attributes.push(ElementAttrNamed {
                         el_name: el_name.clone(),
                         attr: ElementAttr::CustomAttrExpression { name, value },
@@ -82,7 +81,7 @@ impl Parse for Element {
                 content.parse::<Token![:]>()?;
 
                 if name_str.starts_with("on") {
-                    listeners.push(ElementAttrNamed {
+                    attributes.push(ElementAttrNamed {
                         el_name: el_name.clone(),
                         attr: ElementAttr::EventTokens {
                             name,
@@ -182,7 +181,6 @@ impl Parse for Element {
             name: el_name,
             attributes,
             children,
-            listeners,
             _is_static: false,
         })
     }
@@ -193,14 +191,21 @@ impl ToTokens for Element {
         let name = &self.name;
         let children = &self.children;
 
-        let listeners = &self.listeners;
-        let attr = &self.attributes;
-
         let key = match &self.key {
             Some(ty) => quote! { Some(format_args_f!(#ty)) },
             None => quote! { None },
         };
 
+        let listeners = self
+            .attributes
+            .iter()
+            .filter(|f| matches!(f.attr, ElementAttr::EventTokens { .. }));
+
+        let attr = self
+            .attributes
+            .iter()
+            .filter(|f| !matches!(f.attr, ElementAttr::EventTokens { .. }));
+
         tokens.append_all(quote! {
             __cx.element(
                 dioxus_elements::#name,
@@ -213,29 +218,28 @@ impl ToTokens for Element {
     }
 }
 
-enum ElementAttr {
-    // attribute: "valuee {}"
+pub enum ElementAttr {
+    /// attribute: "valuee {}"
     AttrText { name: Ident, value: LitStr },
 
-    // attribute: true,
+    /// attribute: true,
     AttrExpression { name: Ident, value: Expr },
 
-    // "attribute": "value {}"
+    /// "attribute": "value {}"
     CustomAttrText { name: LitStr, value: LitStr },
 
-    // "attribute": true,
+    /// "attribute": true,
     CustomAttrExpression { name: LitStr, value: Expr },
 
-    // // onclick: move |_| {}
+    // /// onclick: move |_| {}
     // EventClosure { name: Ident, closure: ExprClosure },
-
-    // onclick: {}
+    /// onclick: {}
     EventTokens { name: Ident, tokens: Expr },
 }
 
-struct ElementAttrNamed {
-    el_name: Ident,
-    attr: ElementAttr,
+pub struct ElementAttrNamed {
+    pub el_name: Ident,
+    pub attr: ElementAttr,
 }
 
 impl ToTokens for ElementAttrNamed {

+ 4 - 2
packages/core-macro/src/rsx/mod.rs → packages/rsx/src/lib.rs

@@ -15,6 +15,8 @@ mod component;
 mod element;
 mod node;
 
+pub mod pretty;
+
 // Re-export the namespaces into each other
 pub use component::*;
 pub use element::*;
@@ -29,8 +31,8 @@ use syn::{
 };
 
 pub struct CallBody {
-    custom_context: Option<Ident>,
-    roots: Vec<BodyNode>,
+    pub custom_context: Option<Ident>,
+    pub roots: Vec<BodyNode>,
 }
 
 impl Parse for CallBody {

+ 3 - 1
packages/core-macro/src/rsx/node.rs → packages/rsx/src/node.rs

@@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     parse::{Parse, ParseStream},
-    token, Expr, LitStr, Result, Token,
+    token, Attribute, Expr, LitStr, Result, Token,
 };
 
 /*
@@ -20,6 +20,7 @@ pub enum BodyNode {
     Component(Component),
     Text(LitStr),
     RawExpr(Expr),
+    Meta(Attribute),
 }
 
 impl Parse for BodyNode {
@@ -79,6 +80,7 @@ impl ToTokens for BodyNode {
             BodyNode::RawExpr(exp) => tokens.append_all(quote! {
                  __cx.fragment_from_iter(#exp)
             }),
+            BodyNode::Meta(_) => {}
         }
     }
 }

+ 1 - 0
packages/rsx/src/pretty.rs

@@ -0,0 +1 @@
+//! pretty printer for rsx!

+ 4 - 1
src/lib.rs

@@ -340,6 +340,9 @@ pub use dioxus_web as web;
 #[cfg(feature = "desktop")]
 pub use dioxus_desktop as desktop;
 
+#[cfg(feature = "fermi")]
+pub use fermi;
+
 // #[cfg(feature = "mobile")]
 // pub use dioxus_mobile as mobile;
 
@@ -350,7 +353,7 @@ pub mod events {
 
 pub mod prelude {
     pub use dioxus_core::prelude::*;
-    pub use dioxus_core_macro::{format_args_f, inline_props, rsx, Props, Routable};
+    pub use dioxus_core_macro::{format_args_f, inline_props, rsx, Props};
     pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
     pub use dioxus_hooks::*;
     pub use dioxus_html as dioxus_elements;

+ 46 - 0
tests/fermi.rs

@@ -0,0 +1,46 @@
+#![allow(non_snake_case)]
+
+use dioxus::prelude::*;
+use fermi::*;
+
+#[test]
+fn test_fermi() {
+    let mut app = VirtualDom::new(App);
+    app.rebuild();
+}
+
+static TITLE: Atom<String> = |_| "".to_string();
+static USERS: AtomFamily<u32, String> = |_| Default::default();
+
+fn App(cx: Scope) -> Element {
+    cx.render(rsx!(
+        Leaf { id: 0 }
+        Leaf { id: 1 }
+        Leaf { id: 2 }
+    ))
+}
+
+#[derive(Debug, PartialEq, Props)]
+struct LeafProps {
+    id: u32,
+}
+
+fn Leaf(cx: Scope<LeafProps>) -> Element {
+    let _user = use_read(&cx, TITLE);
+    let _user = use_read(&cx, USERS);
+
+    rsx!(cx, div {
+        button {
+            onclick: move |_| {},
+            "Start"
+        }
+        button {
+            onclick: move |_| {},
+            "Stop"
+        }
+        button {
+            onclick: move |_| {},
+            "Reverse"
+        }
+    })
+}