From f47651b32afb385297ddae00616f4308083a36e6 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 12 Feb 2021 00:29:46 -0500 Subject: [PATCH] Feat: overall API updates --- .vscode/settings.json | 2 +- CHANGELOG.md | 5 +- Cargo.toml | 2 +- packages/core/examples/contextapi.rs | 2 - packages/core/examples/listener.rs | 140 +++------------------------ packages/core/examples/nested.rs | 40 ++++++++ packages/core/examples/step.rs | 13 ++- packages/core/src/component.rs | 8 +- packages/core/src/context.rs | 2 +- packages/core/src/hooks.rs | 116 ++++++++++++++++++++++ packages/core/src/lib.rs | 3 + packages/core/src/nodes.rs | 34 ++++--- packages/core/src/scope.rs | 13 +-- packages/dioxus/src/lib.rs | 1 - packages/hooks/src/lib.rs | 14 +-- packages/html-macro-2/src/lib.rs | 16 ++- 16 files changed, 234 insertions(+), 177 deletions(-) create mode 100644 packages/core/examples/nested.rs create mode 100644 packages/core/src/hooks.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 2f418701b..80f51cff8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": true + "rust-analyzer.inlayHints.enable": false } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index cc883686f..138dc1e2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,8 +28,8 @@ # Project: Hooks + Context + Subscriptions (TBD) > Implement the foundations for state management - [x] Implement context object -- [ ] Implement use_state -- [ ] Implement use_ref +- [x] Implement use_state +- [x] Implement use_ref - [ ] Implement use_reducer - [ ] Implement use_context @@ -41,6 +41,7 @@ # Project: Initial VDOM support (TBD) > Get the initial VDom + Event System + Patching + Diffing + Component framework up and running +> Get a demo working using just the web - [x] (Core) Migrate virtual node into new VNode type - [x] (Core) Arena allocate VNodes - [x] (Core) Allow VNodes to borrow arena contents diff --git a/Cargo.toml b/Cargo.toml index 24c25ace4..3a0d7db9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ members = [ # Built-in "packages/dioxus", "packages/core", - "packages/hooks", "packages/recoil", "packages/redux", "packages/core-macro", @@ -15,6 +14,7 @@ members = [ # "packages/macro", # TODO @Jon, share the validation code # "packages/web", + # "packages/hooks", "packages/cli", "examples", "packages/html-macro", diff --git a/packages/core/examples/contextapi.rs b/packages/core/examples/contextapi.rs index dad7568d5..38cd68268 100644 --- a/packages/core/examples/contextapi.rs +++ b/packages/core/examples/contextapi.rs @@ -19,12 +19,10 @@ static Example: FC = |ctx, props| { ctx.view(move |bump| { button(bump) .on("click", move |_| { - // // println!("Value is {}", props.name); println!("Value is {}", value.as_str()); println!("Value is {}", *value); }) - // .on("click", move |_| { println!("Value is {}", props.name); }) diff --git a/packages/core/examples/listener.rs b/packages/core/examples/listener.rs index 9d7175054..e36bc0c6f 100644 --- a/packages/core/examples/listener.rs +++ b/packages/core/examples/listener.rs @@ -4,131 +4,21 @@ use dioxus_core::prelude::*; fn main() {} +/* +Our flagship demo :) + +*/ static Example: FC<()> = |ctx, props| { - todo!() - // let (val1, set_val1) = use_state(&ctx, || "b1"); + let (val1, set_val1) = use_state(&ctx, || "b1"); - // ctx.view(|bump| { - // builder::button(bump) - // .on("click", move |c| { - // // - // println!("Value is {}", val1); - // }) - // .finish() - // }) - // ctx.view(html! { - //
- // - // - // - //
- //

"Value is: {val1}"

- //
- //
- // }) + ctx.view(html! { +
+ + + +
+

"Value is: {val1}"

+
+
+ }) }; - -use use_state_def::use_state; -mod use_state_def { - use dioxus_core::prelude::*; - use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc}; - - struct UseState { - new_val: Rc>>, - current_val: T, - caller: Box, - } - - /// Store state between component renders! - /// When called, this hook retrives a stored value and provides a setter to update that value. - /// When the setter is called, the component is re-ran with the new value. - /// - /// This is behaves almost exactly the same way as React's "use_state". - /// - /// Usage: - /// ```ignore - /// static Example: FC<()> = |ctx| { - /// let (counter, set_counter) = use_state(ctx, || 0); - /// let increment = || set_couter(counter + 1); - /// let decrement = || set_couter(counter + 1); - /// - /// html! { - ///
- ///

