mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
examples: upgrade to new version of dioxus core.
also add the inline_props macro
This commit is contained in:
parent
21e00c114e
commit
cda759c659
41 changed files with 556 additions and 253 deletions
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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!" }
|
||||
))
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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!"
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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}" }
|
||||
|
|
|
@ -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/"
|
||||
|
|
22
packages/core-macro/examples/inline_props.rs
Normal file
22
packages/core-macro/examples/inline_props.rs
Normal 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();
|
||||
}
|
102
packages/core-macro/src/inlineprops.rs
Normal file
102
packages/core-macro/src/inlineprops.rs
Normal 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
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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, "")
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" } })}
|
||||
|
|
|
@ -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 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {}
|
||||
}
|
||||
|
|
|
@ -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!()
|
||||
},
|
||||
|
|
|
@ -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.
|
||||
///
|
||||
|
|
|
@ -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)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
// })
|
||||
};
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in a new issue