diff --git a/packages/core-macro/src/router.rs b/packages/core-macro/src/router.rs index d64fe2aea..3edad75e3 100644 --- a/packages/core-macro/src/router.rs +++ b/packages/core-macro/src/router.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + use proc_macro2::TokenStream; use quote::quote; use syn::parse::{Parse, ParseStream}; @@ -109,85 +111,85 @@ fn parse_variants_attributes( } impl Routable { - fn build_from_path(&self) -> TokenStream { - let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| { - let ident = &variant.ident; - let right = match &variant.fields { - Fields::Unit => quote! { Self::#ident }, - Fields::Named(field) => { - let fields = field.named.iter().map(|it| { - //named fields have idents - it.ident.as_ref().unwrap() - }); - quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } } - } - Fields::Unnamed(_) => unreachable!(), // already checked - }; + // fn build_from_path(&self) -> TokenStream { + // let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| { + // let ident = &variant.ident; + // let right = match &variant.fields { + // Fields::Unit => quote! { Self::#ident }, + // Fields::Named(field) => { + // let fields = field.named.iter().map(|it| { + // //named fields have idents + // it.ident.as_ref().unwrap() + // }); + // quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } } + // } + // Fields::Unnamed(_) => unreachable!(), // already checked + // }; - let left = self.ats.get(i).unwrap(); - quote! { - #left => ::std::option::Option::Some(#right) - } - }); + // let left = self.ats.get(i).unwrap(); + // quote! { + // #left => ::std::option::Option::Some(#right) + // } + // }); - quote! { - fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option { - match path { - #(#from_path_matches),*, - _ => ::std::option::Option::None, - } - } - } - } + // quote! { + // fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option { + // match path { + // #(#from_path_matches),*, + // _ => ::std::option::Option::None, + // } + // } + // } + // } - fn build_to_path(&self) -> TokenStream { - let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| { - let ident = &variant.ident; - let mut right = self.ats.get(i).unwrap().value(); + // fn build_to_path(&self) -> TokenStream { + // let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| { + // let ident = &variant.ident; + // let mut right = self.ats.get(i).unwrap().value(); - match &variant.fields { - Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) }, - Fields::Named(field) => { - let fields = field - .named - .iter() - .map(|it| it.ident.as_ref().unwrap()) - .collect::>(); + // match &variant.fields { + // Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) }, + // Fields::Named(field) => { + // let fields = field + // .named + // .iter() + // .map(|it| it.ident.as_ref().unwrap()) + // .collect::>(); - for field in fields.iter() { - // :param -> {param} - // so we can pass it to `format!("...", param)` - right = right.replace(&format!(":{}", field), &format!("{{{}}}", field)) - } + // for field in fields.iter() { + // // :param -> {param} + // // so we can pass it to `format!("...", param)` + // right = right.replace(&format!(":{}", field), &format!("{{{}}}", field)) + // } - quote! { - Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*) - } - } - Fields::Unnamed(_) => unreachable!(), // already checked - } - }); + // quote! { + // Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*) + // } + // } + // Fields::Unnamed(_) => unreachable!(), // already checked + // } + // }); - quote! { - fn to_path(&self) -> ::std::string::String { - match self { - #(#to_path_matches),*, - } - } - } - } + // quote! { + // fn to_path(&self) -> ::std::string::String { + // match self { + // #(#to_path_matches),*, + // } + // } + // } + // } } pub fn routable_derive_impl(input: Routable) -> TokenStream { let Routable { - ats, - not_found_route, - ident, + // ats, + // not_found_route, + // ident, .. } = &input; - let from_path = input.build_from_path(); - let to_path = input.build_to_path(); + // let from_path = input.build_from_path(); + // let to_path = input.build_to_path(); quote! { // #[automatically_derived] diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index fb5b51149..f895ec814 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -28,7 +28,7 @@ smallvec = "1.6.1" slab = "0.4.3" -futures-channel = "0.3.16" +futures-channel = "0.3.18" # used for noderefs once_cell = "1.8.0" diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 0436efbcf..13266d049 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -473,6 +473,12 @@ impl<'bump> DiffState<'bump> { // TODO: add noderefs to current noderef list Noderefs let _new_component = self.scopes.get_scope(&new_idx).unwrap(); + log::debug!( + "initializing component {:?} with height {:?}", + new_idx, + height + 1 + ); + // Run the scope for one iteration to initialize it if self.scopes.run_scope(&new_idx) { // Take the node that was just generated from running the component diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index 69034e004..1b2643d20 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -315,18 +315,10 @@ impl ScopeArena { let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") }; - // log::debug!("found scope, about to run: {:?}", id); - // Safety: // - We dropped the listeners, so no more &mut T can be used while these are held // - All children nodes that rely on &mut T are replaced with a new reference - scope.hook_vals.get_mut().drain(..).for_each(|state| { - let as_mut = unsafe { &mut *state }; - let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) }; - drop(boxed); - }); scope.hook_idx.set(0); - scope.hook_arena.reset(); // Safety: // - We've dropped all references to the wip bump frame with "ensure_drop_safety" diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 5d07731f1..b95cd45a4 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -10,7 +10,7 @@ use indexmap::IndexSet; use smallvec::SmallVec; use std::{any::Any, collections::VecDeque, pin::Pin, sync::Arc, task::Poll}; -/// A virtual node system that progresses user events and diffs UI trees. +/// A virtual node s ystem that progresses user events and diffs UI trees. /// /// /// ## Guide @@ -347,8 +347,13 @@ impl VirtualDom { pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec { let mut committed_mutations = vec![]; + log::debug!( + "Working with deadline. \nDirty scopes: {:?}. \nPending messages: {:?}", + self.dirty_scopes, + self.pending_messages + ); + while !self.dirty_scopes.is_empty() { - // log::debug!("working with deadline"); let scopes = &self.scopes; let mut diff_state = DiffState::new(scopes); @@ -357,6 +362,7 @@ impl VirtualDom { // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height self.dirty_scopes .retain(|id| scopes.get_scope(id).is_some()); + self.dirty_scopes.sort_by(|a, b| { let h1 = scopes.get_scope(a).unwrap().height; let h2 = scopes.get_scope(b).unwrap().height; @@ -364,6 +370,7 @@ impl VirtualDom { }); if let Some(scopeid) = self.dirty_scopes.pop() { + log::debug!("Analyzing dirty scope {:?}", scopeid); if !ran_scopes.contains(&scopeid) { ran_scopes.insert(scopeid); @@ -372,6 +379,7 @@ impl VirtualDom { self.scopes.wip_head(&scopeid), self.scopes.fin_head(&scopeid), ); + log::debug!("Diffing old: {:?}, new: {:?}", old, new); diff_state.stack.push(DiffInstruction::Diff { new, old }); diff_state.stack.scope_stack.push(scopeid); @@ -391,9 +399,11 @@ impl VirtualDom { for scope in seen_scopes { self.dirty_scopes.remove(&scope); } + log::debug!("Working generated mutations: {:?}", mutations); committed_mutations.push(mutations); } else { + log::debug!("Could not finish work in time"); // leave the work in an incomplete state return committed_mutations; } @@ -535,6 +545,7 @@ impl VirtualDom { } } +#[derive(Debug)] pub enum SchedulerMsg { // events from the host UiEvent(UserEvent), diff --git a/packages/coroutines/Cargo.toml b/packages/coroutines/Cargo.toml new file mode 100644 index 000000000..1b2632366 --- /dev/null +++ b/packages/coroutines/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "dioxus-coroutines" +version = "0.0.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] \ No newline at end of file diff --git a/packages/coroutines/src/lib.rs b/packages/coroutines/src/lib.rs new file mode 100644 index 000000000..1b4a90c93 --- /dev/null +++ b/packages/coroutines/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let result = 2 + 2; + assert_eq!(result, 4); + } +} diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index a2bffd8de..40ac7b6ed 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -17,7 +17,7 @@ thiserror = "1.0.23" log = "0.4.13" html-escape = "0.2.9" wry = "0.12.2" -futures-channel = "0.3.16" +futures-channel = "0.3.18" tokio = { version = "1.12.0", features = [ "sync", "rt-multi-thread", diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index 3e8b73d6f..be0b7847b 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -11,14 +11,16 @@ use dioxus_hooks::*; use dioxus_html as dioxus_elements; fn main() { + simple_logger::init().unwrap(); dioxus_desktop::launch(App, |c| c); } static App: FC<()> = |cx, props| { let mut count = use_state(cx, || 0); + log::debug!("count is {:?}", count); cx.push_task(|| async move { - tokio::time::sleep(Duration::from_millis(100)).await; + tokio::time::sleep(Duration::from_millis(1000)).await; count += 1; }); diff --git a/packages/desktop/src/cfg.rs b/packages/desktop/src/cfg.rs index 9b74a048a..49d781465 100644 --- a/packages/desktop/src/cfg.rs +++ b/packages/desktop/src/cfg.rs @@ -1,14 +1,7 @@ -use std::ops::{Deref, DerefMut}; - use dioxus_core::DomEdit; use wry::{ - application::{ - error::OsError, - event_loop::{EventLoop, EventLoopWindowTarget}, - menu::MenuBar, - window::{Fullscreen, Icon, Window, WindowBuilder}, - }, - webview::{RpcRequest, RpcResponse, WebView}, + application::{event_loop::EventLoop, window::WindowBuilder}, + webview::WebView, }; pub struct DesktopConfig<'a> { @@ -22,7 +15,7 @@ impl<'a> DesktopConfig<'a> { /// Initializes a new `WindowBuilder` with default values. #[inline] pub fn new() -> Self { - let mut window = WindowBuilder::new().with_title("Dioxus app"); + let window = WindowBuilder::new().with_title("Dioxus app"); Self { event_handler: None, window, @@ -41,13 +34,24 @@ impl<'a> DesktopConfig<'a> { self } - pub fn with_window(&mut self, f: impl FnOnce(WindowBuilder) -> WindowBuilder) -> &mut Self { + pub fn with_window( + &mut self, + configure: impl FnOnce(WindowBuilder) -> WindowBuilder, + ) -> &mut Self { // gots to do a swap because the window builder only takes itself as muy self // I wish more people knew about returning &mut Self let mut builder = WindowBuilder::default().with_title("Dioxus App"); std::mem::swap(&mut self.window, &mut builder); - builder = f(builder); + builder = configure(builder); std::mem::swap(&mut self.window, &mut builder); self } + + pub fn with_event_handler( + &mut self, + handler: impl Fn(&mut EventLoop<()>, &mut WebView) + 'static, + ) -> &mut Self { + self.event_handler = Some(Box::new(handler)); + self + } } diff --git a/packages/desktop/src/dom.rs b/packages/desktop/src/dom.rs deleted file mode 100644 index 09323d40b..000000000 --- a/packages/desktop/src/dom.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! webview dom - -use dioxus_core::DomEdit; - -// pub struct WebviewRegistry {} - -// impl WebviewRegistry { -// pub fn new() -> Self { -// Self {} -// } -// } - -pub struct WebviewDom<'bump> { - pub edits: Vec>, - pub node_counter: u64, - // pub registry: WebviewRegistry, -} -impl WebviewDom<'_> { - pub fn new() -> Self { - Self { - edits: Vec::new(), - node_counter: 0, - // registry, - } - } - - // // Finish using the dom (for its edit list) and give back the node and event registry - // pub fn consume(self) -> WebviewRegistry { - // self.registry - // } -} diff --git a/packages/desktop/src/err.rs b/packages/desktop/src/err.rs deleted file mode 100644 index 8b1378917..000000000 --- a/packages/desktop/src/err.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/packages/desktop/src/escape.rs b/packages/desktop/src/escape.rs index 654ecbeeb..386a82b44 100644 --- a/packages/desktop/src/escape.rs +++ b/packages/desktop/src/escape.rs @@ -16,7 +16,8 @@ use std::fmt::{self, Write}; /// /// view.eval(&format!("callback({});", web_view::escape(string))); /// ``` -pub fn escape(string: &str) -> Escaper { +#[allow(unused)] +pub fn escape_js_string(string: &str) -> Escaper { Escaper(string) } @@ -76,6 +77,6 @@ impl<'a> fmt::Display for Escaper<'a> { #[test] fn test() { let plain = "ABC \n\r' abc \\ \u{2028} \u{2029}123"; - let escaped = escape(plain).to_string(); + let escaped = escape_js_string(plain).to_string(); assert!(escaped == "'ABC \\n\\r\\' abc \\\\ \\u2028 \\u2029123'"); } diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index ec5c6aebd..8f86c3270 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -1,10 +1,10 @@ //! Convert a serialized event to an event Trigger //! +use std::any::Any; use std::sync::Arc; -use std::{any::Any, rc::Rc}; -use dioxus_core::{ElementId, EventPriority, ScopeId, UserEvent}; +use dioxus_core::{ElementId, EventPriority, UserEvent}; use dioxus_html::on::*; #[derive(serde::Serialize, serde::Deserialize)] @@ -16,11 +16,11 @@ struct ImEvent { } pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent { - let mut ims: Vec = serde_json::from_value(val).unwrap(); + let ims: Vec = serde_json::from_value(val).unwrap(); + let ImEvent { event, mounted_dom_id, - // scope, contents, } = ims.into_iter().next().unwrap(); diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 1e4290590..9fb86b907 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -3,41 +3,31 @@ //! Render the Dioxus VirtualDom using the platform's native WebView implementation. //! -use std::borrow::BorrowMut; -use std::cell::{Cell, RefCell}; -use std::collections::{HashMap, VecDeque}; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; -use std::sync::atomic::AtomicBool; -use std::sync::mpsc::channel; -use std::sync::{Arc, RwLock}; +mod cfg; +mod escape; +mod events; +// mod desktop_context; use cfg::DesktopConfig; use dioxus_core::*; -use serde::{Deserialize, Serialize}; - -pub use wry; - -use wry::application::accelerator::{Accelerator, SysMods}; -use wry::application::event::{ElementState, Event, StartCause, WindowEvent}; -use wry::application::event_loop::{self, ControlFlow, EventLoop, EventLoopWindowTarget}; -use wry::application::keyboard::{Key, KeyCode, ModifiersState}; -use wry::application::menu::{MenuBar, MenuItem, MenuItemAttributes}; -use wry::application::window::{Fullscreen, WindowId}; -use wry::webview::{WebView, WebViewBuilder}; +use std::{ + collections::{HashMap, VecDeque}, + sync::atomic::AtomicBool, + sync::{Arc, RwLock}, +}; +use tokio::task::LocalSet; use wry::{ - application::menu, - application::window::{Window, WindowBuilder}, - webview::{RpcRequest, RpcResponse}, + application::{ + accelerator::{Accelerator, SysMods}, + event::{Event, StartCause, WindowEvent}, + event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget}, + keyboard::{KeyCode, ModifiersState}, + menu::{MenuBar, MenuItem}, + window::{Window, WindowId}, + }, + webview::RpcRequest, + webview::{WebView, WebViewBuilder}, }; - -mod cfg; -mod desktop_context; -mod dom; -mod escape; -mod events; - -static HTML_CONTENT: &'static str = include_str!("./index.html"); pub fn launch( root: FC<()>, @@ -54,7 +44,7 @@ pub fn launch_with_props( run(root, props, builder) } -#[derive(Serialize)] +#[derive(serde::Serialize)] struct Response<'a> { pre_rendered: Option, edits: Vec>, @@ -65,93 +55,43 @@ pub fn run( props: T, user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, ) { - // Generate the config - let mut cfg = DesktopConfig::new(); - user_builder(&mut cfg); - let DesktopConfig { - window: window_cfg, - manual_edits, - pre_rendered, - .. - } = cfg; - - // All of our webview windows are stored in a way that we can look them up later - // The "DesktopContext" will provide functionality for spawning these windows - let mut webviews = HashMap::::new(); - let event_loop = EventLoop::new(); - - let props_shared = Cell::new(Some(props)); - - // create local modifier state - let modifiers = ModifiersState::default(); + let mut desktop_cfg = DesktopConfig::new(); + user_builder(&mut desktop_cfg); + let mut state = DesktopController::new_on_tokio(root, props); let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ); - - let edit_queue = Arc::new(RwLock::new(VecDeque::new())); - let is_ready: Arc = Default::default(); - - let mut frame = 0; + let modifiers = ModifiersState::default(); + let event_loop = EventLoop::new(); event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; match window_event { - Event::NewEvents(StartCause::Init) => { - let window = create_window(event_loop, &window_cfg); - let window_id = window.id(); - let sender = - launch_vdom_with_tokio(root, props_shared.take().unwrap(), edit_queue.clone()); - let webview = create_webview(window, is_ready.clone(), sender); - webviews.insert(window_id, webview); - } + Event::NewEvents(StartCause::Init) => state.new_window(&desktop_cfg, event_loop), Event::WindowEvent { event, window_id, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, - WindowEvent::Destroyed { .. } => { - webviews.remove(&window_id); - if webviews.is_empty() { - *control_flow = ControlFlow::Exit; - } - } - WindowEvent::Moved(pos) => { - // - } + WindowEvent::Destroyed { .. } => state.close_window(window_id, control_flow), WindowEvent::KeyboardInput { event, .. } => { if quit_hotkey.matches(&modifiers, &event.physical_key) { - webviews.remove(&window_id); - if webviews.is_empty() { - *control_flow = ControlFlow::Exit; - } + state.close_window(window_id, control_flow); } } WindowEvent::Resized(_) | WindowEvent::Moved(_) => { - if let Some(view) = webviews.get_mut(&window_id) { + if let Some(view) = state.webviews.get_mut(&window_id) { let _ = view.resize(); } } - // TODO: we want to shuttle all of these events into the user's app + + // TODO: we want to shuttle all of these events into the user's app or provide some handler _ => {} }, - Event::MainEventsCleared => { - // I hate this ready hack but it's needed to wait for the "onload" to occur - // We can't run any initializion scripts because the window isn't ready yet? - if is_ready.load(std::sync::atomic::Ordering::Relaxed) { - let mut queue = edit_queue.write().unwrap(); - let (id, view) = webviews.iter_mut().next().unwrap(); - while let Some(edit) = queue.pop_back() { - view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) - .unwrap(); - } - } else { - println!("waiting for onload {:?}", frame); - frame += 1; - } - } + Event::MainEventsCleared => state.try_load_ready_webviews(), Event::Resumed => {} Event::Suspended => {} Event::LoopDestroyed => {} @@ -161,121 +101,160 @@ pub fn run( }) } -// Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom. -pub(crate) fn launch_vdom_with_tokio( - root: FC

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

, props: P) -> Self { + let edit_queue = Arc::new(RwLock::new(VecDeque::new())); + let pending_edits = edit_queue.clone(); + + let (sender, receiver) = futures_channel::mpsc::unbounded::(); + let return_sender = sender.clone(); + + std::thread::spawn(move || { + // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + + runtime.block_on(async move { + // LocalSet::new().block_on(&runtime, async move { + let mut dom = + VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver); + + let edits = dom.rebuild(); + + edit_queue + .write() + .unwrap() + .push_front(serde_json::to_string(&edits.edits).unwrap()); + + loop { + dom.wait_for_work().await; + let mut muts = dom.work_with_deadline(|| false); + while let Some(edit) = muts.pop() { + log::debug!("found mutations {:?}", muts); + edit_queue + .write() + .unwrap() + .push_front(serde_json::to_string(&edit.edits).unwrap()); + } + } + }) + }); + + Self { + pending_edits, + sender: return_sender, + + webviews: HashMap::new(), + is_ready: Arc::new(AtomicBool::new(false)), + quit_app_on_close: true, + } + } + + pub fn new_window(&mut self, cfg: &DesktopConfig, event_loop: &EventLoopWindowTarget<()>) { + let builder = cfg.window.clone().with_menu({ + // create main menubar menu + let mut menu_bar_menu = MenuBar::new(); + + // create `first_menu` + let mut first_menu = MenuBar::new(); + + first_menu.add_native_item(MenuItem::About("Todos".to_string())); + first_menu.add_native_item(MenuItem::Services); + first_menu.add_native_item(MenuItem::Separator); + first_menu.add_native_item(MenuItem::Hide); + first_menu.add_native_item(MenuItem::HideOthers); + first_menu.add_native_item(MenuItem::ShowAll); + + first_menu.add_native_item(MenuItem::Quit); + first_menu.add_native_item(MenuItem::CloseWindow); + + // create second menu + let mut second_menu = MenuBar::new(); + + // second_menu.add_submenu("Sub menu", true, my_sub_menu); + second_menu.add_native_item(MenuItem::Copy); + second_menu.add_native_item(MenuItem::Paste); + second_menu.add_native_item(MenuItem::SelectAll); + + menu_bar_menu.add_submenu("First menu", true, first_menu); + menu_bar_menu.add_submenu("Second menu", true, second_menu); + + menu_bar_menu + }); + + let window = builder.build(event_loop).unwrap(); + let window_id = window.id(); + + let (is_ready, sender) = (self.is_ready.clone(), self.sender.clone()); + + let webview = WebViewBuilder::new(window) + .unwrap() + .with_url("wry://index.html") + .unwrap() + .with_rpc_handler(move |_window: &Window, req: RpcRequest| { + match req.method.as_str() { + "user_event" => { + let event = events::trigger_from_serialized(req.params.unwrap()); + log::debug!("User event: {:?}", event); + sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap(); + } + "initialize" => { + is_ready.store(true, std::sync::atomic::Ordering::Relaxed); + } + _ => {} + } + // response always driven through eval. + // unfortunately, it seems to be pretty slow, so we might want to look into an RPC form + None + }) + // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case" + // For now, we only serve two pieces of content which get included as bytes into the final binary. + .with_custom_protocol("wry".into(), move |request| { + let path = request.uri().replace("wry://", ""); + let (data, meta) = match path.as_str() { + "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"), + "index.html/index.js" => { + (include_bytes!("./index.js").to_vec(), "text/javascript") + } + _ => unimplemented!("path {}", path), + }; + + wry::http::ResponseBuilder::new().mimetype(meta).body(data) + }) .build() .unwrap(); - runtime.block_on(async move { - let mut dom = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver); + self.webviews.insert(window_id, webview); + } - let edits = dom.rebuild(); + pub fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) { + self.webviews.remove(&window_id); - edit_queue - .write() - .unwrap() - .push_front(serde_json::to_string(&edits.edits).unwrap()); + if self.webviews.is_empty() && self.quit_app_on_close { + *control_flow = ControlFlow::Exit; + } + } - loop { - dom.wait_for_work().await; - let mut muts = dom.work_with_deadline(|| false); - while let Some(edit) = muts.pop() { - edit_queue - .write() - .unwrap() - .push_front(serde_json::to_string(&edit.edits).unwrap()); - } + pub fn try_load_ready_webviews(&mut self) { + if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) { + let mut queue = self.pending_edits.write().unwrap(); + let (_id, view) = self.webviews.iter_mut().next().unwrap(); + while let Some(edit) = queue.pop_back() { + view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) + .unwrap(); } - }) - }); - - return_sender -} - -fn build_menu() -> MenuBar { - // create main menubar menu - let mut menu_bar_menu = MenuBar::new(); - - // create `first_menu` - let mut first_menu = MenuBar::new(); - - first_menu.add_native_item(MenuItem::About("Todos".to_string())); - first_menu.add_native_item(MenuItem::Services); - first_menu.add_native_item(MenuItem::Separator); - first_menu.add_native_item(MenuItem::Hide); - first_menu.add_native_item(MenuItem::HideOthers); - first_menu.add_native_item(MenuItem::ShowAll); - - first_menu.add_native_item(MenuItem::Quit); - first_menu.add_native_item(MenuItem::CloseWindow); - - // create second menu - let mut second_menu = MenuBar::new(); - - // second_menu.add_submenu("Sub menu", true, my_sub_menu); - second_menu.add_native_item(MenuItem::Copy); - second_menu.add_native_item(MenuItem::Paste); - second_menu.add_native_item(MenuItem::SelectAll); - - menu_bar_menu.add_submenu("First menu", true, first_menu); - menu_bar_menu.add_submenu("Second menu", true, second_menu); - - menu_bar_menu -} - -fn create_window(event_loop: &EventLoopWindowTarget<()>, cfg: &WindowBuilder) -> Window { - let builder = cfg.clone().with_menu(build_menu()); - builder.build(event_loop).unwrap() -} - -fn create_webview( - window: Window, - is_ready: Arc, - sender: futures_channel::mpsc::UnboundedSender, -) -> WebView { - WebViewBuilder::new(window) - .unwrap() - .with_url("wry://index.html") - .unwrap() - .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| { - match req.method.as_str() { - "user_event" => { - let event = events::trigger_from_serialized(req.params.unwrap()); - log::debug!("User event: {:?}", event); - sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap(); - } - "initialize" => { - is_ready.store(true, std::sync::atomic::Ordering::Relaxed); - } - _ => {} - } - // always driven through eval - None - }) - // .with_initialization_script(include_str!("./index.js")) - // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case" - // For now, we only serve two pieces of content which get included as bytes into the final binary. - .with_custom_protocol("wry".into(), move |request| { - let path = request.uri().replace("wry://", ""); - let (data, meta) = match path.as_str() { - "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"), - "index.html/index.js" => (include_bytes!("./index.js").to_vec(), "text/javascript"), - _ => unimplemented!("path {}", path), - }; - - wry::http::ResponseBuilder::new().mimetype(meta).body(data) - }) - .build() - .unwrap() + } + } } diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml index c8c304782..08feb8535 100644 --- a/packages/hooks/Cargo.toml +++ b/packages/hooks/Cargo.toml @@ -7,4 +7,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../../packages/core", version = "0.1.2" } +dioxus-core = { path = "../../packages/core", version = "0.1.3" } +futures = "0.3.18" diff --git a/packages/hooks/README.md b/packages/hooks/README.md index d0602c3dd..39ab816b5 100644 --- a/packages/hooks/README.md +++ b/packages/hooks/README.md @@ -19,7 +19,7 @@ You can always use it "normally" with the `split` method: let value = use_state(cx, || 10); // "Classic" usage: -let (value, set_value) = use_state(cx, || 0).classic(); +let (value, set_value) = use_state(cx, || 0).split(); ``` ## use_ref diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index 3c5de0f2b..c09c287bb 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -6,3 +6,6 @@ pub use useref::*; mod use_shared_state; pub use use_shared_state::*; + +mod usecoroutine; +pub use usecoroutine::*; diff --git a/packages/hooks/src/usecoroutine.rs b/packages/hooks/src/usecoroutine.rs new file mode 100644 index 000000000..773dfeae8 --- /dev/null +++ b/packages/hooks/src/usecoroutine.rs @@ -0,0 +1,76 @@ +use dioxus_core::Context; +use futures::Future; +use std::{ + borrow::Borrow, + cell::{Cell, RefCell}, + pin::Pin, + rc::Rc, +}; + +pub fn use_coroutine<'a, F: Future + 'a>( + cx: Context<'a>, + f: impl FnOnce() -> F + 'a, +) -> CoroutineHandle { + // + cx.use_hook( + move |_| State { + running: Default::default(), + fut: Default::default(), + submit: Default::default(), + }, + |state| { + let fut_slot = state.fut.clone(); + let running = state.running.clone(); + let submit: Box = Box::new(move || { + let g = async move { + running.set(true); + f().await; + running.set(false); + }; + let p: Pin>> = Box::pin(g); + fut_slot + .borrow_mut() + .replace(unsafe { std::mem::transmute(p) }); + }); + + let submit = unsafe { std::mem::transmute(submit) }; + state.submit.get_mut().replace(submit); + + if state.running.get() { + let mut fut = state.fut.borrow_mut(); + cx.push_task(|| fut.as_mut().unwrap().as_mut()); + } else { + // make sure to drop the old future + if let Some(fut) = state.fut.borrow_mut().take() { + // + } + } + CoroutineHandle { cx, inner: state } + }, + ) +} + +struct State { + running: Rc>, + submit: RefCell>>, + fut: Rc>>>>>, +} + +pub struct CoroutineHandle<'a> { + cx: Context<'a>, + inner: &'a State, +} + +impl<'a> CoroutineHandle<'a> { + pub fn start(&self) { + if self.inner.running.get() { + return; + } + if let Some(submit) = self.inner.submit.borrow_mut().take() { + submit(); + let mut fut = self.inner.fut.borrow_mut(); + self.cx.push_task(|| fut.as_mut().unwrap().as_mut()); + } + } + pub fn resume(&self) {} +} diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index fb8d3e4af..d215c87a7 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -1,7 +1,7 @@ use dioxus_core::prelude::Context; use std::{ cell::{Cell, Ref, RefCell, RefMut}, - fmt::Display, + fmt::{Debug, Display}, ops::Not, rc::Rc, }; @@ -54,11 +54,14 @@ pub fn use_state<'a, T: 'static>( initial_state_fn: impl FnOnce() -> T, ) -> UseState<'a, T> { cx.use_hook( - move |_| UseStateInner { - current_val: initial_state_fn(), - update_callback: cx.schedule_update(), - wip: Rc::new(RefCell::new(None)), - update_scheuled: Cell::new(false), + move |_| { + // + UseStateInner { + current_val: initial_state_fn(), + update_callback: cx.schedule_update(), + wip: Rc::new(RefCell::new(None)), + update_scheuled: Cell::new(false), + } }, move |hook| { hook.update_scheuled.set(false); @@ -91,7 +94,11 @@ where UseState { inner: self.inner } } } - +impl Debug for UseState<'_, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.inner.current_val) + } +} impl<'a, T: 'static> UseState<'a, T> { /// Tell the Dioxus Scheduler that we need to be processed pub fn needs_update(&self) { diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 3f6992374..6eea36766 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -22,7 +22,7 @@ web-sys = { version = "0.3", features = [ "Window", ], optional = true } -serde = "1" +serde = "1.0.130" serde_urlencoded = "0.7" @@ -30,13 +30,16 @@ wasm-bindgen = "0.2" js-sys = "0.3" gloo = "0.4" route-recognizer = "0.3.1" +url = "2.2.2" +url_serde = "0.2.0" [features] -default = ["web"] +default = ["web", "derive"] web = ["web-sys"] desktop = [] mobile = [] +derive = [] [dev-dependencies] console_error_panic_hook = "0.1.7" diff --git a/packages/router/README.md b/packages/router/README.md index 902bfdb17..598c4f6a9 100644 --- a/packages/router/README.md +++ b/packages/router/README.md @@ -9,7 +9,7 @@ Using the router should feel similar to tide's routing framework where an "addre Here's an example of how to use the router hook: ```rust -#[derive(Clone, Routable)] +#[derive(Clone, PartialEq, Serialize, Deserialize, Routable)] enum AppRoute { Home, Posts,