"Counter: {counter}"

- /// - /// - ///
- /// } - /// } - /// ``` - pub fn use_state<'b, 'a, T: 'static, F: FnOnce() -> T + 'static>( - ctx: &'b Context<'a>, - initial_state_fn: F, - ) -> (&'a T, &'a impl Fn(T)) { - ctx.use_hook( - move || UseState { - new_val: Rc::new(RefCell::new(None)), - current_val: initial_state_fn(), - caller: Box::new(|_| println!("setter called!")), - }, - move |hook| { - let inner = hook.new_val.clone(); - let scheduled_update = ctx.schedule_update(); - - // get ownership of the new val and replace the current with the new - // -> as_ref -> borrow_mut -> deref_mut -> take - // -> rc -> &RefCell -> RefMut -> &Option -> T - if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() { - hook.current_val = new_val; - } - - // todo: swap out the caller with a subscription call and an internal update - hook.caller = Box::new(move |new_val| { - // update the setter with the new value - let mut new_inner = inner.as_ref().borrow_mut(); - *new_inner = Some(new_val); - - // Ensure the component gets updated - scheduled_update(); - }); - - // box gets derefed into a ref which is then taken as ref with the hook - (&hook.current_val, &hook.caller) - }, - |_| {}, - ) - } -} - -mod use_ref_def { - use dioxus_core::prelude::*; - use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc}; - - pub struct UseRef { - current: RefCell, - } - impl UseRef { - fn new(val: T) -> Self { - Self { - current: RefCell::new(val), - } - } - - fn modify(&self, modifier: impl FnOnce(&mut T)) { - let mut val = self.current.borrow_mut(); - let val_as_ref = val.deref_mut(); - modifier(val_as_ref); - } - } - - /// Store a mutable value between renders! - /// To read the value, borrow the ref. - /// To change it, use modify. - /// Modifications to this value do not cause updates to the component - pub fn use_ref<'a, T: 'static>( - ctx: &'a Context<'a>, - initial_state_fn: impl FnOnce() -> T + 'static, - ) -> &'a UseRef { - ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {}) - } -} diff --git a/packages/core/examples/nested.rs b/packages/core/examples/nested.rs new file mode 100644 index 000000000..333d9b90f --- /dev/null +++ b/packages/core/examples/nested.rs @@ -0,0 +1,40 @@ +#![allow(unused)] +//! Example of components in + +use std::borrow::Borrow; + +use dioxus_core::prelude::*; + +fn main() {} + +static Header: FC<()> = |ctx, props| { + let inner = use_ref(&ctx, || 0); + + let handler1 = move || println!("Value is {}", inner.current()); + + ctx.view(html! { +
+

"This is the header bar"

+

"Idnt it awesome"

+ +
+ }) +}; + +static Bottom: FC<()> = |ctx, props| { + ctx.view(html! { +
+

"bruh 1"

+

"bruh 2"

+
+ }) +}; + +static Example: FC<()> = |ctx, props| { + ctx.view(html! { +
+

"BROSKI!"

+

"DRO!"

+
+ }) +}; diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index 990b81183..70ef1c696 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -20,15 +20,20 @@ struct Props { name: String, } impl Properties for Props { - fn new() -> Self { - todo!() - } + fn call(&self, ptr: *const ()) {} + + // fn new() -> Self { + // todo!() + // } } static Example: FC = |ctx, props| { ctx.view(html! {
-

+

"hello world!"

+

"hello world!"

+

"hello world!"

+

"hello world!"

}) }; diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 7097a6ec0..bb3d3584c 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -23,15 +23,13 @@ impl Component for FC

