examples: upgrade to new version of dioxus core.

also add the inline_props macro
This commit is contained in:
Jonathan Kelley 2021-12-25 17:18:05 -05:00
parent 21e00c114e
commit cda759c659
41 changed files with 556 additions and 253 deletions

View file

@ -17,7 +17,7 @@ pub static App: Component<()> = |cx| {
let (async_count, dir) = (count.for_async(), *direction);
let (task, _) = use_coroutine(cx, move || async move {
let task = use_coroutine(&cx, move || async move {
loop {
TimeoutFuture::new(250).await;
*async_count.get_mut() += dir;

View file

@ -40,7 +40,7 @@ struct C1Props<'a> {
}
fn Child1<'a>(cx: Scope<'a, C1Props<'a>>) -> Element {
let (left, right) = props.text.split_once("=").unwrap();
let (left, right) = cx.props.text.split_once("=").unwrap();
cx.render(rsx! {
div {
@ -58,7 +58,7 @@ struct C2Props<'a> {
fn Child2<'a>(cx: Scope<'a, C2Props<'a>>) -> Element {
cx.render(rsx! {
Child3 {
text: props.text
text: cx.props.text
}
})
}

View file

@ -124,6 +124,6 @@ fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
rsx!(cx, button {
class: "calculator-key {cx.props.name}"
onclick: {cx.props.onclick}
{&props.children}
{&cx.props.children}
})
}

View file

@ -58,15 +58,15 @@ static App: Component<()> = |cx| {
#[derive(Props)]
struct HorseyProps<'a> {
pos: i32,
children: ScopeChildren<'a>,
children: Element<'a>,
}
fn Horsey<'a>((cx, props): ScopeState<'a, HorseyProps<'a>>) -> Element {
fn Horsey<'a>(cx: Scope<'a, HorseyProps<'a>>) -> Element {
cx.render(rsx! {
div {
button { "pause" }
div {
{&props.children}
{&cx.props.children}
}
}
})

View file

@ -8,7 +8,7 @@
use dioxus::prelude::*;
fn main() {
simple_logger::init_with_level(log::Level::Debug);
// simple_logger::init_with_level(log::Level::Debug);
dioxus::desktop::launch_cfg(App, |c| {
c.with_window(|w| {
w.with_resizable(true).with_inner_size(

View file

@ -97,7 +97,7 @@ struct ActionButtonProps<'a> {
fn ActionButton<'a>(cx: Scope<'a, ActionButtonProps<'a>>) -> Element {
rsx!(cx, div { class: "col-sm-6 smallpad"
button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (props.onclick)(),
button { class:"btn btn-primary btn-block", r#type: "button", id: "{cx.props.id}", onclick: move |_| (cx.props.onclick)(),
"{cx.props.name}"
}
})

View file

@ -1,10 +1,10 @@
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
fn App((cx, props): ScopeState<()>) -> Element {
fn app(cx: Scope<()>) -> Element {
cx.render(rsx! (
div { "Hello, world!" }
))

View file

@ -14,9 +14,9 @@ use dioxus::ssr;
fn main() {
let vdom = VirtualDom::new(App);
let content = ssr::render_vdom(&vdom, |f| f.pre_render(true));
let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
dioxus::desktop::launch(App, |c| c.with_prerendered(content));
dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
}
static App: Component<()> = |cx| {

View file

@ -33,11 +33,7 @@ fn main() {
AppendChildren { many: 1 },
];
dioxus_desktop::run(APP, (), |c| c.with_edits(edits));
}
let app: Component<()> = |cx| rsx!(cx, div { "some app" });
const APP: Component<()> = |(cx, _props)| {
rsx!(cx, div {
"some app"
})
};
dioxus_desktop::run(app, (), |c| c.with_edits(edits));
}

View file

@ -15,6 +15,8 @@
//! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
//! RefMuts at the same time.
use std::sync::Arc;
use dioxus::desktop::wry::application::dpi::LogicalSize;
use dioxus::events::*;
use dioxus::prelude::*;
@ -73,16 +75,16 @@ static App: Component<()> = |cx| {
#[derive(Props)]
struct CalculatorKeyProps<'a> {
name: &'static str,
onclick: &'a dyn Fn(MouseEvent),
children: ScopeChildren<'a>,
onclick: &'a dyn Fn(Arc<MouseEvent>),
children: Element<'a>,
}
fn CalculatorKey<'a>((cx, props): ScopeState<'a, CalculatorKeyProps<'a>>) -> Element<'a> {
fn CalculatorKey<'a>(cx: Scope<'a, CalculatorKeyProps<'a>>) -> Element {
cx.render(rsx! {
button {
class: "calculator-key {cx.props.name}"
onclick: {cx.props.onclick}
{&props.children}
onclick: move |e| (cx.props.onclick)(e)
{&cx.props.children}
}
})
}
@ -168,7 +170,7 @@ impl Calculator {
self.cur_val = self.display_value.parse::<f64>().unwrap();
self.waiting_for_operand = true;
}
fn handle_keydown(&mut self, evt: KeyboardEvent) {
fn handle_keydown(&mut self, evt: Arc<KeyboardEvent>) {
match evt.key_code {
KeyCode::Backspace => self.backspace(),
KeyCode::Num0 => self.input_digit(0),

View file

@ -1,5 +1,5 @@
use dioxus::prelude::*;
use dioxus::router::Router;
use dioxus_router::{use_router, Link};
#[derive(PartialEq, Debug, Clone)]
pub enum Route {
@ -24,7 +24,7 @@ pub enum Route {
}
static App: Component<()> = |cx| {
let route = use_router(cx, Route::parse)?;
let route = use_router(&cx, Route::parse);
cx.render(rsx! {
nav {
@ -61,26 +61,4 @@ impl Route {
}
}
impl Routable for Route {
fn from_path(path: &str, params: &std::collections::HashMap<&str, &str>) -> Option<Self> {
todo!()
}
fn to_path(&self) -> String {
todo!()
}
fn routes() -> Vec<&'static str> {
todo!()
}
fn not_found_route() -> Option<Self> {
todo!()
}
fn recognize(pathname: &str) -> Option<Self> {
todo!()
}
}
fn main() {}

View file

@ -39,7 +39,7 @@
//! - Allow top-level fragments
//!
fn main() {
dioxus::desktop::launch(Example);
dioxus::desktop::launch(EXAMPLE);
}
/// When trying to return "nothing" to Dioxus, you'll need to specify the type parameter or Rust will be sad.
@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None;
use baller::Baller;
use dioxus::prelude::*;
pub static Example: Component<()> = |cx| {
pub static EXAMPLE: Component<()> = |cx| {
let formatting = "formatting!";
let formatting_tuple = ("a", "b");
let lazy_fmt = format_args!("lazily formatted text");
@ -173,12 +173,12 @@ pub static Example: Component<()> = |cx| {
Taller { a: "asd", div {"hello world!"} }
// helper functions
{helper(cx, "hello world!")}
{helper(&cx, "hello world!")}
}
})
};
fn helper(cx: Scope, text: &str) -> Element {
fn helper<'a>(cx: &'a ScopeState, text: &str) -> Element<'a> {
rsx!(cx, p { "{text}" })
}
@ -188,7 +188,7 @@ mod baller {
pub struct BallerProps {}
/// This component totally balls
pub fn Baller(_: ScopeState<BallerProps>) -> Element {
pub fn Baller(_: Scope<BallerProps>) -> Element {
todo!()
}
}
@ -196,13 +196,11 @@ mod baller {
#[derive(Props)]
pub struct TallerProps<'a> {
a: &'static str,
#[builder(default)]
children: ScopeChildren<'a>,
children: Element<'a>,
}
/// This component is taller than most :)
pub fn Taller<'a>(_: ScopeState<'a, TallerProps<'a>>) -> Element {
pub fn Taller<'a>(_: Scope<'a, TallerProps<'a>>) -> Element {
let b = true;
todo!()
}

View file

@ -1,15 +1,13 @@
#![allow(non_upper_case_globals)]
use dioxus::prelude::*;
use dioxus::ssr;
fn main() {
let mut vdom = VirtualDom::new(App);
vdom.rebuild_in_place().expect("Rebuilding failed");
let mut vdom = VirtualDom::new(APP);
let _ = vdom.rebuild();
println!("{}", ssr::render_vdom(&vdom));
}
static App: Component<()> = |cx| {
static APP: Component<()> = |cx| {
cx.render(rsx!(
div {
h1 { "Title" }
@ -17,10 +15,3 @@ static App: Component<()> = |cx| {
}
))
};
struct MyProps<'a> {
text: &'a str,
}
fn App2<'a>(cx: Scope<'a, MyProps<'a>>) -> Element {
None
}

View file

@ -2,7 +2,7 @@ use dioxus::prelude::*;
fn main() {
use dioxus::desktop::wry::application::platform::macos::*;
dioxus::desktop::launch(App, |c| {
dioxus::desktop::launch_cfg(App, |c| {
c.with_window(|w| {
w.with_fullsize_content_view(true)
.with_titlebar_buttons_hidden(false)

View file

@ -6,29 +6,24 @@ use std::time::Duration;
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App);
dioxus::desktop::launch(app);
}
static App: Component<()> = |cx| {
fn app(cx: Scope<()>) -> Element {
let mut count = use_state(&cx, || 0);
cx.push_task(async move {
cx.push_task(|| async move {
tokio::time::sleep(Duration::from_millis(100)).await;
println!("setting count");
count += 1;
// count.set(10);
// *count += 1;
// let c = count.get() + 1;
// count.set(c);
});
cx.render(rsx! {
div {
h1 { "High-Five counter: {count}" }
// button {
// onclick: move |_| count +=1 ,
// "Click me!"
// }
button {
onclick: move |_| count +=1 ,
"Click me!"
}
}
})
};
}

View file

@ -23,9 +23,9 @@ pub struct TodoItem {
const STYLE: &str = include_str!("./assets/todomvc.css");
const App: Component<()> = |cx| {
let mut draft = use_state(&cx, || "".to_string());
let mut todos = use_state(&cx, || HashMap::<u32, Rc<TodoItem>>::new());
let mut filter = use_state(&cx, || FilterState::All);
let draft = use_state(&cx, || "".to_string());
let todos = use_state(&cx, || HashMap::<u32, Rc<TodoItem>>::new());
let filter = use_state(&cx, || FilterState::All);
let todolist = todos
.iter()
@ -86,9 +86,9 @@ pub struct TodoEntryProps {
}
pub fn TodoEntry(cx: Scope<TodoEntryProps>) -> Element {
let mut is_editing = use_state(&cx, || false);
let mut contents = use_state(&cx, || String::from(""));
let todo = &props.todo;
let is_editing = use_state(&cx, || false);
let contents = use_state(&cx, || String::from(""));
let todo = &cx.props.todo;
rsx!(cx, li {
"{todo.id}"

View file

@ -45,8 +45,8 @@ struct RowProps {
row_id: usize,
label: Label,
}
fn Row((cx, props): ScopeState<RowProps>) -> Element {
let [adj, col, noun] = props.label.0;
fn Row(cx: Scope<RowProps>) -> Element {
let [adj, col, noun] = cx.props.label.0;
cx.render(rsx! {
tr {
td { class:"col-md-1", "{cx.props.row_id}" }

View file

@ -2,7 +2,7 @@
name = "dioxus-core-macro"
version = "0.1.2"
authors = ["Jonathan Kelley"]
edition = "2018"
edition = "2021"
description = "Core macro for Dioxus Virtual DOM"
license = "MIT/Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"

View file

@ -0,0 +1,22 @@
use dioxus_core_macro::{inline_props, Props};
fn main() {}
type Element<'a> = ();
pub struct Scope<'a, T> {
props: &'a T,
}
#[inline_props]
pub fn component(
cx: Scope,
chkk: String,
chkk2: String,
r: u32,
cat: &'a str,
drd: String,
e: String,
) -> Element {
let r = chkk.len();
}

View file

@ -0,0 +1,102 @@
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
token, Block, FnArg, Generics, Ident, Result, ReturnType, Token, Visibility,
};
pub struct InlinePropsBody {
pub vis: syn::Visibility,
pub fn_token: Token![fn],
pub ident: Ident,
pub cx_token: Ident,
pub generics: Generics,
pub paren_token: token::Paren,
pub inputs: Punctuated<FnArg, Token![,]>,
// pub fields: FieldsNamed,
pub output: ReturnType,
pub block: Box<Block>,
}
/// The custom rusty variant of parsing rsx!
impl Parse for InlinePropsBody {
fn parse(input: ParseStream) -> Result<Self> {
let vis: Visibility = input.parse()?;
let fn_token = input.parse()?;
let ident = input.parse()?;
let generics = input.parse()?;
let content;
let paren_token = syn::parenthesized!(content in input);
let cx_token: Ident = content.parse()?;
let _: Token![:] = content.parse()?;
let _: Ident = content.parse()?;
let _: Result<Token![,]> = content.parse();
let inputs = syn::punctuated::Punctuated::parse_terminated(&content)?;
let output = input.parse()?;
let block = input.parse()?;
Ok(Self {
vis,
fn_token,
ident,
generics,
paren_token,
inputs,
output,
block,
cx_token,
})
}
}
/// Serialize the same way, regardless of flavor
impl ToTokens for InlinePropsBody {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let Self {
vis,
ident,
generics,
inputs,
output,
block,
cx_token,
..
} = self;
let fields = inputs.iter().map(|f| {
quote! { #vis #f }
});
let struct_name = Ident::new(&format!("{}Props", ident), Span::call_site());
let field_names = inputs.iter().filter_map(|f| match f {
FnArg::Receiver(_) => todo!(),
FnArg::Typed(t) => Some(&t.pat),
});
let modifiers = if generics.params.is_empty() {
quote! { #[derive(Props, PartialEq)] }
} else {
quote! { #[derive(PartialEq)] }
};
out_tokens.append_all(quote! {
#modifiers
#vis struct #struct_name #generics {
#(#fields),*
}
#vis fn #ident #generics (#cx_token: Scope<#struct_name>) #output {
let #struct_name { #(#field_names),* } = &cx.props;
#block
}
});
}
}

View file

@ -2,8 +2,8 @@ use proc_macro::TokenStream;
use quote::ToTokens;
use syn::parse_macro_input;
pub(crate) mod htm;
pub(crate) mod ifmt;
pub(crate) mod inlineprops;
pub(crate) mod props;
pub(crate) mod router;
pub(crate) mod rsx;
@ -214,3 +214,37 @@ pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
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>`,
/// removing some boilerplate when defining props.
///
/// You don't *need* to use this macro at all, but it can be helpful in cases where
/// you would be repeating a lot of the usual Rust boilerplate.
///
/// # Example
/// ```
/// #[inline_props]
/// fn app(cx: Scope<{ bob: String }>) -> Element {
/// cx.render(rsx!("hello, {bob}"))
/// }
///
/// // is equivalent to
///
/// #[derive(PartialEq, Props)]
/// struct AppProps {
/// bob: String,
/// }
///
/// fn app(cx: Scope<AppProps>) -> Element {
/// cx.render(rsx!("hello, {bob}"))
/// }
/// ```
#[proc_macro_attribute]
pub fn inline_props(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
match syn::parse::<inlineprops::InlinePropsBody>(s) {
Err(e) => e.to_compile_error().into(),
Ok(s) => s.to_token_stream().into(),
}
}

View file

@ -52,7 +52,6 @@ fern = { version = "0.6.0", features = ["colored"] }
rand = { version = "0.8.4", features = ["small_rng"] }
simple_logger = "1.13.0"
dioxus-core-macro = { path = "../core-macro", version = "^0.1.2" }
# dioxus-hooks = { path = "../hooks" }
criterion = "0.3.5"
thiserror = "1.0.30"

View file

@ -46,11 +46,9 @@ fn main() {
VNodes::Element(vel) => dbg!(vel),
_ => todo!()
}
}
```
## Internals
Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts:

View file

@ -5,11 +5,11 @@ use dioxus_html as dioxus_elements;
fn main() {}
fn App(cx: Scope<()>) -> Element {
fn app(cx: Scope) -> Element {
cx.render(rsx!(div {
App2 {
app2 (
p: "asd"
}
)
}))
}
@ -18,7 +18,7 @@ struct Borrowed<'a> {
p: &'a str,
}
fn App2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element {
fn app2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element {
let g = eat2(&cx);
rsx!(cx, "")
}

View file

@ -1,6 +1,6 @@
//! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
//! This module contains the stateful DiffState and all methods to diff VNodes, their properties, and their children.
//!
//! The [`DiffMachine`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
//! of mutations for the RealDom to apply.
//!
//! ## Notice:
@ -86,14 +86,14 @@
//! - Certain web-dom-specific optimizations.
//!
//! More info on how to improve this diffing algorithm:
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
//! - <https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/>
use crate::innerlude::*;
use fxhash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
use DomEdit::*;
/// Our DiffMachine is an iterative tree differ.
/// Our DiffState is an iterative tree differ.
///
/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This
/// was originally implemented using recursive techniques, but Rust lacks the ability to call async functions recursively,
@ -490,7 +490,7 @@ impl<'bump> DiffState<'bump> {
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
// The normal pathway still works, but generates slightly weird instructions
// This pathway just ensures we get create and replace
// This pathway ensures uses the ReplaceAll, not the InsertAfter and remove
(Placeholder(_), Fragment(new)) => {
self.stack
.create_children(new.children, MountType::Replace { old: old_node });

View file

@ -8,10 +8,10 @@
//! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
//! we build a tiny alloactor in the stack and allocate the closure into that.
//!
//! The logic for this was borrowed from https://docs.rs/stack_dst/0.6.1/stack_dst/. Unfortunately, this crate does not
//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
use crate::prelude::{NodeFactory, VNode};
use crate::innerlude::{NodeFactory, VNode};
use std::mem;
/// A concrete type provider for closures that build VNode structures.
@ -35,15 +35,15 @@ enum StackNodeStorage<'a, 'b> {
}
impl<'a, 'b> LazyNodes<'a, 'b> {
pub fn new_some<F>(_val: F) -> Option<Self>
pub fn new_some<F>(_val: F) -> Self
where
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
{
Some(Self::new(_val))
Self::new(_val)
}
/// force this call onto the stack
pub fn new_boxed<F>(_val: F) -> Option<Self>
pub fn new_boxed<F>(_val: F) -> Self
where
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
{
@ -55,9 +55,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
fac.map(inner)
};
Some(Self {
Self {
inner: StackNodeStorage::Heap(Box::new(val)),
})
}
}
pub fn new<F>(_val: F) -> Self
@ -249,9 +249,7 @@ fn it_works() {
let caller = LazyNodes::new_some(|f| {
//
f.text(format_args!("hello world!"))
})
.unwrap();
});
let g = caller.call(factory);
dbg!(g);
@ -291,7 +289,6 @@ fn it_drops() {
log::debug!("main closure");
f.fragment_from_iter(it)
})
.unwrap()
};
let _ = caller.call(factory);

View file

@ -57,19 +57,25 @@ pub(crate) mod innerlude {
/// // ...
/// };
/// ```
pub type Component<P> = fn(Scope<P>) -> Element;
pub type Component<P = ()> = fn(Scope<P>) -> Element;
/// A list of attributes
///
pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
}
pub use crate::innerlude::{
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority,
IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope,
ScopeId, ScopeState, UserEvent, VElement, VFragment, VNode, VirtualDom,
ScopeId, ScopeState, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder, VText,
VirtualDom,
};
pub mod prelude {
pub use crate::innerlude::Scope;
pub use crate::innerlude::{
Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, ScopeState,
Attributes, Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory,
ScopeState,
};
pub use crate::nodes::VNode;
pub use crate::virtual_dom::{fc_to_builder, Fragment, Properties};

View file

@ -1,6 +1,9 @@
//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
//!
//! This module contains an internal API to generate these instructions.
//!
//! Beware that changing code in this module will break compatibility with
//! interpreters for these types of DomEdits.
use crate::innerlude::*;
use std::{any::Any, fmt::Debug};

View file

@ -20,7 +20,7 @@ use std::{
///
/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
///
/// - the [`rsx`] macro
/// - the `rsx!` macro
/// - the [`NodeFactory`] API
pub enum VNode<'src> {
/// Text VNodes simply bump-allocated (or static) string slices
@ -51,7 +51,7 @@ pub enum VNode<'src> {
/// key: "a",
/// onclick: |e| log::info!("clicked"),
/// hidden: "true",
/// style: { background_color: "red" }
/// style: { background_color: "red" },
/// "hello"
/// }
/// });
@ -59,7 +59,7 @@ pub enum VNode<'src> {
/// if let VNode::Element(velement) = node {
/// assert_eq!(velement.tag_name, "div");
/// assert_eq!(velement.namespace, None);
/// assert_eq!(velement.key, Some("a));
/// assert_eq!(velement.key, Some("a"));
/// }
/// ```
Element(&'src VElement<'src>),
@ -720,6 +720,14 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
}
}
impl<'a, 'b> IntoIterator for LazyNodes<'a, 'b> {
type Item = LazyNodes<'a, 'b>;
type IntoIter = std::iter::Once<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
std::iter::once(self)
}
}
impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
self.call(cx)

View file

@ -351,7 +351,7 @@ impl ScopeArena {
/// cx.render(rsx!{ div {"Hello, {cx.props.name}"} })
/// }
/// ```
pub struct Scope<'a, P> {
pub struct Scope<'a, P = ()> {
pub scope: &'a ScopeState,
pub props: &'a P,
}
@ -366,6 +366,26 @@ impl<P> Clone for Scope<'_, P> {
}
}
impl<'src, P> Scope<'src, P> {
pub fn register_task(self, task: impl Future<Output = ()> + 'src) {
let fut: &'src mut dyn Future<Output = ()> = self.scope.bump().alloc(task);
let boxed_fut: BumpBox<'src, dyn Future<Output = ()> + 'src> =
unsafe { BumpBox::from_raw(fut) };
let pinned_fut: Pin<BumpBox<'src, _>> = boxed_fut.into();
// erase the 'src lifetime for self-referential storage
// todo: provide a miri test around this
// concerned about segfaulting
let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) };
self.sender
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap();
self.scope.items.borrow_mut().tasks.push(self_ref_fut);
}
}
impl<'a, P> std::ops::Deref for Scope<'a, P> {
// rust will auto deref again to the original 'a lifetime at the call site
type Target = &'a ScopeState;
@ -578,7 +598,7 @@ impl ScopeState {
/// Schedule an update for any component given its ScopeId.
///
/// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
/// A component's ScopeId can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
///
/// This method should be used when you want to schedule an update for a component
pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
@ -662,29 +682,33 @@ impl ScopeState {
/// Pushes the future onto the poll queue to be polled after the component renders.
///
/// The future is forcibly dropped if the component is not ready by the next render
pub fn push_task<'src, F>(&'src self, fut: impl FnOnce() -> F + 'src) -> usize
pub fn push_task<'src, F>(&'src self, fut: F) -> usize
where
F: Future<Output = ()>,
F::Output: 'src,
F: 'src,
{
self.sender
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap();
// wrap it in a type that will actually drop the contents
//
// Safety: we just made the pointer above and will promise not to alias it!
// The main reason we do this through from_raw is because Bumpalo's box does
// not support unsized coercion
let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut());
let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
let pinned_fut: Pin<BumpBox<_>> = boxed_fut.into();
let fut: &'src mut dyn Future<Output = ()> = self.bump().alloc(fut);
let boxed_fut: BumpBox<'src, dyn Future<Output = ()> + 'src> =
unsafe { BumpBox::from_raw(fut) };
let pinned_fut: Pin<BumpBox<'src, _>> = boxed_fut.into();
// erase the 'src lifetime for self-referential storage
// todo: provide a miri test around this
// concerned about segfaulting
let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) };
let self_ref_fut = unsafe {
std::mem::transmute::<Pin<BumpBox<'src, _>>, Pin<BumpBox<'static, _>>>(pinned_fut)
};
self.sender
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap();
// Push the future into the tasks
let mut items = self.items.borrow_mut();
@ -707,14 +731,11 @@ impl ScopeState {
/// cx.render(lazy_tree)
/// }
///```
pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VNode<'src>> {
pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
let fac = NodeFactory {
bump: &self.wip_frame().bump,
};
match rsx {
Some(s) => Some(s.call(fac)),
None => todo!("oh no no nodes"),
}
Some(rsx.call(fac))
}
/// Store a value between renders

View file

@ -15,7 +15,7 @@ use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::A
///
/// ## Guide
///
/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`].
/// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
///
/// ```rust, ignore
/// #[derive(Props, PartialEq)]
@ -552,11 +552,11 @@ impl VirtualDom {
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> {
pub fn render_vnodes<'a>(&'a self, lazy_nodes: LazyNodes<'a, '_>) -> &'a VNode<'a> {
let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
let frame = scope.wip_frame();
let factory = NodeFactory { bump: &frame.bump };
let node = lazy_nodes.unwrap().call(factory);
let node = lazy_nodes.call(factory);
frame.bump.alloc(node)
}
@ -594,7 +594,7 @@ impl VirtualDom {
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn create_vnodes<'a>(&'a self, nodes: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
let mut machine = DiffState::new(&self.scopes);
machine.stack.element_stack.push(ElementId(0));
machine
@ -619,8 +619,8 @@ impl VirtualDom {
/// ```
pub fn diff_lazynodes<'a>(
&'a self,
left: Option<LazyNodes<'a, '_>>,
right: Option<LazyNodes<'a, '_>>,
left: LazyNodes<'a, '_>,
right: LazyNodes<'a, '_>,
) -> (Mutations<'a>, Mutations<'a>) {
let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
@ -947,7 +947,7 @@ impl<'a> Properties for FragmentProps<'a> {
#[allow(non_upper_case_globals, non_snake_case)]
pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
let i = cx.props.0.as_ref().map(|f| f.decouple());
cx.render(Some(LazyNodes::new(|f| f.fragment_from_iter(i))))
cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
}
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus

View file

@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
#[test]
fn test_original_diff() {
static APP: Component<()> = |cx| {
static APP: Component = |cx| {
cx.render(rsx! {
div {
div {
@ -57,7 +57,7 @@ fn test_original_diff() {
#[test]
fn create() {
static APP: Component<()> = |cx| {
static APP: Component = |cx| {
cx.render(rsx! {
div {
div {
@ -120,7 +120,7 @@ fn create() {
#[test]
fn create_list() {
static APP: Component<()> = |cx| {
static APP: Component = |cx| {
cx.render(rsx! {
{(0..3).map(|f| rsx!{ div {
"hello"
@ -169,7 +169,7 @@ fn create_list() {
#[test]
fn create_simple() {
static APP: Component<()> = |cx| {
static APP: Component = |cx| {
cx.render(rsx! {
div {}
div {}
@ -207,7 +207,7 @@ fn create_simple() {
}
#[test]
fn create_components() {
static App: Component<()> = |cx| {
static App: Component = |cx| {
cx.render(rsx! {
Child { "abc1" }
Child { "abc2" }
@ -273,7 +273,7 @@ fn create_components() {
}
#[test]
fn anchors() {
static App: Component<()> = |cx| {
static App: Component = |cx| {
cx.render(rsx! {
{true.then(|| rsx!{ div { "hello" } })}
{false.then(|| rsx!{ div { "goodbye" } })}

View file

@ -802,3 +802,68 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
]
);
}
// noticed some weird behavior in the desktop interpreter
// just making sure it doesnt happen in the core implementation
#[test]
fn remove_list() {
let dom = new_dom();
let left = rsx!({
(0..10).rev().take(5).map(|i| {
rsx! { Fragment { key: "{i}", "{i}" }}
})
});
let right = rsx!({
(0..10).rev().take(2).map(|i| {
rsx! { Fragment { key: "{i}", "{i}" }}
})
});
let (create, changes) = dom.diff_lazynodes(left, right);
// dbg!(create);
// dbg!(changes);
assert_eq!(
changes.edits,
[
// remove 5, 4, 3
Remove { root: 3 },
Remove { root: 4 },
Remove { root: 5 },
]
);
}
// noticed some weird behavior in the desktop interpreter
// just making sure it doesnt happen in the core implementation
#[test]
fn remove_list_nokeyed() {
let dom = new_dom();
let left = rsx!({
(0..10).rev().take(5).map(|i| {
rsx! { Fragment { "{i}" }}
})
});
let right = rsx!({
(0..10).rev().take(2).map(|i| {
rsx! { Fragment { "{i}" }}
})
});
let (create, changes) = dom.diff_lazynodes(left, right);
assert_eq!(
changes.edits,
[
// remove 5, 4, 3
Remove { root: 3 },
Remove { root: 4 },
Remove { root: 5 },
]
);
}

View file

@ -11,7 +11,7 @@ use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
fn main() {
simple_logger::init().unwrap();
// simple_logger::init().unwrap();
dioxus_desktop::launch(app);
}
@ -21,6 +21,7 @@ fn app(cx: Scope<()>) -> Element {
cx.push_task(|| async move {
tokio::time::sleep(Duration::from_millis(1000)).await;
println!("count is now {:?}", count);
count += 1;
});
@ -33,4 +34,4 @@ fn app(cx: Scope<()>) -> Element {
}
}
})
};
}

View file

@ -23,6 +23,7 @@ use tao::{
pub use wry;
pub use wry::application as tao;
use wry::{
application::event_loop::EventLoopProxy,
webview::RpcRequest,
webview::{WebView, WebViewBuilder},
};
@ -37,7 +38,7 @@ pub fn launch_cfg(
launch_with_props(root, (), config_builder)
}
pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
pub fn launch_with_props<P: 'static + Send>(
root: Component<P>,
props: P,
builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
@ -51,7 +52,7 @@ struct Response<'a> {
edits: Vec<DomEdit<'a>>,
}
pub fn run<T: 'static + Send + Sync>(
pub fn run<T: 'static + Send>(
root: Component<T>,
props: T,
user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
@ -59,51 +60,61 @@ pub fn run<T: 'static + Send + Sync>(
let mut desktop_cfg = DesktopConfig::new();
user_builder(&mut desktop_cfg);
let mut state = DesktopController::new_on_tokio(root, props);
let event_loop = EventLoop::with_user_event();
let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ);
let modifiers = ModifiersState::default();
let event_loop = EventLoop::new();
log::debug!("Starting event loop");
event_loop.run(move |window_event, event_loop, control_flow| {
*control_flow = ControlFlow::Wait;
match window_event {
Event::NewEvents(StartCause::Init) => state.new_window(&desktop_cfg, event_loop),
Event::NewEvents(StartCause::Init) => desktop.new_window(&desktop_cfg, event_loop),
Event::WindowEvent {
event, window_id, ..
} => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Destroyed { .. } => state.close_window(window_id, control_flow),
} => {
match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
WindowEvent::KeyboardInput { event, .. } => {
if quit_hotkey.matches(&modifiers, &event.physical_key) {
state.close_window(window_id, control_flow);
WindowEvent::KeyboardInput { event, .. } => {
if quit_hotkey.matches(&modifiers, &event.physical_key) {
desktop.close_window(window_id, control_flow);
}
}
}
WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
if let Some(view) = state.webviews.get_mut(&window_id) {
let _ = view.resize();
WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
if let Some(view) = desktop.webviews.get_mut(&window_id) {
let _ = view.resize();
}
}
// TODO: we want to shuttle all of these events into the user's app or provide some handler
_ => {}
}
}
// TODO: we want to shuttle all of these events into the user's app or provide some handler
_ => {}
},
Event::MainEventsCleared => state.try_load_ready_webviews(),
Event::UserEvent(_evt) => {
desktop.try_load_ready_webviews();
}
Event::MainEventsCleared => {
desktop.try_load_ready_webviews();
}
Event::Resumed => {}
Event::Suspended => {}
Event::LoopDestroyed => {}
Event::RedrawRequested(_id) => {}
_ => {}
}
})
}
pub enum UserWindowEvent {
Start,
Update,
}
pub struct DesktopController {
webviews: HashMap<WindowId, WebView>,
sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
@ -115,7 +126,11 @@ pub struct DesktopController {
impl DesktopController {
// Launch the virtualdom on its own thread managed by tokio
// returns the desktop state
pub fn new_on_tokio<P: Send + 'static>(root: Component<P>, props: P) -> Self {
pub fn new_on_tokio<P: Send + 'static>(
root: Component<P>,
props: P,
evt: EventLoopProxy<UserWindowEvent>,
) -> Self {
let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
let pending_edits = edit_queue.clone();
@ -130,7 +145,6 @@ impl DesktopController {
.unwrap();
runtime.block_on(async move {
// LocalSet::new().block_on(&runtime, async move {
let mut dom =
VirtualDom::new_with_props_and_scheduler(root, props, (sender, receiver));
@ -150,6 +164,7 @@ impl DesktopController {
.unwrap()
.push_front(serde_json::to_string(&edit.edits).unwrap());
}
let _ = evt.send_event(UserWindowEvent::Update);
}
})
});
@ -164,7 +179,11 @@ impl DesktopController {
}
}
pub fn new_window(&mut self, cfg: &DesktopConfig, event_loop: &EventLoopWindowTarget<()>) {
pub fn new_window(
&mut self,
cfg: &DesktopConfig,
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
) {
let builder = cfg.window.clone().with_menu({
// create main menubar menu
let mut menu_bar_menu = MenuBar::new();

View file

@ -5,44 +5,71 @@ use std::{
pin::Pin,
rc::Rc,
};
/*
pub fn use_coroutine<'a, F: Future<Output = ()> + 'static>(
let g = use_coroutine(&cx, || {
// clone the items in
async move {
}
})
*/
pub fn use_coroutine<'a, F>(
cx: &'a ScopeState,
mut f: impl FnMut() -> F + 'a,
) -> CoroutineHandle<'a> {
create_future: impl FnOnce() -> F,
) -> CoroutineHandle<'a>
where
F: Future<Output = ()>,
F: 'static,
{
cx.use_hook(
move |_| State {
running: Default::default(),
fut: Default::default(),
submit: Default::default(),
pending_fut: Default::default(),
running_fut: Default::default(),
},
|state| {
let fut_slot = state.fut.clone();
let running = state.running.clone();
let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
let g = async move {
running.set(true);
f().await;
running.set(false);
};
let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
fut_slot
.borrow_mut()
.replace(unsafe { std::mem::transmute(p) });
});
let f = create_future();
state.pending_fut.set(Some(Box::pin(f)));
let submit = unsafe { std::mem::transmute(submit) };
state.submit.get_mut().replace(submit);
if state.running.get() {
let mut fut = state.fut.borrow_mut();
cx.push_task(|| fut.as_mut().unwrap().as_mut());
} else {
// make sure to drop the old future
if let Some(fut) = state.fut.borrow_mut().take() {
drop(fut);
}
if let Some(fut) = state.running_fut.as_mut() {
cx.push_task(fut);
}
// if let Some(fut) = state.running_fut.take() {
// state.running.set(true);
// fut.resume();
// }
// let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
// let g = async move {
// running.set(true);
// create_future().await;
// running.set(false);
// };
// let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
// fut_slot
// .borrow_mut()
// .replace(unsafe { std::mem::transmute(p) });
// });
// let submit = unsafe { std::mem::transmute(submit) };
// state.submit.get_mut().replace(submit);
// if state.running.get() {
// // let mut fut = state.fut.borrow_mut();
// // cx.push_task(|| fut.as_mut().unwrap().as_mut());
// } else {
// // make sure to drop the old future
// if let Some(fut) = state.fut.borrow_mut().take() {
// drop(fut);
// }
// }
CoroutineHandle { cx, inner: state }
},
)
@ -50,14 +77,20 @@ pub fn use_coroutine<'a, F: Future<Output = ()> + 'static>(
struct State {
running: Rc<Cell<bool>>,
submit: RefCell<Option<Box<dyn FnOnce()>>>,
fut: Rc<RefCell<Option<Pin<Box<dyn Future<Output = ()>>>>>>,
// the way this is structure, you can toggle the coroutine without re-rendering the comppnent
// this means every render *generates* the future, which is a bit of a waste
// todo: allocate pending futures in the bump allocator and then have a true promotion
pending_fut: Cell<Option<Pin<Box<dyn Future<Output = ()> + 'static>>>>,
running_fut: Option<Pin<Box<dyn Future<Output = ()> + 'static>>>,
// running_fut: Rc<RefCell<Option<Pin<Box<dyn Future<Output = ()> + 'static>>>>>,
}
pub struct CoroutineHandle<'a> {
cx: &'a ScopeState,
inner: &'a State,
}
impl Clone for CoroutineHandle<'_> {
fn clone(&self) -> Self {
CoroutineHandle {
@ -73,10 +106,11 @@ impl<'a> CoroutineHandle<'a> {
if self.is_running() {
return;
}
if let Some(submit) = self.inner.submit.borrow_mut().take() {
submit();
let mut fut = self.inner.fut.borrow_mut();
self.cx.push_task(|| fut.as_mut().unwrap().as_mut());
if let Some(submit) = self.inner.pending_fut.take() {
// submit();
// let inner = self.inner;
// self.cx.push_task(submit());
}
}
@ -84,5 +118,11 @@ impl<'a> CoroutineHandle<'a> {
self.inner.running.get()
}
pub fn resume(&self) {}
pub fn resume(&self) {
// self.cx.push_task(fut)
}
pub fn stop(&self) {}
pub fn restart(&self) {}
}

View file

@ -89,9 +89,9 @@ pub fn use_model_coroutine<'a, T, F: Future<Output = ()> + 'static>(
}
},
|inner| {
if let Some(task) = inner.task.get_mut() {
cx.push_task(|| task);
}
// if let Some(task) = inner.task.get_mut() {
// cx.push_task(|| task);
// }
//
todo!()
},

View file

@ -171,7 +171,11 @@ impl<'a, T: 'static> UseState<'a, T> {
}
impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
/// Gain mutable access to the new value. This method is only available when the value is a `ToOwned` type.
/// Gain mutable access to the new value via RefMut.
///
/// If `modify` is called, then the component will re-render.
///
/// This method is only available when the value is a `ToOwned` type.
///
/// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
///

View file

@ -2,14 +2,28 @@ mod utils;
use std::{cell::RefCell, rc::Rc};
use dioxus::Attribute;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_core_macro::{rsx, Props};
use dioxus_core_macro::{format_args_f, rsx, Props};
use dioxus_html as dioxus_elements;
// use wasm_bindgen::{JsCast, JsValue};
use crate::utils::strip_slash_suffix;
/// Initialize the app's router service and provide access to `Link` components
pub fn use_router<R: 'static>(cx: &ScopeState, f: impl Fn(&str) -> R) -> &R {
let r = f("/");
cx.use_hook(
|_| {
//
r
},
|f| f,
)
}
pub trait Routable: 'static + Send + Clone + PartialEq {}
impl<T> Routable for T where T: 'static + Send + Clone + PartialEq {}
@ -30,20 +44,29 @@ pub struct LinkProps<'a, R: Routable> {
/// Link { to: Route::Home, href: Route::as_url }
///
/// ```
href: fn(&R) -> String,
#[props(default, setter(strip_option))]
href: Option<&'a str>,
#[props(default, setter(strip_option))]
class: Option<&'a str>,
#[builder(default)]
children: Element<'a>,
#[props(default)]
attributes: Option<&'a [Attribute<'a>]>,
}
pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element {
let service = todo!();
// let service = todo!();
// let service: todo!() = use_router_service::<R>(&cx)?;
// cx.render(rsx! {
// a {
// href: format_args!("{}", (cx.props.href)(&cx.props.to)),
// onclick: move |_| service.push_route(cx.props.to.clone()),
// // todo!() {&cx.props.children},
// }
// })
let class = cx.props.class.unwrap_or("");
cx.render(rsx! {
a {
href: "#",
class: "{class}",
{&cx.props.children}
// onclick: move |_| service.push_route(cx.props.to.clone()),
// href: format_args!("{}", (cx.props.href)(&cx.props.to)),
}
})
}

View file

@ -15,26 +15,27 @@ fn main() {
}
static App: Component<()> = |cx| {
let doggo = cx.suspend(|| async move {
#[derive(serde::Deserialize)]
struct Doggo {
message: String,
}
todo!("suspense is broken")
// let doggo = suspend(|| async move {
// #[derive(serde::Deserialize)]
// struct Doggo {
// message: String,
// }
let src = reqwest::get("https://dog.ceo/api/breeds/image/random")
.await
.expect("Failed to fetch doggo")
.json::<Doggo>()
.await
.expect("Failed to parse doggo")
.message;
// let src = reqwest::get("https://dog.ceo/api/breeds/image/random")
// .await
// .expect("Failed to fetch doggo")
// .json::<Doggo>()
// .await
// .expect("Failed to parse doggo")
// .message;
rsx!(cx, img { src: "{src}" })
});
// rsx!(cx, img { src: "{src}" })
// });
rsx!(cx, div {
h1 {"One doggo coming right up"}
button { onclick: move |_| cx.needs_update(), "Get a new doggo" }
{doggo}
})
// rsx!(cx, div {
// h1 {"One doggo coming right up"}
// button { onclick: move |_| cx.needs_update(), "Get a new doggo" }
// {doggo}
// })
};

View file

@ -89,7 +89,7 @@ mod ric_raf;
/// }
/// ```
pub fn launch(root_component: Component<()>) {
launch_with_props(root_component, ());
launch_with_props(root_component, (), |c| c);
}
/// Launches the VirtualDOM from the specified component function and props.