diff --git a/examples/calculator.rs b/examples/calculator.rs index 4a7884ea3..3d9fab0a4 100644 --- a/examples/calculator.rs +++ b/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::*; diff --git a/examples/model.rs b/examples/model.rs index a5c59aa0d..798fe2ed6 100644 --- a/examples/model.rs +++ b/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); + 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::().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::().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, - cur_val: f64, +#[derive(Props, PartialEq)] +struct CalculatorDisplayProps<'a> { + val: &'a str, } -#[derive(Clone)] -enum Operator { - Add, - Sub, - Mul, - Div, -} - -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::().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::().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::().unwrap().separated_string(); + // TODO: make it autoscaling with css + cx.render(rsx! { + div { class: "calculator-display" + div { class: "auto-scaling-text", "{formatted}" } + } + }) } diff --git a/examples/reducer.rs b/examples/reducer.rs index 1408d58cc..65724e856 100644 --- a/examples/reducer.rs +++ b/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(); diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index b5ce98fcd..1198c1f04 100644 --- a/examples/rsx_usage.rs +++ b/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. diff --git a/examples/webview.rs b/examples/webview.rs index 535915cab..55e1d1b86 100644 --- a/examples/webview.rs +++ b/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| { diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index f5f63c3f5..72e239514 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/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::()?; *manual_props = Some(content.parse::()?); } 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::()?); } 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::()?); } diff --git a/packages/core-macro/src/rsx/element.rs b/packages/core-macro/src/rsx/element.rs index 10c2401d7..65ba6ce2f 100644 --- a/packages/core-macro/src/rsx/element.rs +++ b/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! { diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 2dc2d4ff4..b861a8c0e 100644 --- a/packages/core/src/hooks.rs +++ b/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 { - modifier: Rc>>>, current_val: T, - update: Box, - setter: Box, - // setter: Box, + callback: Rc, + wip: RefCell>, } -// #[derive(Clone, Copy)] -// pub struct UseStateHandle<'a, T: 'static> { -// inner: &'a UseState, -// // pub setter: &'a dyn Fn(T), -// // pub modifier: &'a dyn Fn(&mut T), -// } - -impl<'a, T: 'static> UseState { - pub fn setter(&self) -> &dyn Fn(T) { - &self.setter - // let r = self.setter.as_mut(); - // unsafe { Pin::get_unchecked_mut(r) } +impl UseState { + /// 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> { + self.wip.borrow() } } +impl<'a, T: 'static + ToOwned> UseState { + 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 { type Target = T; @@ -163,67 +171,23 @@ pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T, P>( ) -> &'a UseState { 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 - 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 { -// _current: RefCell, -// } -// impl UseRef { -// 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. diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 4a32c9ad7..fa551ffd8 100644 --- a/packages/core/src/nodebuilder.rs +++ b/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 -// where -// G: IntoVNode<'a>, -// { -// } - -// impl<'a, F> VNodeBuilder<'a, LazyNodes> for LazyNodes 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. diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index ee86e679a..9e890b119 100644 --- a/packages/html/src/lib.rs +++ b/packages/html/src/lib.rs @@ -62,18 +62,22 @@ builder_constructors! { /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) /// element. head; + /// Build a /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link) /// element. link; + /// Build a /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) /// element. meta; + /// Build a /// [`