{ /// The `Properties` trait defines any struct that can be constructed using a combination of default / optional fields. /// Components take a "properties" object -pub trait Properties: 'static { - fn new() -> Self; +pub trait Properties { + fn call(&self, ptr: *const ()) {} } // Auto implement for no-prop components impl Properties for () { - fn new() -> Self { - () - } + fn call(&self, ptr: *const ()) {} } #[cfg(test)] diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 8e82d45a5..b47af34df 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -27,7 +27,7 @@ use std::{ // todo: force lifetime of source into T as a valid lifetime too // it's definitely possible, just needs some more messing around pub struct Context<'src> { - // pub struct Context<'src, PropType> { + pub(crate) scope: &'src Scope, /// Direct access to the properties used to create this component. // pub props: &'src PropType, pub idx: AtomicUsize, diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs new file mode 100644 index 000000000..4d3fe4398 --- /dev/null +++ b/packages/core/src/hooks.rs @@ -0,0 +1,116 @@ +//! Useful, foundational hooks that 3rd parties can implement. +//! Currently implemented: +//! - use_ref +//! - use_state + +pub use use_ref_def::use_ref; +pub use use_state_def::use_state; + +mod use_state_def { + use crate::inner::*; + use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc}; + + struct UseState { + new_val: Rc>>, + current_val: T, + caller: Box, + } + + /// Store state between component renders! + /// When called, this hook retrives a stored value and provides a setter to update that value. + /// When the setter is called, the component is re-ran with the new value. + /// + /// This is behaves almost exactly the same way as React's "use_state". + /// + /// Usage: + /// ```ignore + /// static Example: FC<()> = |ctx| { + /// let (counter, set_counter) = use_state(ctx, || 0); + /// let increment = || set_couter(counter + 1); + /// let decrement = || set_couter(counter + 1); + /// + /// html! { + ///

+ ///

"Counter: {counter}"

+ /// + /// + ///
+ /// } + /// } + /// ``` + pub fn use_state<'a, T: 'static, F: FnOnce() -> T + 'static>( + ctx: &'_ Context<'a>, + initial_state_fn: F, + ) -> (&'a T, &'a impl Fn(T)) { + ctx.use_hook( + move || UseState { + new_val: Rc::new(RefCell::new(None)), + current_val: initial_state_fn(), + caller: Box::new(|_| println!("setter called!")), + }, + move |hook| { + let inner = hook.new_val.clone(); + let scheduled_update = ctx.schedule_update(); + + // get ownership of the new val and replace the current with the new + // -> as_ref -> borrow_mut -> deref_mut -> take + // -> rc -> &RefCell -> RefMut -> &Option -> T + if let Some(new_val) = hook.new_val.as_ref().borrow_mut().deref_mut().take() { + hook.current_val = new_val; + } + + // todo: swap out the caller with a subscription call and an internal update + hook.caller = Box::new(move |new_val| { + // update the setter with the new value + let mut new_inner = inner.as_ref().borrow_mut(); + *new_inner = Some(new_val); + + // Ensure the component gets updated + scheduled_update(); + }); + + // box gets derefed into a ref which is then taken as ref with the hook + (&hook.current_val, &hook.caller) + }, + |_| {}, + ) + } +} + +mod use_ref_def { + use crate::inner::*; + use std::{borrow::BorrowMut, cell::RefCell, ops::DerefMut, rc::Rc}; + + pub struct UseRef { + _current: RefCell, + } + impl UseRef { + fn new(val: T) -> Self { + Self { + _current: RefCell::new(val), + } + } + + fn modify(&self, modifier: impl FnOnce(&mut T)) { + let mut val = self._current.borrow_mut(); + let val_as_ref = val.deref_mut(); + modifier(val_as_ref); + } + + pub fn current(&self) -> std::cell::Ref<'_, T> { + self._current.borrow() + } + } + + /// Store a mutable value between renders! + /// To read the value, borrow the ref. + /// To change it, use modify. + /// Modifications to this value do not cause updates to the component + /// Attach to inner context reference, so context can be consumed + pub fn use_ref<'a, T: 'static>( + ctx: &'_ Context<'a>, + initial_state_fn: impl FnOnce() -> T + 'static, + ) -> &'a UseRef { + ctx.use_hook(|| UseRef::new(initial_state_fn()), |state| &*state, |_| {}) + } +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index ef1cfef27..fc96814ad 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -69,6 +69,7 @@ pub mod component; pub mod context; pub mod debug_renderer; pub mod events; +pub mod hooks; pub mod nodebuilder; pub mod nodes; pub mod scope; @@ -131,4 +132,6 @@ pub mod prelude { pub use crate::nodebuilder as builder; pub use dioxus_core_macro::fc; pub use dioxus_html_2::html; + + pub use crate::hooks::*; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 0d68f4a17..d6f1a69fe 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -37,7 +37,7 @@ mod vnode { Suspended, /// A User-defined componen node (node type COMPONENT_NODE) - Component(VComponent), + Component(VComponent<'src>), } impl<'a> VNode<'a> { @@ -174,11 +174,7 @@ mod velement { } /// An event listener. - pub struct Listener<'a> -// pub struct Listener<'a, 'b> - // where - // 'b: 'a + 'static, - { + pub struct Listener<'a> { /// The type of event to listen for. pub(crate) event: &'a str, /// The callback to invoke when the event happens. @@ -249,19 +245,33 @@ mod vtext { /// Only supports the functional syntax mod vcomponent { use crate::prelude::Properties; - use std::{any::TypeId, fmt, future::Future}; + use std::{any::TypeId, fmt, future::Future, marker::PhantomData}; use super::VNode; - #[derive(PartialEq)] - pub struct VComponent { - // props_id: TypeId, + + pub struct VComponent<'src> { + _p: PhantomData<&'src ()>, + runner: Box, + caller: *const (), + props_type: TypeId, + } + + // impl<'src> PartialEq for CallerSource<'src> { + // fn eq(&self, other: &Self) -> bool { + // todo!() + // } + // } + // caller: Box VComponent<'a> { // /// Construct a VComponent directly from a function component // /// This should be *very* fast - we store the function pointer and props type ID. It should also be small on the stack // pub fn from_fn(f: FC

, props: P) -> Self { diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 3231d0269..a9992eea1 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -47,18 +47,19 @@ impl Scope { } } - pub fn create_context<'runner, T: Properties>( - &'runner mut self, - components: &'runner generational_arena::Arena, - props: &'runner T, + pub fn create_context<'a, T: Properties>( + &'a mut self, + components: &'a generational_arena::Arena, + props: &'a T, ) -> Context { - // ) -> Context { + + // Context { + scope: &*self, _p: PhantomData {}, arena: &self.hook_arena, hooks: &self.hooks, idx: 0.into(), - // props, components, } } diff --git a/packages/dioxus/src/lib.rs b/packages/dioxus/src/lib.rs index c22bc56c1..cb1b61434 100644 --- a/packages/dioxus/src/lib.rs +++ b/packages/dioxus/src/lib.rs @@ -1,7 +1,6 @@ pub mod prelude { pub use dioxus_core::prelude::*; pub use dioxus_core_macro::fc; - pub use dioxus_hooks::prelude::use_state; } use dioxus_core::prelude::FC; diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index e38630898..4820d2607 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -1,10 +1,10 @@ // mod hooks; // pub use hooks::use_context; -pub mod prelude { - use dioxus_core::prelude::Context; - pub fn use_state(ctx: &Context, init: impl Fn() -> T) -> (T, impl Fn(T)) { - let g = init(); - (g, |_| {}) - } -} +// pub mod prelude { +// use dioxus_core::prelude::Context; +// pub fn use_state(ctx: &Context, init: impl Fn() -> T) -> (T, impl Fn(T)) { +// let g = init(); +// (g, |_| {}) +// } +// } diff --git a/packages/html-macro-2/src/lib.rs b/packages/html-macro-2/src/lib.rs index 3414f8e36..bf20f922a 100644 --- a/packages/html-macro-2/src/lib.rs +++ b/packages/html-macro-2/src/lib.rs @@ -1,7 +1,6 @@ use ::{ proc_macro::TokenStream, proc_macro2::{Span, TokenStream as TokenStream2}, - proc_macro_hack::proc_macro_hack, quote::{quote, ToTokens, TokenStreamExt}, style_shared::Styles, syn::{ @@ -77,7 +76,6 @@ struct NodeList(Vec>); impl ToTokens for ToToksCtx<&NodeList> { fn to_tokens(&self, tokens: &mut TokenStream2) { - // let ctx = &self.ctx; let nodes = self.inner.0.iter().map(|node| self.recurse(node)); tokens.append_all(quote! { dioxus::bumpalo::vec![in bump; @@ -226,12 +224,17 @@ impl Parse for Attr { let mut name = Ident::parse_any(s)?; let name_str = name.to_string(); s.parse::()?; + + // Check if this is an event handler + // If so, parse into literal tokens let ty = if name_str.starts_with("on") { // remove the "on" bit name = Ident::new(&name_str.trim_start_matches("on"), name.span()); let content; syn::braced!(content in s); + // AttrType::Value(content.parse()?) AttrType::Event(content.parse()?) + // AttrType::Event(content.parse()?) } else { let lit_str = if name_str == "style" && s.peek(token::Brace) { // special-case to deal with literal styles. @@ -332,23 +335,16 @@ where /// ToTokens context struct ToToksCtx { - // struct ToToksCtx<'a, T> { inner: T, - // ctx: &'a Ident, } impl<'a, T> ToToksCtx { fn new(inner: T) -> Self { - // fn new(ctx: &'a Ident, inner: T) -> Self { ToToksCtx { inner } } fn recurse(&self, inner: U) -> ToToksCtx { - // fn recurse(&self, inner: U) -> ToToksCtx<'a, U> { - ToToksCtx { - // ctx: &self.ctx, - inner, - } + ToToksCtx { inner } } }