Bladeren bron

feat: beaf up the use_state hook

Jonathan Kelley 4 jaren geleden
bovenliggende
commit
e4cdb64

+ 1 - 1
examples/calculator.rs

@@ -5,7 +5,7 @@
 // use dioxus::prelude::*;
 
 fn main() {
-    dioxus::desktop::launch(App);
+    dioxus::desktop::launch(App, |cfg| cfg);
 }
 
 use dioxus::events::on::*;

+ 140 - 113
examples/model.rs

@@ -18,39 +18,138 @@
 use dioxus::events::on::*;
 use dioxus::prelude::*;
 
+const STYLE: &str = include_str!("./assets/calculator.css");
 fn main() {
-    dioxus::webview::launch(App)
+    dioxus::desktop::launch(App, |cfg| {
+        cfg.title("Calculator Demo").resizable(false).size(350, 550)
+    });
+}
+
+enum Operator {
+    Add,
+    Sub,
+    Mul,
+    Div,
 }
 
 static App: FC<()> = |cx| {
-    let calc = use_model(cx, || Calculator::new());
+    let (cur_val, set_cur_val) = use_state_classic(cx, || 0.0_f64);
+    let (operator, set_operator) = use_state_classic(cx, || None as Option<Operator>);
+    let (display_value, set_display_value) = use_state_classic(cx, || "0".to_string());
 
-    let clear_display = calc.display_value.eq("0");
+    let clear_display = display_value.eq("0");
     let clear_text = if clear_display { "C" } else { "AC" };
-    let formatted = calc.formatted();
+
+    let input_digit = move |num: u8| {
+        let mut new = if operator.is_some() {
+            String::new()
+        } else if display_value == "0" {
+            String::new()
+        } else {
+            display_value.clone()
+        };
+        if operator.is_some() {
+            let val = display_value.parse::<f64>().unwrap();
+            set_cur_val(val);
+        }
+
+        new.push_str(num.to_string().as_str());
+        set_display_value(new);
+    };
+
+    let input_dot = move || {
+        let mut new = display_value.clone();
+        new.push_str(".");
+        set_display_value(new);
+    };
+
+    let perform_operation = move || {
+        if let Some(op) = operator.as_ref() {
+            let rhs = display_value.parse::<f64>().unwrap();
+            let new_val = match op {
+                Operator::Add => *cur_val + rhs,
+                Operator::Sub => *cur_val - rhs,
+                Operator::Mul => *cur_val * rhs,
+                Operator::Div => *cur_val / rhs,
+            };
+            set_cur_val(new_val);
+            set_display_value(new_val.to_string());
+            set_operator(None);
+        }
+    };
+
+    let toggle_sign = move |_| {
+        if display_value.starts_with("-") {
+            set_display_value(display_value.trim_start_matches("-").to_string())
+        } else {
+            set_display_value(format!("-{}", *display_value))
+        }
+    };
+    let toggle_percent = move |_| todo!();
+
+    let clear_key = move |_| {
+        set_display_value("0".to_string());
+        if !clear_display {
+            set_operator(None);
+            set_cur_val(0.0);
+        }
+    };
+
+    let keydownhandler = move |evt: KeyboardEvent| match evt.key_code() {
+        KeyCode::Backspace => {
+            let mut new = display_value.clone();
+            if !new.as_str().eq("0") {
+                new.pop();
+            }
+            set_display_value(new);
+        }
+        KeyCode::_0 => input_digit(0),
+        KeyCode::_1 => input_digit(1),
+        KeyCode::_2 => input_digit(2),
+        KeyCode::_3 => input_digit(3),
+        KeyCode::_4 => input_digit(4),
+        KeyCode::_5 => input_digit(5),
+        KeyCode::_6 => input_digit(6),
+        KeyCode::_7 => input_digit(7),
+        KeyCode::_8 => input_digit(8),
+        KeyCode::_9 => input_digit(9),
+        KeyCode::Add => set_operator(Some(Operator::Add)),
+        KeyCode::Subtract => set_operator(Some(Operator::Sub)),
+        KeyCode::Divide => set_operator(Some(Operator::Div)),
+        KeyCode::Multiply => set_operator(Some(Operator::Mul)),
+        _ => {}
+    };
 
     cx.render(rsx! {
-        div { class: "calculator", onkeydown: move |evt| calc.get_mut().handle_keydown(evt),
-            div { class: "calculator-display", "{formatted}" }
-            div { class: "input-keys"
-                div { class: "function-keys"
-                    CalculatorKey { name: "key-clear", onclick: move |_| calc.get_mut().clear_display(), "{clear_text}" }
-                    CalculatorKey { name: "key-sign", onclick: move |_| calc.get_mut().toggle_sign(), "±"}
-                    CalculatorKey { name: "key-percent", onclick: move |_| calc.get_mut().toggle_percent(), "%"}
-                }
-                div { class: "digit-keys"
-                    CalculatorKey { name: "key-0", onclick: move |_| calc.get_mut().input_digit(0), "0" }
-                    CalculatorKey { name: "key-dot", onclick: move |_|  calc.get_mut().input_dot(), "●" }
-                    {(1..9).map(move |k| rsx!{
-                        CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_|  calc.get_mut().input_digit(k), "{k}" }
-                    })}
-                }
-                div { class: "operator-keys"
-                    CalculatorKey { name:"key-divide", onclick: move |_| calc.get_mut().set_operator(Operator::Div) "÷" }
-                    CalculatorKey { name:"key-multiply", onclick: move |_| calc.get_mut().set_operator(Operator::Mul) "×" }
-                    CalculatorKey { name:"key-subtract", onclick: move |_| calc.get_mut().set_operator(Operator::Sub) "−" }
-                    CalculatorKey { name:"key-add", onclick: move |_| calc.get_mut().set_operator(Operator::Add) "+" }
-                    CalculatorKey { name:"key-equals", onclick: move |_| calc.get_mut().perform_operation() "=" }
+        div {
+            id: "wrapper"
+            div { class: "app" onkeydown: {keydownhandler}
+                style { "{STYLE}" }
+                div { class: "calculator", 
+                    CalculatorDisplay { val: &display_value}
+                    div { class: "calculator-keypad"
+                        div { class: "input-keys"
+                            div { class: "function-keys"
+                                CalculatorKey { name: "key-clear", onclick: {clear_key} "{clear_text}" }
+                                CalculatorKey { name: "key-sign", onclick: {toggle_sign}, "±"}
+                                CalculatorKey { name: "key-percent", onclick: {toggle_percent} "%"}
+                            }
+                            div { class: "digit-keys"
+                                CalculatorKey { name: "key-0", onclick: move |_| input_digit(0), "0" }
+                                CalculatorKey { name: "key-dot", onclick: move |_|  input_dot(), "●" }
+                                {(1..10).map(move |k| rsx!{
+                                    CalculatorKey { key: "{k}", name: "key-{k}", onclick: move |_| input_digit(k), "{k}" }
+                                })}
+                            }
+                        }
+                        div { class: "operator-keys"
+                            CalculatorKey { name: "key-divide", onclick: move |_| set_operator(Some(Operator::Div)) "÷" }
+                            CalculatorKey { name: "key-multiply", onclick: move |_| set_operator(Some(Operator::Mul)) "×" }
+                            CalculatorKey { name: "key-subtract", onclick: move |_| set_operator(Some(Operator::Sub)) "−" }
+                            CalculatorKey { name: "key-add", onclick: move |_| set_operator(Some(Operator::Add)) "+" }
+                            CalculatorKey { name: "key-equals", onclick: move |_| perform_operation() "=" }
+                        }
+                    }
                 }
             }
         }
@@ -59,11 +158,14 @@ static App: FC<()> = |cx| {
 
 #[derive(Props)]
 struct CalculatorKeyProps<'a> {
+    /// Name!
     name: &'static str,
+
+    /// Click!
     onclick: &'a dyn Fn(MouseEvent),
 }
 
-fn CalculatorKey<'a>(cx: Context<'a, CalculatorKeyProps>) -> VNode<'a> {
+fn CalculatorKey<'a, 'r>(cx: Context<'a, CalculatorKeyProps<'r>>) -> VNode<'a> {
     cx.render(rsx! {
         button {
             class: "calculator-key {cx.name}"
@@ -73,94 +175,19 @@ fn CalculatorKey<'a>(cx: Context<'a, CalculatorKeyProps>) -> VNode<'a> {
     })
 }
 
-#[derive(Clone)]
-struct Calculator {
-    display_value: String,
-    operator: Option<Operator>,
-    cur_val: f64,
-}
-
-#[derive(Clone)]
-enum Operator {
-    Add,
-    Sub,
-    Mul,
-    Div,
+#[derive(Props, PartialEq)]
+struct CalculatorDisplayProps<'a> {
+    val: &'a str,
 }
 
-impl Calculator {
-    fn new() -> Self {
-        Calculator {
-            display_value: "0".to_string(),
-            operator: None,
-            cur_val: 0.0,
-        }
-    }
-    fn formatted(&self) -> String {
-        use separator::Separatable;
-        self.cur_val.separated_string()
-    }
-    fn clear_display(&mut self) {
-        self.display_value = "0".to_string();
-    }
-    fn input_digit(&mut self, digit: u8) {
-        self.display_value.push_str(digit.to_string().as_str())
-    }
-    fn input_dot(&mut self) {
-        if self.display_value.find(".").is_none() {
-            self.display_value.push_str(".");
-        }
-    }
-    fn perform_operation(&mut self) {
-        if let Some(op) = &self.operator {
-            let rhs = self.display_value.parse::<f64>().unwrap();
-            let new_val = match op {
-                Operator::Add => self.cur_val + rhs,
-                Operator::Sub => self.cur_val - rhs,
-                Operator::Mul => self.cur_val * rhs,
-                Operator::Div => self.cur_val / rhs,
-            };
-            self.cur_val = new_val;
-            self.display_value = new_val.to_string();
-            self.operator = None;
-        }
-    }
-    fn toggle_sign(&mut self) {
-        if self.display_value.starts_with("-") {
-            self.display_value = self.display_value.trim_start_matches("-").to_string();
-        } else {
-            self.display_value = format!("-{}", self.display_value);
-        }
-    }
-    fn toggle_percent(&mut self) {
-        self.display_value = (self.display_value.parse::<f64>().unwrap() / 100.0).to_string();
-    }
-    fn backspace(&mut self) {
-        if !self.display_value.as_str().eq("0") {
-            self.display_value.pop();
-        }
-    }
-    fn set_operator(&mut self, operator: Operator) {
-        self.operator = Some(operator)
-    }
-    fn handle_keydown(&mut self, evt: KeyboardEvent) {
-        match evt.key_code() {
-            KeyCode::Backspace => self.backspace(),
-            KeyCode::_0 => self.input_digit(0),
-            KeyCode::_1 => self.input_digit(1),
-            KeyCode::_2 => self.input_digit(2),
-            KeyCode::_3 => self.input_digit(3),
-            KeyCode::_4 => self.input_digit(4),
-            KeyCode::_5 => self.input_digit(5),
-            KeyCode::_6 => self.input_digit(6),
-            KeyCode::_7 => self.input_digit(7),
-            KeyCode::_8 => self.input_digit(8),
-            KeyCode::_9 => self.input_digit(9),
-            KeyCode::Add => self.operator = Some(Operator::Add),
-            KeyCode::Subtract => self.operator = Some(Operator::Sub),
-            KeyCode::Divide => self.operator = Some(Operator::Div),
-            KeyCode::Multiply => self.operator = Some(Operator::Mul),
-            _ => {}
+fn CalculatorDisplay<'a>(cx: Context<'a, CalculatorDisplayProps>) -> VNode<'a> {
+    use separator::Separatable;
+    // Todo, add float support to the num-format crate
+    let formatted = cx.val.parse::<f64>().unwrap().separated_string();
+    // TODO: make it autoscaling with css
+    cx.render(rsx! {
+        div { class: "calculator-display"
+            div { class: "auto-scaling-text", "{formatted}" }
         }
-    }
+    })
 }

+ 4 - 2
examples/reducer.rs

@@ -4,10 +4,12 @@
 //! This pattern is very useful when a single component can handle many types of input that can
 //! be represented by an enum.
 
-fn main() {}
 use dioxus::prelude::*;
+fn main() {
+    dioxus::desktop::launch(App, |c| c);
+}
 
-pub static ExampleReducer: FC<()> = |cx| {
+pub static App: FC<()> = |cx| {
     let (state, reduce) = use_reducer(cx, PlayerState::new, PlayerState::reduce);
 
     let is_playing = state.is_playing();

+ 1 - 1
examples/rsx_usage.rs

@@ -39,7 +39,7 @@
 //! - Allow top-level fragments
 //!
 fn main() {
-    dioxus::webview::launch(Example);
+    dioxus::desktop::launch(Example, |c| c);
 }
 
 /// When trying to return "nothing" to Dioxus, you'll need to specify the type parameter or Rust will be sad.

+ 1 - 1
examples/webview.rs

@@ -12,7 +12,7 @@
 use dioxus::prelude::*;
 
 fn main() {
-    dioxus::desktop::launch(App);
+    dioxus::desktop::launch(App, |c| c);
 }
 
 static App: FC<()> = |cx| {

+ 14 - 3
packages/core-macro/src/rsx/component.rs

@@ -72,6 +72,8 @@ pub struct BodyParseConfig {
     pub allow_manual_props: bool,
 }
 
+// todo: unify this body parsing for both elements and components
+// both are style rather ad-hoc, though components are currently more configured
 pub fn parse_component_body(
     content: &ParseBuffer,
     cfg: &BodyParseConfig,
@@ -87,18 +89,27 @@ pub fn parse_component_body(
 
         if content.peek(Token![..]) {
             if !cfg.allow_manual_props {
-                // toss an error
+                return Err(Error::new(
+                    content.span(),
+                    "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
+                ));
             }
             content.parse::<Token![..]>()?;
             *manual_props = Some(content.parse::<Expr>()?);
         } else if content.peek(Ident) && content.peek2(Token![:]) {
             if !cfg.allow_fields {
-                // toss an error
+                return Err(Error::new(
+                    content.span(),
+                    "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
+                ));
             }
             body.push(content.parse::<ComponentField>()?);
         } else {
             if !cfg.allow_children {
-                // toss an error
+                return Err(Error::new(
+                    content.span(),
+                    "This item is not allowed to accept children.",
+                ));
             }
             children.push(content.parse::<Node>()?);
         }

+ 3 - 3
packages/core-macro/src/rsx/element.rs

@@ -22,14 +22,14 @@ pub struct Element {
 impl ToTokens for Element {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = &self.name;
-        // let name = &self.name.to_string();
 
         tokens.append_all(quote! {
             __cx.element(dioxus_elements::#name)
         });
 
-        // Add attributes
-        // TODO: conver to the "attrs" syntax for compile-time known sizes
+        // By gating these methods, we can keep the output of `cargo expand` readable.
+        // We also prevent the issue where zero-sized arrays fail to have propery type inference.
+
         if self.attributes.len() > 0 {
             let attr = &self.attributes;
             tokens.append_all(quote! {

+ 37 - 73
packages/core/src/hooks.rs

@@ -8,7 +8,7 @@
 use crate::innerlude::Context;
 
 use std::{
-    cell::RefCell,
+    cell::{Cell, RefCell},
     ops::{Deref, DerefMut},
     rc::Rc,
 };
@@ -87,39 +87,47 @@ use std::{
 };
 
 pub struct UseState<T: 'static> {
-    modifier: Rc<RefCell<Option<Box<dyn FnOnce(&mut T)>>>>,
     current_val: T,
-    update: Box<dyn Fn() + 'static>,
-    setter: Box<dyn Fn(T) + 'static>,
-    // setter: Box<dyn Fn(T) + 'static>,
+    callback: Rc<dyn Fn()>,
+    wip: RefCell<Option<T>>,
 }
 
-// #[derive(Clone, Copy)]
-// pub struct UseStateHandle<'a, T: 'static> {
-//     inner: &'a UseState<T>,
-//     // pub setter: &'a dyn Fn(T),
-//     // pub modifier: &'a dyn Fn(&mut T),
-// }
-
-impl<'a, T: 'static> UseState<T> {
-    pub fn setter(&self) -> &dyn Fn(T) {
-        &self.setter
-        // let r = self.setter.as_mut();
-        // unsafe { Pin::get_unchecked_mut(r) }
+impl<T: 'static> UseState<T> {
+    /// Tell the Dioxus Scheduler that we need to be processed
+    pub fn needs_update(&self) {
+        (self.callback)();
     }
 
     pub fn set(&self, new_val: T) {
-        self.modify(|f| *f = new_val);
+        self.needs_update();
+        *self.wip.borrow_mut() = Some(new_val);
     }
 
-    // signal that we need to be updated
-    // save the modifier
-    pub fn modify(&self, f: impl FnOnce(&mut T) + 'static) {
-        let mut slot = self.modifier.as_ref().borrow_mut();
-        *slot = Some(Box::new(f));
-        (self.update)();
+    pub fn get(&self) -> &T {
+        &self.current_val
+    }
+
+    /// Get the current status of the work-in-progress data
+    pub fn get_wip(&self) -> Ref<Option<T>> {
+        self.wip.borrow()
     }
 }
+impl<'a, T: 'static + ToOwned<Owned = T>> UseState<T> {
+    pub fn get_mut<'r>(&'r self) -> RefMut<'r, T> {
+        // make sure we get processed
+        self.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()
+        })
+    }
+}
+
 impl<'a, T: 'static> std::ops::Deref for UseState<T> {
     type Target = T;
 
@@ -163,67 +171,23 @@ pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P>(
 ) -> &'a UseState<T> {
     cx.use_hook(
         move || UseState {
-            modifier: Rc::new(RefCell::new(None)),
             current_val: initial_state_fn(),
-            update: Box::new(|| {}),
-            setter: Box::new(|_| {}),
+            callback: cx.schedule_update(),
+            wip: RefCell::new(None),
         },
         move |hook| {
             log::debug!("addr of hook: {:#?}", hook as *const _);
-            let scheduled_update = cx.schedule_update();
-
-            // log::debug!("Checking if new value {:#?}", &hook.current_val);
-            // 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> -> T
-            if let Some(new_val) = hook.modifier.as_ref().borrow_mut().deref_mut().take() {
-                // log::debug!("setting prev {:#?}", &hook.current_val);
-                (new_val)(&mut hook.current_val);
-                // log::debug!("setting new value {:#?}", &hook.current_val);
+            let mut new_val = hook.wip.borrow_mut();
+            if new_val.is_some() {
+                hook.current_val = new_val.take().unwrap();
             }
 
-            hook.update = Box::new(move || scheduled_update());
-
-            let modifier = hook.modifier.clone();
-            hook.setter = Box::new(move |new_val: T| {
-                let mut slot = modifier.as_ref().borrow_mut();
-
-                let slot2 = slot.deref_mut();
-                *slot2 = Some(Box::new(move |old: &mut T| *old = new_val));
-            });
-
             &*hook
         },
         |_| {},
     )
 }
 
-// pub struct UseRef<T: 'static> {
-//     _current: RefCell<T>,
-// }
-// impl<T: 'static> UseRef<T> {
-//     fn new(val: T) -> Self {
-//         Self {
-//             _current: RefCell::new(val),
-//         }
-//     }
-
-//     pub fn set(&self, new: T) {
-//         let mut val = self._current.borrow_mut();
-//         *val = new;
-//     }
-
-//     pub 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.

+ 2 - 15
packages/core/src/nodebuilder.rs

@@ -549,13 +549,8 @@ impl<'a> IntoVNode<'a> for VNode<'a> {
 }
 
 impl<'a> IntoVNode<'a> for &VNode<'a> {
-    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>
-// where
-    //     'a: 'c,
-    {
-        todo!()
-        // cloning is cheap since vnodes are just references into bump arenas
-        // self.clone()
+    fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
+        self.clone()
     }
 }
 
@@ -563,14 +558,6 @@ pub trait IntoVNode<'a> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
 }
 
-// pub trait VNodeBuilder<'a, G>: IntoIterator<Item = G>
-// where
-//     G: IntoVNode<'a>,
-// {
-// }
-
-// impl<'a, F> VNodeBuilder<'a, LazyNodes<F>> for LazyNodes<F> where F: FnOnce(NodeFactory) -> VNode {}
-
 // Wrap the the node-builder closure in a concrete type.
 // ---
 // This is a bit of a hack to implement the IntoVNode trait for closure types.

+ 6 - 1
packages/html/src/lib.rs

@@ -62,18 +62,22 @@ builder_constructors! {
     /// [`<head>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head)
     /// element.
     head;
+
     /// Build a
     /// [`<link>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link)
     /// element.
     link;
+
     /// Build a
     /// [`<meta>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)
     /// element.
     meta;
+
     /// Build a
     /// [`<style>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style)
     /// element.
     style;
+
     /// Build a
     /// [`<title>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title)
     /// element.
@@ -109,7 +113,6 @@ builder_constructors! {
     /// element.
     header;
 
-
     /// Build a
     /// [`<h1>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1)
     /// element.
@@ -122,11 +125,13 @@ builder_constructors! {
     /// - The `<h1>` heading is usually a large bolded font.
     ///
     /// # Usage
+    ///
     /// ```
     /// html!(<h1> A header element </h1>)
     /// rsx!(h1 { "A header element" })
     /// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
     /// ```
+    ///
     h1;
 
 

+ 4 - 4
packages/webview/src/lib.rs

@@ -10,15 +10,15 @@ mod dom;
 static HTML_CONTENT: &'static str = include_str!("./index.html");
 
 pub fn launch(
-    builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
     root: FC<()>,
+    builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
 ) -> anyhow::Result<()> {
-    launch_with_props(builder, (), root)
+    launch_with_props(root, (), builder)
 }
 pub fn launch_with_props<P: Properties + 'static>(
-    builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
-    props: P,
     root: FC<P>,
+    props: P,
+    builder: impl FnOnce(DioxusWebviewBuilder) -> DioxusWebviewBuilder,
 ) -> anyhow::Result<()> {
     WebviewRenderer::run(root, props, builder)
 }