66 files changed
+1272
-569
No files matched your search
@@ -17,6 +17,7 @@ dioxus-desktop = { path = "./packages/desktop", optional = true }
|
||||
dioxus-hooks = { path = "./packages/hooks", optional = true }
|
||||
dioxus-ssr = { path = "./packages/ssr", optional = true }
|
||||
dioxus-mobile = { path = "./packages/mobile", optional = true }
|
||||
dioxus-liveview = { path = "./packages/liveview", optional = true }
|
||||
|
||||
[features]
|
||||
# core
|
||||
@@ -26,6 +27,7 @@ macro = ["dioxus-core-macro"]
|
||||
hooks = ["dioxus-hooks"]
|
||||
html = ["dioxus-html"]
|
||||
router = ["dioxus-router"]
|
||||
liveview = ["dioxus-liveview"]
|
||||
|
||||
# utilities
|
||||
atoms = []
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ async fn main() {
|
||||
dioxus::desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
pub static App: FC<()> = |cx, _| {
|
||||
pub static App: Component<()> = |cx, _| {
|
||||
let count = use_state(cx, || 0);
|
||||
let mut direction = use_state(cx, || 1);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ fn main() {
|
||||
dioxus::desktop::launch(APP, |cfg| cfg);
|
||||
}
|
||||
|
||||
const APP: FC<()> = |cx, _| {
|
||||
const APP: Component<()> = |cx, _| {
|
||||
let cur_val = use_state(cx, || 0.0_f64);
|
||||
let operator = use_state(cx, || None as Option<&'static str>);
|
||||
let display_value = use_state(cx, || String::from(""));
|
||||
|
||||
@@ -27,7 +27,7 @@ fn main() {
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let p1 = use_state(cx, || 0);
|
||||
let p2 = use_state(cx, || 0);
|
||||
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ pub struct Client {
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, _| {
|
||||
static App: Component<()> = |cx, _| {
|
||||
let mut clients = use_ref(cx, || vec![] as Vec<Client>);
|
||||
let mut scene = use_state(cx, || Scene::ClientsList);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ fn main() {
|
||||
});
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let file_manager = use_ref(cx, || Files::new());
|
||||
let files = file_manager.read();
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ impl Label {
|
||||
}
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, _props| {
|
||||
static App: Component<()> = |cx, _props| {
|
||||
let mut items = use_ref(cx, || vec![]);
|
||||
let mut selected = use_state(cx, || None);
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ fn main() {
|
||||
dioxus::desktop::launch(App, |c| c.with_prerendered(content));
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let mut val = use_state(cx, || 0);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
||||
@@ -36,7 +36,7 @@ fn main() {
|
||||
dioxus_desktop::run(APP, (), |c| c.with_edits(edits));
|
||||
}
|
||||
|
||||
const APP: FC<()> = |(cx, _props)| {
|
||||
const APP: Component<()> = |(cx, _props)| {
|
||||
rsx!(cx, div {
|
||||
"some app"
|
||||
})
|
||||
|
||||
@@ -31,7 +31,7 @@ fn main() {
|
||||
});
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let state = use_ref(cx, || Calculator::new());
|
||||
|
||||
let clear_display = state.read().display_value.eq("0");
|
||||
|
||||
@@ -11,7 +11,7 @@ fn main() {
|
||||
dioxus::desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
pub static App: FC<()> = |cx, _| {
|
||||
pub static App: Component<()> = |cx, _| {
|
||||
let state = use_state(cx, PlayerState::new);
|
||||
|
||||
let is_playing = state.is_playing();
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ fn main() {
|
||||
dioxus::desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let mut count = use_state(cx, || 0);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ pub enum Route {
|
||||
NotFound,
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let route = use_router(cx, Route::parse)?;
|
||||
|
||||
cx.render(rsx! {
|
||||
|
||||
@@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None;
|
||||
use baller::Baller;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub static Example: FC<()> = |cx, props| {
|
||||
pub static Example: Component<()> = |cx, props| {
|
||||
let formatting = "formatting!";
|
||||
let formatting_tuple = ("a", "b");
|
||||
let lazy_fmt = format_args!("lazily formatted text");
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ fn main() {
|
||||
println!("{}", ssr::render_vdom(&vdom, |c| c));
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
h1 { "Title" }
|
||||
|
||||
@@ -14,7 +14,7 @@ fn main() {
|
||||
|
||||
const STYLE: &str = "body {overflow:hidden;}";
|
||||
|
||||
pub static App: FC<()> = |cx, props| {
|
||||
pub static App: Component<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
div { class: "overflow-hidden"
|
||||
style { "{STYLE}" }
|
||||
@@ -30,7 +30,7 @@ pub static App: FC<()> = |cx, props| {
|
||||
))
|
||||
};
|
||||
|
||||
pub static Header: FC<()> = |cx, props| {
|
||||
pub static Header: Component<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
header { class: "text-gray-400 bg-gray-900 body-font"
|
||||
@@ -56,7 +56,7 @@ pub static Header: FC<()> = |cx, props| {
|
||||
})
|
||||
};
|
||||
|
||||
pub static Hero: FC<()> = |cx, props| {
|
||||
pub static Hero: Component<()> = |cx, props| {
|
||||
//
|
||||
cx.render(rsx! {
|
||||
section{ class: "text-gray-400 bg-gray-900 body-font"
|
||||
@@ -94,7 +94,7 @@ pub static Hero: FC<()> = |cx, props| {
|
||||
}
|
||||
})
|
||||
};
|
||||
pub static Entry: FC<()> = |cx, props| {
|
||||
pub static Entry: Component<()> = |cx, props| {
|
||||
//
|
||||
cx.render(rsx! {
|
||||
section{ class: "text-gray-400 bg-gray-900 body-font"
|
||||
@@ -107,7 +107,7 @@ pub static Entry: FC<()> = |cx, props| {
|
||||
})
|
||||
};
|
||||
|
||||
pub static StacksIcon: FC<()> = |cx, props| {
|
||||
pub static StacksIcon: Component<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
svg {
|
||||
// xmlns: "http://www.w3.org/2000/svg"
|
||||
@@ -122,7 +122,7 @@ pub static StacksIcon: FC<()> = |cx, props| {
|
||||
}
|
||||
))
|
||||
};
|
||||
pub static RightArrowIcon: FC<()> = |cx, props| {
|
||||
pub static RightArrowIcon: Component<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
svg {
|
||||
fill: "none"
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ fn main() {
|
||||
dioxus::desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let mut count = use_state(cx, || 0);
|
||||
|
||||
cx.push_task(async move {
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ pub struct TodoItem {
|
||||
}
|
||||
|
||||
const STYLE: &str = include_str!("./assets/todomvc.css");
|
||||
const App: FC<()> = |cx, props| {
|
||||
const App: Component<()> = |cx, props| {
|
||||
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);
|
||||
|
||||
@@ -12,7 +12,7 @@ fn main() {
|
||||
|
||||
const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather";
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
//
|
||||
let body = use_suspense(
|
||||
cx,
|
||||
@@ -40,7 +40,7 @@ static App: FC<()> = |cx, props| {
|
||||
#[derive(PartialEq, Props)]
|
||||
struct WeatherProps {}
|
||||
|
||||
static WeatherDisplay: FC<WeatherProps> = |cx, props| {
|
||||
static WeatherDisplay: Component<WeatherProps> = |cx, props| {
|
||||
//
|
||||
cx.render(rsx!(
|
||||
div { class: "flex items-center justify-center flex-col"
|
||||
|
||||
@@ -19,7 +19,7 @@ fn main() {
|
||||
dioxus::web::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let rows = (0..1_000).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
|
||||
@@ -16,7 +16,7 @@ fn main() {
|
||||
dioxus::web::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let mut count = use_state(cx, || 0);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
||||
@@ -23,7 +23,7 @@ criterion_group!(mbenches, create_rows);
|
||||
criterion_main!(mbenches);
|
||||
|
||||
fn create_rows(c: &mut Criterion) {
|
||||
static App: FC<()> = |cx, _| {
|
||||
static App: Component<()> = |cx, _| {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let rows = (0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
fn main() {
|
||||
let _ = VirtualDom::new(App);
|
||||
}
|
||||
|
||||
fn App(cx: Context, _props: &()) -> Element {
|
||||
//
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
Child {}
|
||||
}
|
||||
))
|
||||
}
|
||||
|
||||
struct ChildProps<'a> {
|
||||
click_handler: EventHandler<'a>,
|
||||
}
|
||||
|
||||
fn Child(cx: Context, _props: &()) -> Element {
|
||||
//
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
h1 {
|
||||
"Hello, World!"
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
@@ -20,7 +20,7 @@ use dioxus_html as dioxus_elements;
|
||||
use rand::prelude::*;
|
||||
|
||||
fn main() {
|
||||
static App: FC<()> = |cx, _| {
|
||||
static App: Component<()> = |cx, _| {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
let rows = (0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core_macro::*;
|
||||
@@ -7,8 +9,8 @@ fn main() {
|
||||
let _ = VirtualDom::new(Parent);
|
||||
}
|
||||
|
||||
fn Parent(cx: Context, props: &()) -> Element {
|
||||
let value = cx.use_hook(|_| String::new(), |f| &*f);
|
||||
fn Parent(cx: Context, _props: &()) -> Element {
|
||||
let value = cx.use_hook(|_| String::new(), |f| f);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
@@ -105,11 +105,11 @@ use DomEdit::*;
|
||||
///
|
||||
/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
|
||||
/// stack machines all the way down!
|
||||
pub struct DiffState<'bump> {
|
||||
scopes: &'bump ScopeArena,
|
||||
pub mutations: Mutations<'bump>,
|
||||
pub(crate) struct DiffState<'bump> {
|
||||
pub(crate) scopes: &'bump ScopeArena,
|
||||
pub(crate) mutations: Mutations<'bump>,
|
||||
pub(crate) stack: DiffStack<'bump>,
|
||||
pub force_diff: bool,
|
||||
pub(crate) force_diff: bool,
|
||||
}
|
||||
|
||||
impl<'bump> DiffState<'bump> {
|
||||
@@ -149,7 +149,7 @@ pub(crate) enum DiffInstruction<'a> {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum MountType<'a> {
|
||||
pub(crate) enum MountType<'a> {
|
||||
Absorb,
|
||||
Append,
|
||||
Replace { old: &'a VNode<'a> },
|
||||
@@ -159,9 +159,9 @@ pub enum MountType<'a> {
|
||||
|
||||
pub(crate) struct DiffStack<'bump> {
|
||||
pub(crate) instructions: Vec<DiffInstruction<'bump>>,
|
||||
nodes_created_stack: SmallVec<[usize; 10]>,
|
||||
pub scope_stack: SmallVec<[ScopeId; 5]>,
|
||||
pub element_stack: SmallVec<[ElementId; 10]>,
|
||||
pub(crate) nodes_created_stack: SmallVec<[usize; 10]>,
|
||||
pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
|
||||
pub(crate) element_stack: SmallVec<[ElementId; 10]>,
|
||||
}
|
||||
|
||||
impl<'bump> DiffStack<'bump> {
|
||||
|
||||
@@ -1,17 +1,6 @@
|
||||
#![allow(non_snake_case)]
|
||||
#![doc = include_str!("../README.md")]
|
||||
|
||||
/*
|
||||
Navigating this crate:
|
||||
- virtual_dom: the primary entrypoint for the crate
|
||||
- scheduler: the core interior logic called by the [`VirtualDom`]
|
||||
- nodes: the definition of VNodes, listeners, etc.
|
||||
- diff: the stackmachine-based diffing algorithm
|
||||
- hooks: foundational hooks that require crate-private APIs
|
||||
- mutations: DomEdits/NodeRefs and internal API to create them
|
||||
|
||||
Some utilities
|
||||
*/
|
||||
pub(crate) mod component;
|
||||
pub(crate) mod diff;
|
||||
pub(crate) mod lazynodes;
|
||||
@@ -23,7 +12,7 @@ pub(crate) mod virtual_dom;
|
||||
|
||||
pub(crate) mod innerlude {
|
||||
pub use crate::component::*;
|
||||
pub use crate::diff::*;
|
||||
pub(crate) use crate::diff::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::*;
|
||||
@@ -32,19 +21,21 @@ pub(crate) mod innerlude {
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
pub type Element = Option<VPortal>;
|
||||
pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> Element;
|
||||
pub type Component<P> = for<'a> fn(Context<'a>, &'a P) -> Element;
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode,
|
||||
LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId,
|
||||
UserEvent, VElement, VFragment, VNode, VirtualDom, FC,
|
||||
Attribute, Component, Context, DioxusElement, DomEdit, Element, ElementId, EventHandler,
|
||||
EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties,
|
||||
SchedulerMsg, ScopeId, UserEvent, VElement, VFragment, VNode, VirtualDom,
|
||||
};
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::component::{fc_to_builder, Fragment, Properties};
|
||||
pub use crate::innerlude::Context;
|
||||
pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, Scope, FC};
|
||||
pub use crate::innerlude::{
|
||||
Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, Scope,
|
||||
};
|
||||
pub use crate::nodes::VNode;
|
||||
pub use crate::VirtualDom;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ pub struct Mutations<'a> {
|
||||
pub edits: Vec<DomEdit<'a>>,
|
||||
pub dirty_scopes: FxHashSet<ScopeId>,
|
||||
pub refs: Vec<NodeRefMutation<'a>>,
|
||||
pub effects: Vec<&'a dyn FnMut()>,
|
||||
}
|
||||
|
||||
impl Debug for Mutations<'_> {
|
||||
@@ -113,7 +112,6 @@ impl<'a> Mutations<'a> {
|
||||
edits: Vec::new(),
|
||||
refs: Vec::new(),
|
||||
dirty_scopes: Default::default(),
|
||||
effects: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -338,9 +338,32 @@ pub struct Listener<'bump> {
|
||||
pub event: &'static str,
|
||||
|
||||
/// The actual callback that the user specified
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) callback:
|
||||
RefCell<Option<BumpBox<'bump, dyn FnMut(std::sync::Arc<dyn Any + Send + Sync>) + 'bump>>>,
|
||||
pub(crate) callback: EventHandler<'bump>,
|
||||
}
|
||||
|
||||
pub struct EventHandler<'bump> {
|
||||
pub callback: &'bump RefCell<Option<ListenerCallback<'bump>>>,
|
||||
}
|
||||
|
||||
impl EventHandler<'_> {
|
||||
pub fn call(&self, event: Arc<dyn Any + Send + Sync>) {
|
||||
if let Some(callback) = self.callback.borrow_mut().as_mut() {
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
pub fn release(&self) {
|
||||
self.callback.replace(None);
|
||||
}
|
||||
}
|
||||
type ListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'bump>;
|
||||
|
||||
impl Copy for EventHandler<'_> {}
|
||||
impl Clone for EventHandler<'_> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
callback: self.callback,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A cached node is a "pointer" to a "rendered" node in a particular scope
|
||||
@@ -608,15 +631,11 @@ impl<'a> NodeFactory<'a> {
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn listener(
|
||||
self,
|
||||
event: &'static str,
|
||||
callback: BumpBox<'a, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'a>,
|
||||
) -> Listener<'a> {
|
||||
pub fn listener(self, event: &'static str, callback: EventHandler<'a>) -> Listener<'a> {
|
||||
Listener {
|
||||
event,
|
||||
mounted_node: Cell::new(None),
|
||||
callback: RefCell::new(Some(callback)),
|
||||
callback,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+50
-50
@@ -7,6 +7,7 @@ use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
@@ -32,6 +33,14 @@ use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||
/// ```
|
||||
pub type Context<'a> = &'a Scope;
|
||||
|
||||
/// A component's unique identifier.
|
||||
///
|
||||
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused
|
||||
/// once a component has been unmounted.
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct ScopeId(pub usize);
|
||||
|
||||
/// Every component in Dioxus is represented by a `Scope`.
|
||||
///
|
||||
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
|
||||
@@ -44,7 +53,6 @@ pub type Context<'a> = &'a Scope;
|
||||
pub struct Scope {
|
||||
pub(crate) parent_scope: Option<*mut Scope>,
|
||||
|
||||
// parent element I think?
|
||||
pub(crate) container: ElementId,
|
||||
|
||||
pub(crate) our_arena_idx: ScopeId,
|
||||
@@ -75,17 +83,9 @@ pub struct Scope {
|
||||
pub struct SelfReferentialItems<'a> {
|
||||
pub(crate) listeners: Vec<&'a Listener<'a>>,
|
||||
pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
|
||||
pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
|
||||
pub(crate) tasks: Vec<Pin<BumpBox<'a, dyn Future<Output = ()>>>>,
|
||||
}
|
||||
|
||||
/// A component's unique identifier.
|
||||
///
|
||||
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused
|
||||
/// once a component has been unmounted.
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct ScopeId(pub usize);
|
||||
|
||||
// Public methods exposed to libraries and components
|
||||
impl Scope {
|
||||
/// Get the subtree ID that this scope belongs to.
|
||||
@@ -227,11 +227,11 @@ impl Scope {
|
||||
let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id));
|
||||
}
|
||||
|
||||
/// Get the [`ScopeId`] of a mounted component.
|
||||
///
|
||||
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
|
||||
pub fn bump(&self) -> &Bump {
|
||||
&self.wip_frame().bump
|
||||
/// Get the Root Node of this scope
|
||||
pub fn root_node(&self) -> &VNode {
|
||||
todo!("Portals have changed how we address nodes. Still fixing this, sorry.");
|
||||
// let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
|
||||
// unsafe { std::mem::transmute(&*node) }
|
||||
}
|
||||
|
||||
/// This method enables the ability to expose state to children further down the VirtualDOM Tree.
|
||||
@@ -274,6 +274,7 @@ impl Scope {
|
||||
let mut search_parent = self.parent_scope;
|
||||
|
||||
while let Some(parent_ptr) = search_parent {
|
||||
// safety: all parent pointers are valid thanks to the bump arena
|
||||
let parent = unsafe { &*parent_ptr };
|
||||
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
||||
return Some(shared.clone().downcast::<T>().unwrap());
|
||||
@@ -286,15 +287,10 @@ impl Scope {
|
||||
|
||||
/// 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: Future<Output = ()>>(
|
||||
&'src self,
|
||||
fut: impl FnOnce() -> F + 'src,
|
||||
) -> usize
|
||||
pub fn push_task<'src, F>(&'src self, fut: impl FnOnce() -> F + 'src) -> usize
|
||||
where
|
||||
F: Future<Output = ()>,
|
||||
F::Output: 'src,
|
||||
F: 'src,
|
||||
{
|
||||
@@ -302,18 +298,20 @@ impl Scope {
|
||||
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
|
||||
.unwrap();
|
||||
|
||||
// allocate the future
|
||||
let fut = fut();
|
||||
let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
|
||||
|
||||
// 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();
|
||||
|
||||
// erase the 'src lifetime for self-referential storage
|
||||
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
|
||||
let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) };
|
||||
|
||||
// Push the future into the tasks
|
||||
let mut items = self.items.borrow_mut();
|
||||
|
||||
items.tasks.push(self_ref_fut);
|
||||
items.tasks.len() - 1
|
||||
}
|
||||
@@ -334,22 +332,19 @@ impl Scope {
|
||||
/// }
|
||||
///```
|
||||
pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VPortal> {
|
||||
let frame = self.wip_frame();
|
||||
let bump = &frame.bump;
|
||||
let factory = NodeFactory { bump };
|
||||
let node = rsx.map(|f| f.call(factory))?;
|
||||
let node = bump.alloc(node);
|
||||
let bump = &self.wip_frame().bump;
|
||||
|
||||
let node_ptr = node as *mut _;
|
||||
let node_ptr = unsafe { std::mem::transmute(node_ptr) };
|
||||
let owned_node: VNode<'src> = rsx.map(|f| f.call(NodeFactory { bump }))?;
|
||||
let alloced_vnode: &'src mut VNode<'src> = bump.alloc(owned_node);
|
||||
let node_ptr: *mut VNode<'src> = alloced_vnode as *mut _;
|
||||
|
||||
let link = VPortal {
|
||||
let node: *mut VNode<'static> = unsafe { std::mem::transmute(node_ptr) };
|
||||
|
||||
Some(VPortal {
|
||||
scope_id: Cell::new(Some(self.our_arena_idx)),
|
||||
link_idx: Cell::new(0),
|
||||
node: node_ptr,
|
||||
};
|
||||
|
||||
Some(link)
|
||||
node,
|
||||
})
|
||||
}
|
||||
|
||||
/// Store a value between renders
|
||||
@@ -379,12 +374,12 @@ impl Scope {
|
||||
runner: impl FnOnce(&'src mut State) -> Output,
|
||||
) -> Output {
|
||||
let mut vals = self.hook_vals.borrow_mut();
|
||||
|
||||
let hook_len = vals.len();
|
||||
let cur_idx = self.hook_idx.get();
|
||||
|
||||
if cur_idx >= hook_len {
|
||||
let val = self.hook_arena.alloc(initializer(hook_len));
|
||||
vals.push(val);
|
||||
vals.push(self.hook_arena.alloc(initializer(hook_len)));
|
||||
}
|
||||
|
||||
let state = vals
|
||||
@@ -415,6 +410,7 @@ impl Scope {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mutable access to the "work in progress frame" - used to clear it
|
||||
pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame {
|
||||
match self.generation.get() & 1 == 0 {
|
||||
true => &mut self.frames[0],
|
||||
@@ -422,6 +418,7 @@ impl Scope {
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the frame where finalized nodes existed
|
||||
pub(crate) fn fin_frame(&self) -> &BumpFrame {
|
||||
match self.generation.get() & 1 == 1 {
|
||||
true => &self.frames[0],
|
||||
@@ -432,20 +429,23 @@ impl Scope {
|
||||
/// Reset this component's frame
|
||||
///
|
||||
/// # Safety:
|
||||
///
|
||||
/// This method breaks every reference of VNodes in the current frame.
|
||||
///
|
||||
/// Calling reset itself is not usually a big deal, but we consider it important
|
||||
/// due to the complex safety guarantees we need to uphold.
|
||||
pub(crate) unsafe fn reset_wip_frame(&mut self) {
|
||||
// todo: unsafecell or something
|
||||
let bump = self.wip_frame_mut();
|
||||
bump.bump.reset();
|
||||
self.wip_frame_mut().bump.reset();
|
||||
}
|
||||
|
||||
/// Cycle to the next generation
|
||||
pub(crate) fn cycle_frame(&self) {
|
||||
self.generation.set(self.generation.get() + 1);
|
||||
}
|
||||
|
||||
pub fn root_node(&self) -> &VNode {
|
||||
let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
|
||||
unsafe { std::mem::transmute(&*node) }
|
||||
/// Get the [`Bump`] of the WIP frame.
|
||||
pub(crate) fn bump(&self) -> &Bump {
|
||||
&self.wip_frame().bump
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,7 +454,7 @@ pub(crate) struct BumpFrame {
|
||||
pub nodes: RefCell<Vec<*const VNode<'static>>>,
|
||||
}
|
||||
impl BumpFrame {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
pub(crate) fn new(capacity: usize) -> Self {
|
||||
let bump = Bump::with_capacity(capacity);
|
||||
|
||||
let node = &*bump.alloc(VText {
|
||||
@@ -467,7 +467,7 @@ impl BumpFrame {
|
||||
Self { bump, nodes }
|
||||
}
|
||||
|
||||
pub fn assign_nodelink(&self, node: &VPortal) {
|
||||
pub(crate) fn assign_nodelink(&self, node: &VPortal) {
|
||||
let mut nodes = self.nodes.borrow_mut();
|
||||
|
||||
let len = nodes.len();
|
||||
|
||||
@@ -9,9 +9,9 @@ use std::{
|
||||
|
||||
use crate::innerlude::*;
|
||||
|
||||
pub type FcSlot = *const ();
|
||||
pub(crate) type FcSlot = *const ();
|
||||
|
||||
pub struct Heuristic {
|
||||
pub(crate) struct Heuristic {
|
||||
hook_arena_size: usize,
|
||||
node_arena_size: usize,
|
||||
}
|
||||
@@ -92,15 +92,8 @@ impl ScopeArena {
|
||||
let new_scope_id = ScopeId(self.scope_counter.get());
|
||||
self.scope_counter.set(self.scope_counter.get() + 1);
|
||||
|
||||
// log::debug!("new scope {:?} with parent {:?}", new_scope_id, container);
|
||||
|
||||
if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
|
||||
let scope = unsafe { &mut *old_scope };
|
||||
// log::debug!(
|
||||
// "reusing scope {:?} as {:?}",
|
||||
// scope.our_arena_idx,
|
||||
// new_scope_id
|
||||
// );
|
||||
|
||||
scope.caller = caller;
|
||||
scope.parent_scope = parent_scope;
|
||||
@@ -203,8 +196,6 @@ impl ScopeArena {
|
||||
pub fn try_remove(&self, id: &ScopeId) -> Option<()> {
|
||||
self.ensure_drop_safety(id);
|
||||
|
||||
// log::debug!("removing scope {:?}", id);
|
||||
|
||||
// Safety:
|
||||
// - ensure_drop_safety ensures that no references to this scope are in use
|
||||
// - this raw pointer is removed from the map
|
||||
@@ -281,30 +272,30 @@ impl ScopeArena {
|
||||
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
|
||||
/// be dropped.
|
||||
pub(crate) fn ensure_drop_safety(&self, scope_id: &ScopeId) {
|
||||
let scope = self.get_scope(scope_id).unwrap();
|
||||
if let Some(scope) = self.get_scope(scope_id) {
|
||||
let mut items = scope.items.borrow_mut();
|
||||
|
||||
let mut items = scope.items.borrow_mut();
|
||||
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
||||
// run the hooks (which hold an &mut Reference)
|
||||
// recursively call ensure_drop_safety on all children
|
||||
items.borrowed_props.drain(..).for_each(|comp| {
|
||||
let scope_id = comp
|
||||
.associated_scope
|
||||
.get()
|
||||
.expect("VComponents should be associated with a valid Scope");
|
||||
|
||||
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
||||
// run the hooks (which hold an &mut Reference)
|
||||
// recursively call ensure_drop_safety on all children
|
||||
items.borrowed_props.drain(..).for_each(|comp| {
|
||||
let scope_id = comp
|
||||
.associated_scope
|
||||
.get()
|
||||
.expect("VComponents should be associated with a valid Scope");
|
||||
self.ensure_drop_safety(&scope_id);
|
||||
|
||||
self.ensure_drop_safety(&scope_id);
|
||||
let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
|
||||
drop_props();
|
||||
});
|
||||
|
||||
let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
|
||||
drop_props();
|
||||
});
|
||||
|
||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
||||
items
|
||||
.listeners
|
||||
.drain(..)
|
||||
.for_each(|listener| drop(listener.callback.borrow_mut().take()));
|
||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
||||
items
|
||||
.listeners
|
||||
.drain(..)
|
||||
.for_each(|listener| drop(listener.callback.callback.borrow_mut().take()));
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope(&self, id: &ScopeId) -> bool {
|
||||
@@ -375,7 +366,7 @@ impl ScopeArena {
|
||||
//
|
||||
for listener in real_el.listeners.borrow().iter() {
|
||||
if listener.event == event.name {
|
||||
let mut cb = listener.callback.borrow_mut();
|
||||
let mut cb = listener.callback.callback.borrow_mut();
|
||||
if let Some(cb) = cb.as_mut() {
|
||||
(cb)(event.data.clone());
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ impl VirtualDom {
|
||||
/// ```
|
||||
///
|
||||
/// Note: the VirtualDOM is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
|
||||
pub fn new(root: FC<()>) -> Self {
|
||||
pub fn new(root: Component<()>) -> Self {
|
||||
Self::new_with_props(root, ())
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ impl VirtualDom {
|
||||
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
||||
/// let mutations = dom.rebuild();
|
||||
/// ```
|
||||
pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self {
|
||||
pub fn new_with_props<P: 'static>(root: Component<P>, root_props: P) -> Self {
|
||||
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
|
||||
Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
|
||||
}
|
||||
@@ -183,8 +183,13 @@ impl VirtualDom {
|
||||
///
|
||||
/// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
|
||||
/// VirtualDom to be created just to retrieve its channel receiver.
|
||||
///
|
||||
/// ```rust
|
||||
/// let (sender, receiver) = futures_channel::mpsc::unbounded();
|
||||
/// let dom = VirtualDom::new_with_scheduler(Example, (), sender, receiver);
|
||||
/// ```
|
||||
pub fn new_with_props_and_scheduler<P: 'static>(
|
||||
root: FC<P>,
|
||||
root: Component<P>,
|
||||
root_props: P,
|
||||
sender: UnboundedSender<SchedulerMsg>,
|
||||
receiver: UnboundedReceiver<SchedulerMsg>,
|
||||
@@ -235,27 +240,55 @@ impl VirtualDom {
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
///
|
||||
///
|
||||
/// let dom = VirtualDom::new(App);
|
||||
/// let sender = dom.get_scheduler_channel();
|
||||
/// ```
|
||||
pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
|
||||
self.sender.clone()
|
||||
}
|
||||
|
||||
/// Add a new message to the scheduler queue directly.
|
||||
///
|
||||
///
|
||||
/// This method makes it possible to send messages to the scheduler from outside the VirtualDom without having to
|
||||
/// call `get_schedule_channel` and then `send`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, ignore
|
||||
/// let dom = VirtualDom::new(App);
|
||||
/// dom.insert_scheduler_message(SchedulerMsg::Immediate(ScopeId(0)));
|
||||
/// ```
|
||||
pub fn insert_scheduler_message(&self, msg: SchedulerMsg) {
|
||||
self.sender.unbounded_send(msg).unwrap()
|
||||
}
|
||||
|
||||
/// Check if the [`VirtualDom`] has any pending updates or work to be done.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let dom = VirtualDom::new(App);
|
||||
///
|
||||
///
|
||||
/// // the dom is "dirty" when it is started and must be rebuilt to get the first render
|
||||
/// assert!(dom.has_any_work());
|
||||
/// ```
|
||||
pub fn has_any_work(&self) -> bool {
|
||||
pub fn has_work(&self) -> bool {
|
||||
!(self.dirty_scopes.is_empty() && self.pending_messages.is_empty())
|
||||
}
|
||||
|
||||
/// Waits for the scheduler to have work
|
||||
/// Wait for the scheduler to have any work.
|
||||
///
|
||||
/// This method polls the internal future queue *and* the scheduler channel.
|
||||
/// To add work to the VirtualDom, insert a message via the scheduler channel.
|
||||
///
|
||||
/// This lets us poll async tasks during idle periods without blocking the main thread.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let dom = VirtualDom::new(App);
|
||||
/// let sender = dom.get_scheduler_channel();
|
||||
/// ```
|
||||
pub async fn wait_for_work(&mut self) {
|
||||
loop {
|
||||
if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
|
||||
@@ -390,9 +423,10 @@ impl VirtualDom {
|
||||
|
||||
committed_mutations.push(mutations);
|
||||
} else {
|
||||
log::debug!("Could not finish work in time");
|
||||
|
||||
// leave the work in an incomplete state
|
||||
//
|
||||
// todo: we should store the edits and re-apply them later
|
||||
// for now, we just dump the work completely (threadsafe)
|
||||
return committed_mutations;
|
||||
}
|
||||
}
|
||||
@@ -400,7 +434,7 @@ impl VirtualDom {
|
||||
committed_mutations
|
||||
}
|
||||
|
||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch
|
||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
|
||||
///
|
||||
/// The diff machine expects the RealDom's stack to be the root of the application.
|
||||
///
|
||||
@@ -475,6 +509,15 @@ impl VirtualDom {
|
||||
/// Renders an `rsx` call into the Base Scope's allocator.
|
||||
///
|
||||
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
|
||||
///
|
||||
/// ```rust
|
||||
/// fn Base(cx: Context, props: &()) -> Element {
|
||||
/// rsx!(cx, div {})
|
||||
/// }
|
||||
///
|
||||
/// 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> {
|
||||
let scope = self.scopes.get_scope(&self.base_scope).unwrap();
|
||||
let frame = scope.wip_frame();
|
||||
@@ -485,7 +528,16 @@ impl VirtualDom {
|
||||
|
||||
/// Renders an `rsx` call into the Base Scope's allocator.
|
||||
///
|
||||
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
|
||||
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
|
||||
///
|
||||
/// ```rust
|
||||
/// fn Base(cx: Context, props: &()) -> Element {
|
||||
/// rsx!(cx, div {})
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(Base);
|
||||
/// let nodes = dom.render_nodes(rsx!("div"));
|
||||
/// ```
|
||||
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
|
||||
let mut machine = DiffState::new(&self.scopes);
|
||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||
@@ -498,6 +550,16 @@ impl VirtualDom {
|
||||
/// Renders an `rsx` call into the Base Scope's allocator.
|
||||
///
|
||||
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// fn Base(cx: Context, props: &()) -> Element {
|
||||
/// rsx!(cx, div {})
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(Base);
|
||||
/// let nodes = dom.render_nodes(rsx!("div"));
|
||||
/// ```
|
||||
pub fn create_vnodes<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
|
||||
let nodes = self.render_vnodes(left);
|
||||
let mut machine = DiffState::new(&self.scopes);
|
||||
@@ -507,9 +569,19 @@ impl VirtualDom {
|
||||
machine.mutations
|
||||
}
|
||||
|
||||
/// Renders an `rsx` call into the Base Scope's allocator.
|
||||
/// Renders an `rsx` call into the Base Scopes's arena.
|
||||
///
|
||||
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
|
||||
/// Useful when needing to diff two rsx! calls from outside the VirtualDom, such as in a test.
|
||||
///
|
||||
///
|
||||
/// ```rust
|
||||
/// fn Base(cx: Context, props: &()) -> Element {
|
||||
/// rsx!(cx, div {})
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(Base);
|
||||
/// let nodes = dom.render_nodes(rsx!("div"));
|
||||
/// ```
|
||||
pub fn diff_lazynodes<'a>(
|
||||
&'a self,
|
||||
left: Option<LazyNodes<'a, '_>>,
|
||||
@@ -666,14 +738,7 @@ impl<'a> Future for PollTasks<'a> {
|
||||
|
||||
// really this should just be retain_mut but that doesn't exist yet
|
||||
while let Some(mut task) = items.tasks.pop() {
|
||||
// todo: does this make sense?
|
||||
// I don't usually write futures by hand
|
||||
// I think the futures neeed to be pinned using bumpbox or something
|
||||
// right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
|
||||
let task_mut = task.as_mut();
|
||||
let pinned = unsafe { Pin::new_unchecked(task_mut) };
|
||||
|
||||
if pinned.poll(cx).is_ready() {
|
||||
if task.as_mut().poll(cx).is_ready() {
|
||||
all_pending = false
|
||||
} else {
|
||||
unfinished_tasks.push(task);
|
||||
|
||||
@@ -10,7 +10,7 @@ fn test_borrowed_state() {
|
||||
let _ = VirtualDom::new(Parent);
|
||||
}
|
||||
|
||||
fn Parent(cx: Context, props: &()) -> Element {
|
||||
fn Parent(cx: Context, _props: &()) -> Element {
|
||||
let value = cx.use_hook(|_| String::new(), |f| &*f);
|
||||
|
||||
cx.render(rsx! {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![allow(unused, non_upper_case_globals)]
|
||||
#![allow(unused, non_upper_case_globals, non_snake_case)]
|
||||
|
||||
//! Prove that the dom works normally through virtualdom methods.
|
||||
//!
|
||||
@@ -13,7 +13,7 @@ use dioxus_html as dioxus_elements;
|
||||
mod test_logging;
|
||||
use DomEdit::*;
|
||||
|
||||
fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
|
||||
fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
|
||||
const IS_LOGGING_ENABLED: bool = false;
|
||||
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
||||
VirtualDom::new_with_props(app, props)
|
||||
@@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
|
||||
|
||||
#[test]
|
||||
fn test_original_diff() {
|
||||
static APP: FC<()> = |cx, props| {
|
||||
static APP: Component<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
div {
|
||||
@@ -57,7 +57,7 @@ fn test_original_diff() {
|
||||
|
||||
#[test]
|
||||
fn create() {
|
||||
static APP: FC<()> = |cx, props| {
|
||||
static APP: Component<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
div {
|
||||
@@ -120,7 +120,7 @@ fn create() {
|
||||
|
||||
#[test]
|
||||
fn create_list() {
|
||||
static APP: FC<()> = |cx, props| {
|
||||
static APP: Component<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
{(0..3).map(|f| rsx!{ div {
|
||||
"hello"
|
||||
@@ -169,7 +169,7 @@ fn create_list() {
|
||||
|
||||
#[test]
|
||||
fn create_simple() {
|
||||
static APP: FC<()> = |cx, props| {
|
||||
static APP: Component<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
div {}
|
||||
div {}
|
||||
@@ -207,7 +207,7 @@ fn create_simple() {
|
||||
}
|
||||
#[test]
|
||||
fn create_components() {
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
Child { "abc1" }
|
||||
Child { "abc2" }
|
||||
@@ -273,7 +273,7 @@ fn create_components() {
|
||||
}
|
||||
#[test]
|
||||
fn anchors() {
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
cx.render(rsx! {
|
||||
{true.then(|| rsx!{ div { "hello" } })}
|
||||
{false.then(|| rsx!{ div { "goodbye" } })}
|
||||
|
||||
@@ -22,7 +22,7 @@ fn manual_diffing() {
|
||||
value: Shared<&'static str>,
|
||||
}
|
||||
|
||||
static App: FC<AppProps> = |cx, props| {
|
||||
static App: Component<AppProps> = |cx, props| {
|
||||
let val = props.value.lock().unwrap();
|
||||
cx.render(rsx! { div { "{val}" } })
|
||||
};
|
||||
@@ -46,7 +46,7 @@ fn manual_diffing() {
|
||||
|
||||
#[test]
|
||||
fn events_generate() {
|
||||
static App: FC<()> = |cx, _| {
|
||||
static App: Component<()> = |cx, _| {
|
||||
let mut count = use_state(cx, || 0);
|
||||
|
||||
let inner = match *count {
|
||||
@@ -69,7 +69,7 @@ fn events_generate() {
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mut channel = dom.get_scheduler_channel();
|
||||
assert!(dom.has_any_work());
|
||||
assert!(dom.has_work());
|
||||
|
||||
let edits = dom.rebuild();
|
||||
assert_eq!(
|
||||
@@ -105,7 +105,7 @@ fn events_generate() {
|
||||
|
||||
#[test]
|
||||
fn components_generate() {
|
||||
static App: FC<()> = |cx, _| {
|
||||
static App: Component<()> = |cx, _| {
|
||||
let mut render_phase = use_state(cx, || 0);
|
||||
render_phase += 1;
|
||||
|
||||
@@ -122,7 +122,7 @@ fn components_generate() {
|
||||
})
|
||||
};
|
||||
|
||||
static Child: FC<()> = |cx, _| {
|
||||
static Child: Component<()> = |cx, _| {
|
||||
cx.render(rsx! {
|
||||
h1 {}
|
||||
})
|
||||
@@ -222,7 +222,7 @@ fn components_generate() {
|
||||
#[test]
|
||||
fn component_swap() {
|
||||
// simple_logger::init();
|
||||
static App: FC<()> = |cx, _| {
|
||||
static App: Component<()> = |cx, _| {
|
||||
let mut render_phase = use_state(cx, || 0);
|
||||
render_phase += 1;
|
||||
|
||||
@@ -261,7 +261,7 @@ fn component_swap() {
|
||||
})
|
||||
};
|
||||
|
||||
static NavBar: FC<()> = |cx, _| {
|
||||
static NavBar: Component<()> = |cx, _| {
|
||||
println!("running navbar");
|
||||
cx.render(rsx! {
|
||||
h1 {
|
||||
@@ -271,7 +271,7 @@ fn component_swap() {
|
||||
})
|
||||
};
|
||||
|
||||
static NavLink: FC<()> = |cx, _| {
|
||||
static NavLink: Component<()> = |cx, _| {
|
||||
println!("running navlink");
|
||||
cx.render(rsx! {
|
||||
h1 {
|
||||
@@ -280,7 +280,7 @@ fn component_swap() {
|
||||
})
|
||||
};
|
||||
|
||||
static Dashboard: FC<()> = |cx, _| {
|
||||
static Dashboard: Component<()> = |cx, _| {
|
||||
println!("running dashboard");
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
@@ -289,7 +289,7 @@ fn component_swap() {
|
||||
})
|
||||
};
|
||||
|
||||
static Results: FC<()> = |cx, _| {
|
||||
static Results: Component<()> = |cx, _| {
|
||||
println!("running results");
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
@@ -13,12 +13,12 @@ mod test_logging;
|
||||
fn shared_state_test() {
|
||||
struct MySharedState(&'static str);
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
cx.provide_state(MySharedState("world!"));
|
||||
cx.render(rsx!(Child {}))
|
||||
};
|
||||
|
||||
static Child: FC<()> = |cx, props| {
|
||||
static Child: Component<()> = |cx, props| {
|
||||
let shared = cx.consume_state::<MySharedState>()?;
|
||||
cx.render(rsx!("Hello, {shared.0}"))
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ use DomEdit::*;
|
||||
|
||||
#[test]
|
||||
fn app_runs() {
|
||||
static App: FC<()> = |cx, props| rsx!(cx, div{"hello"} );
|
||||
static App: Component<()> = |cx, props| rsx!(cx, div{"hello"} );
|
||||
|
||||
let mut vdom = VirtualDom::new(App);
|
||||
let edits = vdom.rebuild();
|
||||
@@ -27,7 +27,7 @@ fn app_runs() {
|
||||
|
||||
#[test]
|
||||
fn fragments_work() {
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
div{"hello"}
|
||||
div{"goodbye"}
|
||||
@@ -41,7 +41,7 @@ fn fragments_work() {
|
||||
|
||||
#[test]
|
||||
fn lists_work() {
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
h1 {"hello"}
|
||||
{(0..6).map(|f| rsx!(span{ "{f}" }))}
|
||||
@@ -54,7 +54,7 @@ fn lists_work() {
|
||||
|
||||
#[test]
|
||||
fn conditional_rendering() {
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
h1 {"hello"}
|
||||
{true.then(|| rsx!(span{ "a" }))}
|
||||
@@ -87,13 +87,13 @@ fn conditional_rendering() {
|
||||
|
||||
#[test]
|
||||
fn child_components() {
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
{true.then(|| rsx!(Child { }))}
|
||||
{false.then(|| rsx!(Child { }))}
|
||||
))
|
||||
};
|
||||
static Child: FC<()> = |cx, props| {
|
||||
static Child: Component<()> = |cx, props| {
|
||||
cx.render(rsx!(
|
||||
h1 {"hello"}
|
||||
h1 {"goodbye"}
|
||||
|
||||
@@ -15,7 +15,7 @@ fn main() {
|
||||
dioxus_desktop::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let mut count = use_state(cx, || 0);
|
||||
log::debug!("count is {:?}", count);
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ use std::{
|
||||
sync::atomic::AtomicBool,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use tokio::task::LocalSet;
|
||||
use wry::{
|
||||
application::{
|
||||
accelerator::{Accelerator, SysMods},
|
||||
@@ -30,14 +29,14 @@ use wry::{
|
||||
};
|
||||
|
||||
pub fn launch(
|
||||
root: FC<()>,
|
||||
root: Component<()>,
|
||||
config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
|
||||
) {
|
||||
launch_with_props(root, (), config_builder)
|
||||
}
|
||||
|
||||
pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
|
||||
root: FC<P>,
|
||||
root: Component<P>,
|
||||
props: P,
|
||||
builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
|
||||
) {
|
||||
@@ -51,7 +50,7 @@ struct Response<'a> {
|
||||
}
|
||||
|
||||
pub fn run<T: 'static + Send + Sync>(
|
||||
root: FC<T>,
|
||||
root: Component<T>,
|
||||
props: T,
|
||||
user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
|
||||
) {
|
||||
@@ -112,7 +111,7 @@ 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: FC<P>, props: P) -> Self {
|
||||
pub fn new_on_tokio<P: Send + 'static>(root: Component<P>, props: P) -> Self {
|
||||
let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
|
||||
let pending_edits = edit_queue.clone();
|
||||
|
||||
@@ -142,7 +141,6 @@ impl DesktopController {
|
||||
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()
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
mod usestate;
|
||||
pub use usestate::{use_state, AsyncUseState, UseState};
|
||||
|
||||
mod usestate2;
|
||||
// pub use usestate2::use_state2;
|
||||
|
||||
mod useref;
|
||||
pub use useref::*;
|
||||
|
||||
@@ -9,3 +12,6 @@ pub use use_shared_state::*;
|
||||
|
||||
mod usecoroutine;
|
||||
pub use usecoroutine::*;
|
||||
|
||||
mod usemodel;
|
||||
pub use usemodel::*;
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
type ProvidedState<T> = RefCell<ProvidedStateInner<T>>;
|
||||
|
||||
// Tracks all the subscribers to a shared State
|
||||
pub(crate) struct ProvidedStateInner<T> {
|
||||
pub struct ProvidedStateInner<T> {
|
||||
value: Rc<RefCell<T>>,
|
||||
notify_any: Rc<dyn Fn(ScopeId)>,
|
||||
consumers: HashSet<ScopeId>,
|
||||
@@ -20,6 +20,14 @@ impl<T> ProvidedStateInner<T> {
|
||||
(self.notify_any)(*consumer);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) -> RefMut<T> {
|
||||
self.value.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn read(&self) -> Ref<T> {
|
||||
self.value.borrow()
|
||||
}
|
||||
}
|
||||
|
||||
/// This hook provides some relatively light ergonomics around shared state.
|
||||
@@ -114,10 +122,10 @@ impl<'a, T: 'static> UseSharedState<'a, T> {
|
||||
}
|
||||
|
||||
pub fn notify_consumers(self) {
|
||||
// if !self.needs_notification.get() {
|
||||
self.root.borrow_mut().notify_consumers();
|
||||
// self.needs_notification.set(true);
|
||||
// }
|
||||
if !self.needs_notification.get() {
|
||||
self.root.borrow_mut().notify_consumers();
|
||||
self.needs_notification.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
|
||||
@@ -138,6 +146,10 @@ impl<'a, T: 'static> UseSharedState<'a, T> {
|
||||
pub fn write_silent(&self) -> RefMut<'_, T> {
|
||||
self.value.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn inner(&self) -> Rc<RefCell<ProvidedStateInner<T>>> {
|
||||
self.root.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for UseSharedState<'_, T> {}
|
||||
|
||||
@@ -6,9 +6,9 @@ use std::{
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
pub fn use_coroutine<'a, F: Future<Output = ()> + 'a>(
|
||||
pub fn use_coroutine<'a, F: Future<Output = ()> + 'static>(
|
||||
cx: Context<'a>,
|
||||
f: impl FnOnce() -> F + 'a,
|
||||
mut f: impl FnMut() -> F + 'a,
|
||||
) -> CoroutineHandle {
|
||||
//
|
||||
cx.use_hook(
|
||||
@@ -59,10 +59,19 @@ pub struct CoroutineHandle<'a> {
|
||||
cx: Context<'a>,
|
||||
inner: &'a State,
|
||||
}
|
||||
impl Clone for CoroutineHandle<'_> {
|
||||
fn clone(&self) -> Self {
|
||||
CoroutineHandle {
|
||||
cx: self.cx,
|
||||
inner: self.inner,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Copy for CoroutineHandle<'_> {}
|
||||
|
||||
impl<'a> CoroutineHandle<'a> {
|
||||
pub fn start(&self) {
|
||||
if self.inner.running.get() {
|
||||
if self.is_running() {
|
||||
return;
|
||||
}
|
||||
if let Some(submit) = self.inner.submit.borrow_mut().take() {
|
||||
@@ -71,5 +80,10 @@ impl<'a> CoroutineHandle<'a> {
|
||||
self.cx.push_task(|| fut.as_mut().unwrap().as_mut());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_running(&self) -> bool {
|
||||
self.inner.running.get()
|
||||
}
|
||||
|
||||
pub fn resume(&self) {}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
//! When building complex components, it's occasionally useful to dip into a pure MVC pattern instead of the
|
||||
//! React hooks pattern. Hooks are useful to abstract over some reusable logic, but many models are not reusable
|
||||
//! in the same way that hooks are.
|
||||
//!
|
||||
//! In these cases, we provide `use_model` - a convenient way of abstracting over some state and async functions.
|
||||
|
||||
use dioxus_core::prelude::Context;
|
||||
use futures::Future;
|
||||
use std::{
|
||||
cell::{Cell, Ref, RefCell, RefMut},
|
||||
marker::PhantomData,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
pub fn use_model<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseModel<T> {
|
||||
cx.use_hook(
|
||||
|_| UseModelInner {
|
||||
update_scheduled: Cell::new(false),
|
||||
update_callback: cx.schedule_update(),
|
||||
value: RefCell::new(f()),
|
||||
// tasks: RefCell::new(Vec::new()),
|
||||
},
|
||||
|inner| {
|
||||
inner.update_scheduled.set(false);
|
||||
UseModel { inner }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub struct UseModel<'a, T> {
|
||||
inner: &'a UseModelInner<T>,
|
||||
}
|
||||
|
||||
struct UseModelInner<T> {
|
||||
update_scheduled: Cell<bool>,
|
||||
update_callback: Rc<dyn Fn()>,
|
||||
value: RefCell<T>,
|
||||
// tasks: RefCell<Vec<ModelTask>>,
|
||||
}
|
||||
|
||||
type ModelTask = Pin<Box<dyn Future<Output = ()> + 'static>>;
|
||||
|
||||
impl<'a, T: 'static> UseModel<'a, T> {
|
||||
pub fn read(&self) -> Ref<'_, T> {
|
||||
self.inner.value.borrow()
|
||||
}
|
||||
pub fn write(&self) -> RefMut<'_, T> {
|
||||
self.needs_update();
|
||||
self.inner.value.borrow_mut()
|
||||
}
|
||||
/// Allows the ability to write the value without forcing a re-render
|
||||
pub fn write_silent(&self) -> RefMut<'_, T> {
|
||||
self.inner.value.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn needs_update(&self) {
|
||||
if !self.inner.update_scheduled.get() {
|
||||
self.inner.update_scheduled.set(true);
|
||||
(self.inner.update_callback)();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&self, new: T) {
|
||||
*self.inner.value.borrow_mut() = new;
|
||||
self.needs_update();
|
||||
}
|
||||
|
||||
pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
|
||||
(self.read(), self)
|
||||
}
|
||||
|
||||
pub fn start(&self, f: impl FnOnce() -> ModelTask) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
// keep a coroutine going
|
||||
pub fn use_model_coroutine<T, F: Future<Output = ()> + 'static>(
|
||||
cx: Context,
|
||||
model: UseModel<T>,
|
||||
f: impl FnOnce(AppModels) -> F,
|
||||
) -> UseModelCoroutine {
|
||||
cx.use_hook(
|
||||
|_| {
|
||||
//
|
||||
UseModelTaskInner {
|
||||
task: Default::default(),
|
||||
}
|
||||
},
|
||||
|inner| {
|
||||
if let Some(task) = inner.task.get_mut() {
|
||||
cx.push_task(|| task);
|
||||
}
|
||||
//
|
||||
todo!()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub struct UseModelCoroutine {}
|
||||
|
||||
struct UseModelTaskInner {
|
||||
task: RefCell<Option<ModelTask>>,
|
||||
}
|
||||
|
||||
impl UseModelCoroutine {
|
||||
pub fn start(&self) {}
|
||||
}
|
||||
|
||||
pub struct ModelAsync<T> {
|
||||
_p: PhantomData<T>,
|
||||
}
|
||||
impl<T> ModelAsync<T> {
|
||||
pub fn write(&self) -> RefMut<'_, T> {
|
||||
todo!()
|
||||
}
|
||||
pub fn read(&self) -> Ref<'_, T> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AppModels {}
|
||||
|
||||
impl AppModels {
|
||||
pub fn get<T: 'static>(&self) -> ModelAsync<T> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for UseModel<'_, T> {}
|
||||
impl<'a, T> Clone for UseModel<'a, T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { inner: self.inner }
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,12 @@ use dioxus_core::Context;
|
||||
pub fn use_ref<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseRef<T> {
|
||||
cx.use_hook(
|
||||
|_| UseRefInner {
|
||||
update_scheuled: Cell::new(false),
|
||||
update_scheduled: Cell::new(false),
|
||||
update_callback: cx.schedule_update(),
|
||||
value: RefCell::new(f()),
|
||||
},
|
||||
|inner| {
|
||||
inner.update_scheuled.set(false);
|
||||
inner.update_scheduled.set(false);
|
||||
UseRef { inner }
|
||||
},
|
||||
)
|
||||
@@ -23,7 +23,7 @@ pub struct UseRef<'a, T> {
|
||||
inner: &'a UseRefInner<T>,
|
||||
}
|
||||
struct UseRefInner<T> {
|
||||
update_scheuled: Cell<bool>,
|
||||
update_scheduled: Cell<bool>,
|
||||
update_callback: Rc<dyn Fn()>,
|
||||
value: RefCell<T>,
|
||||
}
|
||||
@@ -54,8 +54,8 @@ impl<'a, T> UseRef<'a, T> {
|
||||
}
|
||||
|
||||
pub fn needs_update(&self) {
|
||||
if !self.inner.update_scheuled.get() {
|
||||
self.inner.update_scheuled.set(true);
|
||||
if !self.inner.update_scheduled.get() {
|
||||
self.inner.update_scheduled.set(true);
|
||||
(self.inner.update_callback)();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
|
||||
/// Store state between component renders!
|
||||
///
|
||||
/// ## The "Pinnacle" of state hooks
|
||||
/// ## Dioxus equivalent of useState, designed for Rust
|
||||
///
|
||||
/// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and
|
||||
/// modify state between component renders. When the state is updated, the component will re-render.
|
||||
@@ -55,9 +55,9 @@ pub fn use_state<'a, T: 'static>(
|
||||
) -> UseState<'a, T> {
|
||||
cx.use_hook(
|
||||
move |_| {
|
||||
//
|
||||
let first_val = initial_state_fn();
|
||||
UseStateInner {
|
||||
current_val: initial_state_fn(),
|
||||
current_val: Rc::new(first_val),
|
||||
update_callback: cx.schedule_update(),
|
||||
wip: Rc::new(RefCell::new(None)),
|
||||
update_scheuled: Cell::new(false),
|
||||
@@ -65,9 +65,15 @@ pub fn use_state<'a, T: 'static>(
|
||||
},
|
||||
move |hook| {
|
||||
hook.update_scheuled.set(false);
|
||||
|
||||
let mut new_val = hook.wip.borrow_mut();
|
||||
if new_val.is_some() {
|
||||
hook.current_val = new_val.take().unwrap();
|
||||
// if there's only one reference (weak or otherwise), we can just swap the values
|
||||
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
|
||||
*val = new_val.take().unwrap();
|
||||
} else {
|
||||
hook.current_val = Rc::new(new_val.take().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
UseState { inner: &*hook }
|
||||
@@ -75,7 +81,7 @@ pub fn use_state<'a, T: 'static>(
|
||||
)
|
||||
}
|
||||
struct UseStateInner<T: 'static> {
|
||||
current_val: T,
|
||||
current_val: Rc<T>,
|
||||
update_scheuled: Cell<bool>,
|
||||
update_callback: Rc<dyn Fn()>,
|
||||
wip: Rc<RefCell<Option<T>>>,
|
||||
@@ -117,6 +123,10 @@ impl<'a, T: 'static> UseState<'a, T> {
|
||||
&self.inner.current_val
|
||||
}
|
||||
|
||||
pub fn get_rc(&self) -> &'a Rc<T> {
|
||||
&self.inner.current_val
|
||||
}
|
||||
|
||||
/// Get the current status of the work-in-progress data
|
||||
pub fn get_wip(&self) -> Ref<Option<T>> {
|
||||
self.inner.wip.borrow()
|
||||
@@ -134,14 +144,15 @@ impl<'a, T: 'static> UseState<'a, T> {
|
||||
pub fn setter(&self) -> Rc<dyn Fn(T)> {
|
||||
let slot = self.inner.wip.clone();
|
||||
Rc::new(move |new| {
|
||||
//
|
||||
*slot.borrow_mut() = Some(new);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn for_async(&self) -> AsyncUseState<T> {
|
||||
AsyncUseState {
|
||||
re_render: self.inner.update_callback.clone(),
|
||||
wip: self.inner.wip.clone(),
|
||||
inner: self.inner.current_val.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,11 +175,15 @@ impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
|
||||
// "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
|
||||
RefMut::map(self.inner.wip.borrow_mut(), |slot| {
|
||||
if slot.is_none() {
|
||||
*slot = Some(self.inner.current_val.to_owned());
|
||||
*slot = Some(self.inner.current_val.as_ref().to_owned());
|
||||
}
|
||||
slot.as_mut().unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn inner(self) -> T {
|
||||
self.inner.current_val.as_ref().to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> std::ops::Deref for UseState<'a, T> {
|
||||
@@ -254,6 +269,8 @@ impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
|
||||
|
||||
/// A less ergonmic but still capable form of use_state that's valid for `static lifetime
|
||||
pub struct AsyncUseState<T: 'static> {
|
||||
inner: Rc<T>,
|
||||
re_render: Rc<dyn Fn()>,
|
||||
wip: Rc<RefCell<Option<T>>>,
|
||||
}
|
||||
|
||||
@@ -269,7 +286,17 @@ impl<T: ToOwned> AsyncUseState<T> {
|
||||
slot.as_mut().unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
impl<T> AsyncUseState<T> {
|
||||
pub fn set(&mut self, val: T) {
|
||||
(self.re_render)();
|
||||
*self.wip.borrow_mut() = Some(val);
|
||||
}
|
||||
pub fn get(&self) -> &T {
|
||||
self.inner.as_ref()
|
||||
}
|
||||
|
||||
pub fn get_rc(&self) -> &Rc<T> {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
use dioxus_core::prelude::Context;
|
||||
use std::{
|
||||
borrow::{Borrow, BorrowMut},
|
||||
cell::{Cell, Ref, RefCell, RefMut},
|
||||
fmt::{Debug, Display},
|
||||
ops::Not,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
// /// Store state between component renders!
|
||||
// ///
|
||||
// /// ## Dioxus equivalent of UseStateInner2, designed for Rust
|
||||
// ///
|
||||
// /// The Dioxus version of `UseStateInner2` is the "king daddy" of state management. It allows you to ergonomically store and
|
||||
// /// modify state between component renders. When the state is updated, the component will re-render.
|
||||
// ///
|
||||
// /// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
|
||||
// ///
|
||||
// /// [`use_state`] exposes a few helper methods to modify the underlying state:
|
||||
// /// - `.set(new)` allows you to override the "work in progress" value with a new value
|
||||
// /// - `.get_mut()` allows you to modify the WIP value
|
||||
// /// - `.get_wip()` allows you to access the WIP value
|
||||
// /// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
|
||||
// ///
|
||||
// /// Additionally, a ton of std::ops traits are implemented for the `UseStateInner2` wrapper, meaning any mutative type operations
|
||||
// /// will automatically be called on the WIP value.
|
||||
// ///
|
||||
// /// ## Combinators
|
||||
// ///
|
||||
// /// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
|
||||
// /// - `.classic()` and `.split()` convert the hook into the classic React-style hook
|
||||
// /// ```rust
|
||||
// /// let (state, set_state) = use_state(cx, || 10).split()
|
||||
// /// ```
|
||||
// ///
|
||||
// ///
|
||||
// /// Usage:
|
||||
// /// ```ignore
|
||||
// /// const Example: FC<()> = |cx, props|{
|
||||
// /// let counter = use_state(cx, || 0);
|
||||
// /// let increment = |_| counter += 1;
|
||||
// /// let decrement = |_| counter += 1;
|
||||
// ///
|
||||
// /// html! {
|
||||
// /// <div>
|
||||
// /// <h1>"Counter: {counter}" </h1>
|
||||
// /// <button onclick={increment}> "Increment" </button>
|
||||
// /// <button onclick={decrement}> "Decrement" </button>
|
||||
// /// </div>
|
||||
// /// }
|
||||
// /// }
|
||||
// /// ```
|
||||
// pub fn use_state2<'a, T: 'static>(
|
||||
// cx: Context<'a>,
|
||||
// initial_state_fn: impl FnOnce() -> T,
|
||||
// ) -> &'a UseState2<T> {
|
||||
// cx.use_hook(
|
||||
// move |_| {
|
||||
// UseState2(Rc::new(UseStateInner2 {
|
||||
// current_val: initial_state_fn(),
|
||||
// update_callback: cx.schedule_update(),
|
||||
// wip: None,
|
||||
// update_scheuled: Cell::new(false),
|
||||
// }))
|
||||
// },
|
||||
// move |hook: &mut UseState2<T>| {
|
||||
// {
|
||||
// let r = hook.0.as_ref();
|
||||
// let mut state = r.borrow_mut();
|
||||
// state.update_scheuled.set(false);
|
||||
// if state.wip.is_some() {
|
||||
// state.current_val = state.wip.take().unwrap();
|
||||
// }
|
||||
// }
|
||||
// &*hook
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
|
||||
// pub struct UseState2<T: 'static>(Rc<UseStateInner2<T>>);
|
||||
|
||||
// impl<T> ToOwned for UseState2<T> {
|
||||
// type Owned = UseState2<T>;
|
||||
// fn to_owned(&self) -> Self::Owned {
|
||||
// UseState2(self.0.clone())
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub struct UseStateInner2<T: 'static> {
|
||||
// current_val: T,
|
||||
// update_scheuled: Cell<bool>,
|
||||
// update_callback: Rc<dyn Fn()>,
|
||||
// wip: Option<T>,
|
||||
// }
|
||||
|
||||
// impl<T: Debug> Debug for UseStateInner2<T> {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// write!(f, "{:?}", self.current_val)
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<T> UseState2<T> {
|
||||
// /// Tell the Dioxus Scheduler that we need to be processed
|
||||
// pub fn needs_update(&self) {
|
||||
// if !self.update_scheuled.get() {
|
||||
// self.update_scheuled.set(true);
|
||||
// (self.update_callback)();
|
||||
// }
|
||||
// }
|
||||
|
||||
// pub fn set(&mut self, new_val: T) -> Option<T> {
|
||||
// self.needs_update();
|
||||
// ip.wip.replace(new_val)
|
||||
// }
|
||||
|
||||
// pub fn get(&self) -> &T {
|
||||
// &self.current_val
|
||||
// }
|
||||
// }
|
||||
|
||||
// // impl<T: 'static + ToOwned<Owned = T>> UseState2<T> {
|
||||
// // /// Gain mutable access to the new value. 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.
|
||||
// // ///
|
||||
// // /// To get a reference to the current value, use `.get()`
|
||||
// // pub fn modify(&self) -> RefMut<T> {
|
||||
// // // make sure we get processed
|
||||
// // self.0.needs_update();
|
||||
|
||||
// // // Bring out the new value, cloning if it we need to
|
||||
// // // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
|
||||
// // RefMut::map(self.wip.borrow_mut(), |slot| {
|
||||
// // if slot.is_none() {
|
||||
// // *slot = Some(self.current_val.to_owned());
|
||||
// // }
|
||||
// // slot.as_mut().unwrap()
|
||||
// // })
|
||||
// // }
|
||||
|
||||
// // pub fn inner(self) -> T {
|
||||
// // self.current_val.to_owned()
|
||||
// // }
|
||||
// // }
|
||||
|
||||
// impl<T> std::ops::Deref for UseStateInner2<T> {
|
||||
// type Target = T;
|
||||
|
||||
// fn deref(&self) -> &Self::Target {
|
||||
// self.get()
|
||||
// }
|
||||
// }
|
||||
|
||||
// use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
|
||||
|
||||
// use crate::UseState;
|
||||
|
||||
// impl<T: Copy + Add<T, Output = T>> Add<T> for UseStateInner2<T> {
|
||||
// type Output = T;
|
||||
|
||||
// fn add(self, rhs: T) -> Self::Output {
|
||||
// self.current_val.add(rhs)
|
||||
// }
|
||||
// }
|
||||
// impl<T: Copy + Add<T, Output = T>> AddAssign<T> for UseStateInner2<T> {
|
||||
// fn add_assign(&mut self, rhs: T) {
|
||||
// self.set(self.current_val.add(rhs));
|
||||
// }
|
||||
// }
|
||||
// impl<T: Copy + Sub<T, Output = T>> Sub<T> for UseStateInner2<T> {
|
||||
// type Output = T;
|
||||
|
||||
// fn sub(self, rhs: T) -> Self::Output {
|
||||
// self.current_val.sub(rhs)
|
||||
// }
|
||||
// }
|
||||
// impl<T: Copy + Sub<T, Output = T>> SubAssign<T> for UseStateInner2<T> {
|
||||
// fn sub_assign(&mut self, rhs: T) {
|
||||
// self.set(self.current_val.sub(rhs));
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// MUL
|
||||
// impl<T: Copy + Mul<T, Output = T>> Mul<T> for UseStateInner2<T> {
|
||||
// type Output = T;
|
||||
|
||||
// fn mul(self, rhs: T) -> Self::Output {
|
||||
// self.current_val.mul(rhs)
|
||||
// }
|
||||
// }
|
||||
// impl<T: Copy + Mul<T, Output = T>> MulAssign<T> for UseStateInner2<T> {
|
||||
// fn mul_assign(&mut self, rhs: T) {
|
||||
// self.set(self.current_val.mul(rhs));
|
||||
// }
|
||||
// }
|
||||
// /// DIV
|
||||
// impl<T: Copy + Div<T, Output = T>> Div<T> for UseStateInner2<T> {
|
||||
// type Output = T;
|
||||
|
||||
// fn div(self, rhs: T) -> Self::Output {
|
||||
// self.current_val.div(rhs)
|
||||
// }
|
||||
// }
|
||||
// impl<T: Copy + Div<T, Output = T>> DivAssign<T> for UseStateInner2<T> {
|
||||
// fn div_assign(&mut self, rhs: T) {
|
||||
// self.set(self.current_val.div(rhs));
|
||||
// }
|
||||
// }
|
||||
// impl<V, T: PartialEq<V>> PartialEq<V> for UseStateInner2<T> {
|
||||
// fn eq(&self, other: &V) -> bool {
|
||||
// self.get() == other
|
||||
// }
|
||||
// }
|
||||
// impl<O, T: Not<Output = O> + Copy> Not for UseStateInner2<T> {
|
||||
// type Output = O;
|
||||
|
||||
// fn not(self) -> Self::Output {
|
||||
// !*self.get()
|
||||
// }
|
||||
// }
|
||||
|
||||
// // enable displaty for the handle
|
||||
// impl<T: 'static + Display> std::fmt::Display for UseStateInner2<T> {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// write!(f, "{}", self.current_val)
|
||||
// }
|
||||
// }
|
||||
@@ -44,7 +44,11 @@ pub mod on {
|
||||
// ie copy
|
||||
let shortname: &'static str = &event_name[2..];
|
||||
|
||||
c.listener(shortname, callback)
|
||||
let handler = EventHandler {
|
||||
callback: bump.alloc(std::cell::RefCell::new(Some(callback))),
|
||||
};
|
||||
|
||||
c.listener(shortname, handler)
|
||||
}
|
||||
)*
|
||||
)*
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[package]
|
||||
name = "dioxus-liveview"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
axum = { version = "0.4.2", optional = true, features = ["ws", "headers"] }
|
||||
|
||||
[features]
|
||||
default = ["axum"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.14.0", features = ["full"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tower-http = { version = "0.2.0", features = ["fs", "trace"] }
|
||||
headers = "0.3"
|
||||
@@ -0,0 +1,49 @@
|
||||
# Dioxus LiveView
|
||||
|
||||
Enabling server-rendered and hybrid applications with incredibly low latency (<1ms).
|
||||
|
||||
```rust
|
||||
#[async_std::main]
|
||||
async fn main() -> tide::Result<()> {
|
||||
let liveview_pool = dioxus::liveview::pool::default();
|
||||
let mut app = tide::new();
|
||||
|
||||
// serve the liveview client
|
||||
app.at("/").get(dioxus::liveview::liveview_frontend);
|
||||
|
||||
// and then connect the client to the backend
|
||||
app.at("/app").get(|req| dioxus::liveview::launch(App, Props { req }))
|
||||
|
||||
app.listen("127.0.0.1:8080").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Dioxus LiveView runs your Dioxus apps on the server
|
||||
|
||||
|
||||
|
||||
```rust
|
||||
use soyuz::prelude::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let mut app = soyuz::new();
|
||||
app.at("/app").get(websocket(handler));
|
||||
app.listen("127.0.0.1:8080").await.unwrap();
|
||||
}
|
||||
|
||||
async fn order_shoes(mut req: WebsocketRequest) -> Response {
|
||||
let stream = req.upgrade();
|
||||
dioxus::liveview::launch(App, stream).await;
|
||||
}
|
||||
|
||||
fn App(cx: Context, props: &()) -> Element {
|
||||
let mut count = use_state(cx, || 0);
|
||||
cx.render(rsx!(
|
||||
button { onclick: move |_| count += 1, "Incr" }
|
||||
button { onclick: move |_| count -= 1, "Decr" }
|
||||
))
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,96 @@
|
||||
//! Example websocket server.
|
||||
//!
|
||||
//! Run with
|
||||
//!
|
||||
//! ```not_rust
|
||||
//! cargo run -p example-websockets
|
||||
//! ```
|
||||
|
||||
use axum::{
|
||||
extract::{
|
||||
ws::{Message, WebSocket, WebSocketUpgrade},
|
||||
TypedHeader,
|
||||
},
|
||||
http::StatusCode,
|
||||
response::IntoResponse,
|
||||
routing::{get, get_service},
|
||||
Router,
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
use tower_http::{
|
||||
services::ServeDir,
|
||||
trace::{DefaultMakeSpan, TraceLayer},
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Set the RUST_LOG, if it hasn't been explicitly defined
|
||||
if std::env::var_os("RUST_LOG").is_none() {
|
||||
std::env::set_var("RUST_LOG", "example_websockets=debug,tower_http=debug")
|
||||
}
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
// build our application with some routes
|
||||
let app = Router::new()
|
||||
.fallback(
|
||||
get_service(
|
||||
ServeDir::new("examples/axum_assets").append_index_html_on_directories(true),
|
||||
)
|
||||
.handle_error(|error: std::io::Error| async move {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Unhandled internal error: {}", error),
|
||||
)
|
||||
}),
|
||||
)
|
||||
// routes are matched from bottom to top, so we have to put `nest` at the
|
||||
// top since it matches all routes
|
||||
.route("/ws", get(ws_handler))
|
||||
// logging so we can see whats going on
|
||||
.layer(
|
||||
TraceLayer::new_for_http()
|
||||
.make_span_with(DefaultMakeSpan::default().include_headers(true)),
|
||||
);
|
||||
|
||||
// run it with hyper
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
tracing::debug!("listening on {}", addr);
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
user_agent: Option<TypedHeader<headers::UserAgent>>,
|
||||
) -> impl IntoResponse {
|
||||
if let Some(TypedHeader(user_agent)) = user_agent {
|
||||
println!("`{}` connected", user_agent.as_str());
|
||||
}
|
||||
|
||||
ws.on_upgrade(handle_socket)
|
||||
}
|
||||
|
||||
async fn handle_socket(mut socket: WebSocket) {
|
||||
if let Some(msg) = socket.recv().await {
|
||||
if let Ok(msg) = msg {
|
||||
println!("Client says: {:?}", msg);
|
||||
} else {
|
||||
println!("client disconnected");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
if socket
|
||||
.send(Message::Text(String::from("Hi!")))
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
println!("client disconnected");
|
||||
return;
|
||||
}
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<script src='script.js'></script>
|
||||
@@ -0,0 +1,9 @@
|
||||
const socket = new WebSocket('ws://localhost:3000/ws');
|
||||
|
||||
socket.addEventListener('open', function (event) {
|
||||
socket.send('Hello Server!');
|
||||
});
|
||||
|
||||
socket.addEventListener('message', function (event) {
|
||||
console.log('Message from server ', event.data);
|
||||
});
|
||||
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
@@ -26,11 +26,14 @@ fn init_logging() {
|
||||
|
||||
static HTML_CONTENT: &'static str = include_str!("../../desktop/src/index.html");
|
||||
|
||||
pub fn launch(root: FC<()>, builder: fn(WindowBuilder) -> WindowBuilder) -> anyhow::Result<()> {
|
||||
pub fn launch(
|
||||
root: Component<()>,
|
||||
builder: fn(WindowBuilder) -> WindowBuilder,
|
||||
) -> anyhow::Result<()> {
|
||||
launch_with_props(root, (), builder)
|
||||
}
|
||||
pub fn launch_with_props<P: 'static + Send>(
|
||||
root: FC<P>,
|
||||
root: Component<P>,
|
||||
props: P,
|
||||
builder: fn(WindowBuilder) -> WindowBuilder,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -41,7 +44,7 @@ pub fn launch_with_props<P: 'static + Send>(
|
||||
/// Components used in WebviewRenderer instances can directly use system libraries, access the filesystem, and multithread with ease.
|
||||
pub struct WebviewRenderer<T> {
|
||||
/// The root component used to render the Webview
|
||||
root: FC<T>,
|
||||
root: Component<T>,
|
||||
}
|
||||
enum RpcEvent<'a> {
|
||||
Initialize {
|
||||
@@ -52,7 +55,7 @@ enum RpcEvent<'a> {
|
||||
|
||||
impl<T: 'static + Send> WebviewRenderer<T> {
|
||||
pub fn run(
|
||||
root: FC<T>,
|
||||
root: Component<T>,
|
||||
props: T,
|
||||
user_builder: fn(WindowBuilder) -> WindowBuilder,
|
||||
) -> anyhow::Result<()> {
|
||||
@@ -60,7 +63,7 @@ impl<T: 'static + Send> WebviewRenderer<T> {
|
||||
}
|
||||
|
||||
pub fn run_with_edits(
|
||||
root: FC<T>,
|
||||
root: Component<T>,
|
||||
props: T,
|
||||
user_builder: fn(WindowBuilder) -> WindowBuilder,
|
||||
redits: Option<Vec<DomEdit<'static>>>,
|
||||
|
||||
@@ -9,7 +9,7 @@ fn main() {
|
||||
dioxus_web::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
enum Route {
|
||||
Home,
|
||||
|
||||
@@ -301,13 +301,13 @@ mod tests {
|
||||
use dioxus_core_macro::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
static SIMPLE_APP: FC<()> = |cx, _| {
|
||||
static SIMPLE_APP: Component<()> = |cx, _| {
|
||||
cx.render(rsx!(div {
|
||||
"hello world!"
|
||||
}))
|
||||
};
|
||||
|
||||
static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx, _| {
|
||||
static SLIGHTLY_MORE_COMPLEX: Component<()> = |cx, _| {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
title: "About W3Schools"
|
||||
@@ -326,14 +326,14 @@ mod tests {
|
||||
})
|
||||
};
|
||||
|
||||
static NESTED_APP: FC<()> = |cx, _| {
|
||||
static NESTED_APP: Component<()> = |cx, _| {
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
SIMPLE_APP {}
|
||||
}
|
||||
))
|
||||
};
|
||||
static FRAGMENT_APP: FC<()> = |cx, _| {
|
||||
static FRAGMENT_APP: Component<()> = |cx, _| {
|
||||
cx.render(rsx!(
|
||||
div { "f1" }
|
||||
div { "f2" }
|
||||
@@ -389,7 +389,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn styles() {
|
||||
static STLYE_APP: FC<()> = |cx, _| {
|
||||
static STLYE_APP: Component<()> = |cx, _| {
|
||||
cx.render(rsx! {
|
||||
div { color: "blue", font_size: "46px" }
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ fn main() {
|
||||
dioxus_web::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let mut count = use_state(cx, || 0);
|
||||
|
||||
cx.push_task(|| async move {
|
||||
|
||||
@@ -107,7 +107,7 @@ impl Label {
|
||||
}
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, _props| {
|
||||
static App: Component<()> = |cx, _props| {
|
||||
let mut items = use_ref(cx, || vec![]);
|
||||
let mut selected = use_state(cx, || None);
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ fn main() {
|
||||
dioxus_web::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, props| {
|
||||
static App: Component<()> = |cx, props| {
|
||||
let show = use_state(cx, || true);
|
||||
|
||||
let inner = match *show {
|
||||
|
||||
@@ -14,7 +14,7 @@ fn main() {
|
||||
dioxus_web::launch(App, |c| c);
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx, _| {
|
||||
static App: Component<()> = |cx, _| {
|
||||
let doggo = cx.suspend(|| async move {
|
||||
#[derive(serde::Deserialize)]
|
||||
struct Doggo {
|
||||
|
||||
+298
-311
@@ -1,311 +1,298 @@
|
||||
/// Wasm-bindgen has a performance option to intern commonly used phrases
|
||||
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
|
||||
/// We intern all the HTML tags and attributes, making most operations much faster.
|
||||
///
|
||||
/// Interning takes < 1ms at the start of the app, but saves a *ton* of time later on.
|
||||
///
|
||||
/// Eventually we might want to procedurally generate these strings for common words, phrases, and values.
|
||||
pub(crate) fn intern_cached_strings() {
|
||||
let cached_words = [
|
||||
// Important tags to dioxus
|
||||
"dioxus-id",
|
||||
"dioxus",
|
||||
"dioxus-event-click", // todo: more events
|
||||
"click",
|
||||
// All the HTML Tags
|
||||
"a",
|
||||
"abbr",
|
||||
"address",
|
||||
"area",
|
||||
"article",
|
||||
"aside",
|
||||
"audio",
|
||||
"b",
|
||||
"base",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"big",
|
||||
"blockquote",
|
||||
"body",
|
||||
"br",
|
||||
"button",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cite",
|
||||
"code",
|
||||
"col",
|
||||
"colgroup",
|
||||
"command",
|
||||
"data",
|
||||
"datalist",
|
||||
"dd",
|
||||
"del",
|
||||
"details",
|
||||
"dfn",
|
||||
"dialog",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"embed",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"footer",
|
||||
"form",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"head",
|
||||
"header",
|
||||
"hr",
|
||||
"html",
|
||||
"i",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"ins",
|
||||
"kbd",
|
||||
"keygen",
|
||||
"label",
|
||||
"legend",
|
||||
"li",
|
||||
"link",
|
||||
"main",
|
||||
"map",
|
||||
"mark",
|
||||
"menu",
|
||||
"menuitem",
|
||||
"meta",
|
||||
"meter",
|
||||
"nav",
|
||||
"noscript",
|
||||
"object",
|
||||
"ol",
|
||||
"optgroup",
|
||||
"option",
|
||||
"output",
|
||||
"p",
|
||||
"param",
|
||||
"picture",
|
||||
"pre",
|
||||
"progress",
|
||||
"q",
|
||||
"rp",
|
||||
"rt",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"section",
|
||||
"select",
|
||||
"small",
|
||||
"source",
|
||||
"span",
|
||||
"strong",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
"table",
|
||||
"tbody",
|
||||
"td",
|
||||
"textarea",
|
||||
"tfoot",
|
||||
"th",
|
||||
"thead",
|
||||
"time",
|
||||
"title",
|
||||
"tr",
|
||||
"track",
|
||||
"u",
|
||||
"ul",
|
||||
"var",
|
||||
"video",
|
||||
"wbr",
|
||||
// All the event handlers
|
||||
"Attribute",
|
||||
"accept",
|
||||
"accept-charset",
|
||||
"accesskey",
|
||||
"action",
|
||||
"alt",
|
||||
"async",
|
||||
"autocomplete",
|
||||
"autofocus",
|
||||
"autoplay",
|
||||
"charset",
|
||||
"checked",
|
||||
"cite",
|
||||
"class",
|
||||
"cols",
|
||||
"colspan",
|
||||
"content",
|
||||
"contenteditable",
|
||||
"controls",
|
||||
"coords",
|
||||
"data",
|
||||
"data-*",
|
||||
"datetime",
|
||||
"default",
|
||||
"defer",
|
||||
"dir",
|
||||
"dirname",
|
||||
"disabled",
|
||||
"download",
|
||||
"draggable",
|
||||
"enctype",
|
||||
"for",
|
||||
"form",
|
||||
"formaction",
|
||||
"headers",
|
||||
"height",
|
||||
"hidden",
|
||||
"high",
|
||||
"href",
|
||||
"hreflang",
|
||||
"http-equiv",
|
||||
"id",
|
||||
"ismap",
|
||||
"kind",
|
||||
"label",
|
||||
"lang",
|
||||
"list",
|
||||
"loop",
|
||||
"low",
|
||||
"max",
|
||||
"maxlength",
|
||||
"media",
|
||||
"method",
|
||||
"min",
|
||||
"multiple",
|
||||
"muted",
|
||||
"name",
|
||||
"novalidate",
|
||||
"onabort",
|
||||
"onafterprint",
|
||||
"onbeforeprint",
|
||||
"onbeforeunload",
|
||||
"onblur",
|
||||
"oncanplay",
|
||||
"oncanplaythrough",
|
||||
"onchange",
|
||||
"onclick",
|
||||
"oncontextmenu",
|
||||
"oncopy",
|
||||
"oncuechange",
|
||||
"oncut",
|
||||
"ondblclick",
|
||||
"ondrag",
|
||||
"ondragend",
|
||||
"ondragenter",
|
||||
"ondragleave",
|
||||
"ondragover",
|
||||
"ondragstart",
|
||||
"ondrop",
|
||||
"ondurationchange",
|
||||
"onemptied",
|
||||
"onended",
|
||||
"onerror",
|
||||
"onfocus",
|
||||
"onhashchange",
|
||||
"oninput",
|
||||
"oninvalid",
|
||||
"onkeydown",
|
||||
"onkeypress",
|
||||
"onkeyup",
|
||||
"onload",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onmousedown",
|
||||
"onmousemove",
|
||||
"onmouseout",
|
||||
"onmouseover",
|
||||
"onmouseup",
|
||||
"onmousewheel",
|
||||
"onoffline",
|
||||
"ononline",
|
||||
"onpageshow",
|
||||
"onpaste",
|
||||
"onpause",
|
||||
"onplay",
|
||||
"onplaying",
|
||||
"onprogress",
|
||||
"onratechange",
|
||||
"onreset",
|
||||
"onresize",
|
||||
"onscroll",
|
||||
"onsearch",
|
||||
"onseeked",
|
||||
"onseeking",
|
||||
"onselect",
|
||||
"onstalled",
|
||||
"onsubmit",
|
||||
"onsuspend",
|
||||
"ontimeupdate",
|
||||
"ontoggle",
|
||||
"onunload",
|
||||
"onvolumechange",
|
||||
"onwaiting",
|
||||
"onwheel",
|
||||
"open",
|
||||
"optimum",
|
||||
"pattern",
|
||||
"placeholder",
|
||||
"poster",
|
||||
"preload",
|
||||
"readonly",
|
||||
"rel",
|
||||
"required",
|
||||
"reversed",
|
||||
"rows",
|
||||
"rowspan",
|
||||
"sandbox",
|
||||
"scope",
|
||||
"selected",
|
||||
"shape",
|
||||
"size",
|
||||
"sizes",
|
||||
"span",
|
||||
"spellcheck",
|
||||
"src",
|
||||
"srcdoc",
|
||||
"srclang",
|
||||
"srcset",
|
||||
"start",
|
||||
"step",
|
||||
"style",
|
||||
"tabindex",
|
||||
"target",
|
||||
"title",
|
||||
"translate",
|
||||
"type",
|
||||
"usemap",
|
||||
"value",
|
||||
"width",
|
||||
"wrap",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"10",
|
||||
"11",
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
];
|
||||
|
||||
for s in cached_words {
|
||||
wasm_bindgen::intern(s);
|
||||
}
|
||||
}
|
||||
pub static BUILTIN_INTERNED_STRINGS: &[&'static str] = &[
|
||||
// Important tags to dioxus
|
||||
"dioxus-id",
|
||||
"dioxus",
|
||||
"dioxus-event-click", // todo: more events
|
||||
"click",
|
||||
// All the HTML Tags
|
||||
"a",
|
||||
"abbr",
|
||||
"address",
|
||||
"area",
|
||||
"article",
|
||||
"aside",
|
||||
"audio",
|
||||
"b",
|
||||
"base",
|
||||
"bdi",
|
||||
"bdo",
|
||||
"big",
|
||||
"blockquote",
|
||||
"body",
|
||||
"br",
|
||||
"button",
|
||||
"canvas",
|
||||
"caption",
|
||||
"cite",
|
||||
"code",
|
||||
"col",
|
||||
"colgroup",
|
||||
"command",
|
||||
"data",
|
||||
"datalist",
|
||||
"dd",
|
||||
"del",
|
||||
"details",
|
||||
"dfn",
|
||||
"dialog",
|
||||
"div",
|
||||
"dl",
|
||||
"dt",
|
||||
"em",
|
||||
"embed",
|
||||
"fieldset",
|
||||
"figcaption",
|
||||
"figure",
|
||||
"footer",
|
||||
"form",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"head",
|
||||
"header",
|
||||
"hr",
|
||||
"html",
|
||||
"i",
|
||||
"iframe",
|
||||
"img",
|
||||
"input",
|
||||
"ins",
|
||||
"kbd",
|
||||
"keygen",
|
||||
"label",
|
||||
"legend",
|
||||
"li",
|
||||
"link",
|
||||
"main",
|
||||
"map",
|
||||
"mark",
|
||||
"menu",
|
||||
"menuitem",
|
||||
"meta",
|
||||
"meter",
|
||||
"nav",
|
||||
"noscript",
|
||||
"object",
|
||||
"ol",
|
||||
"optgroup",
|
||||
"option",
|
||||
"output",
|
||||
"p",
|
||||
"param",
|
||||
"picture",
|
||||
"pre",
|
||||
"progress",
|
||||
"q",
|
||||
"rp",
|
||||
"rt",
|
||||
"ruby",
|
||||
"s",
|
||||
"samp",
|
||||
"script",
|
||||
"section",
|
||||
"select",
|
||||
"small",
|
||||
"source",
|
||||
"span",
|
||||
"strong",
|
||||
"style",
|
||||
"sub",
|
||||
"summary",
|
||||
"sup",
|
||||
"table",
|
||||
"tbody",
|
||||
"td",
|
||||
"textarea",
|
||||
"tfoot",
|
||||
"th",
|
||||
"thead",
|
||||
"time",
|
||||
"title",
|
||||
"tr",
|
||||
"track",
|
||||
"u",
|
||||
"ul",
|
||||
"var",
|
||||
"video",
|
||||
"wbr",
|
||||
// All the event handlers
|
||||
"Attribute",
|
||||
"accept",
|
||||
"accept-charset",
|
||||
"accesskey",
|
||||
"action",
|
||||
"alt",
|
||||
"async",
|
||||
"autocomplete",
|
||||
"autofocus",
|
||||
"autoplay",
|
||||
"charset",
|
||||
"checked",
|
||||
"cite",
|
||||
"class",
|
||||
"cols",
|
||||
"colspan",
|
||||
"content",
|
||||
"contenteditable",
|
||||
"controls",
|
||||
"coords",
|
||||
"data",
|
||||
"data-*",
|
||||
"datetime",
|
||||
"default",
|
||||
"defer",
|
||||
"dir",
|
||||
"dirname",
|
||||
"disabled",
|
||||
"download",
|
||||
"draggable",
|
||||
"enctype",
|
||||
"for",
|
||||
"form",
|
||||
"formaction",
|
||||
"headers",
|
||||
"height",
|
||||
"hidden",
|
||||
"high",
|
||||
"href",
|
||||
"hreflang",
|
||||
"http-equiv",
|
||||
"id",
|
||||
"ismap",
|
||||
"kind",
|
||||
"label",
|
||||
"lang",
|
||||
"list",
|
||||
"loop",
|
||||
"low",
|
||||
"max",
|
||||
"maxlength",
|
||||
"media",
|
||||
"method",
|
||||
"min",
|
||||
"multiple",
|
||||
"muted",
|
||||
"name",
|
||||
"novalidate",
|
||||
"onabort",
|
||||
"onafterprint",
|
||||
"onbeforeprint",
|
||||
"onbeforeunload",
|
||||
"onblur",
|
||||
"oncanplay",
|
||||
"oncanplaythrough",
|
||||
"onchange",
|
||||
"onclick",
|
||||
"oncontextmenu",
|
||||
"oncopy",
|
||||
"oncuechange",
|
||||
"oncut",
|
||||
"ondblclick",
|
||||
"ondrag",
|
||||
"ondragend",
|
||||
"ondragenter",
|
||||
"ondragleave",
|
||||
"ondragover",
|
||||
"ondragstart",
|
||||
"ondrop",
|
||||
"ondurationchange",
|
||||
"onemptied",
|
||||
"onended",
|
||||
"onerror",
|
||||
"onfocus",
|
||||
"onhashchange",
|
||||
"oninput",
|
||||
"oninvalid",
|
||||
"onkeydown",
|
||||
"onkeypress",
|
||||
"onkeyup",
|
||||
"onload",
|
||||
"onloadeddata",
|
||||
"onloadedmetadata",
|
||||
"onloadstart",
|
||||
"onmousedown",
|
||||
"onmousemove",
|
||||
"onmouseout",
|
||||
"onmouseover",
|
||||
"onmouseup",
|
||||
"onmousewheel",
|
||||
"onoffline",
|
||||
"ononline",
|
||||
"onpageshow",
|
||||
"onpaste",
|
||||
"onpause",
|
||||
"onplay",
|
||||
"onplaying",
|
||||
"onprogress",
|
||||
"onratechange",
|
||||
"onreset",
|
||||
"onresize",
|
||||
"onscroll",
|
||||
"onsearch",
|
||||
"onseeked",
|
||||
"onseeking",
|
||||
"onselect",
|
||||
"onstalled",
|
||||
"onsubmit",
|
||||
"onsuspend",
|
||||
"ontimeupdate",
|
||||
"ontoggle",
|
||||
"onunload",
|
||||
"onvolumechange",
|
||||
"onwaiting",
|
||||
"onwheel",
|
||||
"open",
|
||||
"optimum",
|
||||
"pattern",
|
||||
"placeholder",
|
||||
"poster",
|
||||
"preload",
|
||||
"readonly",
|
||||
"rel",
|
||||
"required",
|
||||
"reversed",
|
||||
"rows",
|
||||
"rowspan",
|
||||
"sandbox",
|
||||
"scope",
|
||||
"selected",
|
||||
"shape",
|
||||
"size",
|
||||
"sizes",
|
||||
"span",
|
||||
"spellcheck",
|
||||
"src",
|
||||
"srcdoc",
|
||||
"srclang",
|
||||
"srcset",
|
||||
"start",
|
||||
"step",
|
||||
"style",
|
||||
"tabindex",
|
||||
"target",
|
||||
"title",
|
||||
"translate",
|
||||
"type",
|
||||
"usemap",
|
||||
"value",
|
||||
"width",
|
||||
"wrap",
|
||||
"0",
|
||||
"1",
|
||||
"2",
|
||||
"3",
|
||||
"4",
|
||||
"5",
|
||||
"6",
|
||||
"7",
|
||||
"8",
|
||||
"9",
|
||||
"10",
|
||||
"11",
|
||||
"12",
|
||||
"13",
|
||||
"14",
|
||||
];
|
||||
@@ -11,6 +11,7 @@
|
||||
pub struct WebConfig {
|
||||
pub(crate) hydrate: bool,
|
||||
pub(crate) rootname: String,
|
||||
pub(crate) cached_strings: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for WebConfig {
|
||||
@@ -18,6 +19,7 @@ impl Default for WebConfig {
|
||||
Self {
|
||||
hydrate: false,
|
||||
rootname: "main".to_string(),
|
||||
cached_strings: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,4 +43,12 @@ impl WebConfig {
|
||||
self.rootname = name.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the name of the element that Dioxus will use as the root.
|
||||
///
|
||||
/// This is akint to calling React.render() on the element with the specified name.
|
||||
pub fn with_string_cache(mut self, cache: Vec<String>) -> Self {
|
||||
self.cached_strings = cache;
|
||||
self
|
||||
}
|
||||
}
|
||||
+21
-18
@@ -56,11 +56,10 @@ use std::rc::Rc;
|
||||
|
||||
pub use crate::cfg::WebConfig;
|
||||
use crate::dom::load_document;
|
||||
use cache::intern_cached_strings;
|
||||
use dioxus::SchedulerMsg;
|
||||
use dioxus::VirtualDom;
|
||||
pub use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::FC;
|
||||
use dioxus_core::prelude::Component;
|
||||
use futures_util::FutureExt;
|
||||
|
||||
mod cache;
|
||||
@@ -82,15 +81,15 @@ mod ric_raf;
|
||||
///
|
||||
/// ```rust
|
||||
/// fn main() {
|
||||
/// dioxus_web::launch(App, |c| c);
|
||||
/// dioxus_web::launch(App);
|
||||
/// }
|
||||
///
|
||||
/// static App: FC<()> = |cx, props| {
|
||||
/// static App: Component<()> = |cx, props| {
|
||||
/// rsx!(cx, div {"hello world"})
|
||||
/// }
|
||||
/// ```
|
||||
pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> WebConfig) {
|
||||
launch_with_props(root_component, (), configuration)
|
||||
pub fn launch(root_component: Component<()>) {
|
||||
launch_with_props(root_component, (), |c| c);
|
||||
}
|
||||
|
||||
/// Launches the VirtualDOM from the specified component function and props.
|
||||
@@ -109,12 +108,15 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W
|
||||
/// name: String
|
||||
/// }
|
||||
///
|
||||
/// static App: FC<RootProps> = |cx, props| {
|
||||
/// static App: Component<RootProps> = |cx, props| {
|
||||
/// rsx!(cx, div {"hello {props.name}"})
|
||||
/// }
|
||||
/// ```
|
||||
pub fn launch_with_props<T, F>(root_component: FC<T>, root_properties: T, configuration_builder: F)
|
||||
where
|
||||
pub fn launch_with_props<T, F>(
|
||||
root_component: Component<T>,
|
||||
root_properties: T,
|
||||
configuration_builder: F,
|
||||
) where
|
||||
T: Send + 'static,
|
||||
F: FnOnce(WebConfig) -> WebConfig,
|
||||
{
|
||||
@@ -134,10 +136,15 @@ where
|
||||
/// wasm_bindgen_futures::spawn_local(app_fut);
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg: WebConfig) {
|
||||
pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: WebConfig) {
|
||||
let mut dom = VirtualDom::new_with_props(root, root_props);
|
||||
|
||||
intern_cached_strings();
|
||||
for s in crate::cache::BUILTIN_INTERNED_STRINGS {
|
||||
wasm_bindgen::intern(s);
|
||||
}
|
||||
for s in &cfg.cached_strings {
|
||||
wasm_bindgen::intern(s);
|
||||
}
|
||||
|
||||
let should_hydrate = cfg.hydrate;
|
||||
|
||||
@@ -155,7 +162,6 @@ pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg:
|
||||
// hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
|
||||
// ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
|
||||
if !should_hydrate {
|
||||
// log::info!("Applying rebuild edits..., {:?}", mutations);
|
||||
websys_dom.process_edits(&mut mutations.edits);
|
||||
}
|
||||
|
||||
@@ -166,20 +172,17 @@ pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg:
|
||||
// if there is work then this future resolves immediately.
|
||||
dom.wait_for_work().await;
|
||||
|
||||
// // wait for the mainthread to schedule us in
|
||||
// let mut deadline = work_loop.wait_for_idle_time().await;
|
||||
// wait for the mainthread to schedule us in
|
||||
let mut deadline = work_loop.wait_for_idle_time().await;
|
||||
|
||||
// run the virtualdom work phase until the frame deadline is reached
|
||||
let mutations = dom.work_with_deadline(|| false);
|
||||
// // run the virtualdom work phase until the frame deadline is reached
|
||||
// let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
|
||||
let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
|
||||
|
||||
// wait for the animation frame to fire so we can apply our changes
|
||||
work_loop.wait_for_raf().await;
|
||||
|
||||
for mut edit in mutations {
|
||||
// actually apply our changes during the animation frame
|
||||
// log::info!("Applying change edits..., {:?}", edit);
|
||||
websys_dom.process_edits(&mut edit.edits);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user