diff --git a/examples/text_box.rs b/examples/text_box.rs index 5a51d0217d585c4d016e8281949d5926673e0f1b..78d1c767c7fbc7f5aa8e6bafee8de2c66319eb8b 100644 --- a/examples/text_box.rs +++ b/examples/text_box.rs @@ -11,7 +11,7 @@ use kayak_ui::core::{ styles::{Style, StyleProp, Units}, widget, Index, }; -use kayak_ui::widgets::{App, Inspector, OnChange, SpinBox, TextBox, Window}; +use kayak_ui::widgets::{App, Inspector, OnChange, SpinBox, SpinBoxStyle, TextBox, Window}; #[widget] fn TextBoxExample() { @@ -46,6 +46,8 @@ fn TextBoxExample() { set_spin_value(event.value); }); + let vert = SpinBoxStyle::Vertical; + rsx! { <Window position={(50.0, 50.0)} size={(500.0, 300.0)} title={"TextBox Example".to_string()}> <TextBox styles={Some(input_styles)} value={value} on_change={Some(on_change)} /> @@ -56,7 +58,21 @@ fn TextBoxExample() { placeholder={Some("This is a placeholder".to_string())} /> <TextBox styles={Some(red_text_styles)} value={red_value} on_change={Some(on_change_red)} /> - <SpinBox styles={Some(input_styles)} value={spin_value} on_change={Some(on_change_spin)} /> + <SpinBox + styles={Some(input_styles)} + value={spin_value} + on_change={Some(on_change_spin)} + min_val={0.0} + max_val={10.0} + /> + <SpinBox + spin_button_style={vert} + styles={Some(input_styles)} + value={spin_value} + on_change={Some(on_change_spin)} + min_val={0.0} + max_val={10.0} + /> </Window> } } @@ -74,7 +90,6 @@ fn startup( render! { <App> <TextBoxExample /> - <Inspector /> </App> } }); diff --git a/src/widgets/spin_box.rs b/src/widgets/spin_box.rs index fa160c1016fb440fb9b7305e4144e302375f6f84..1e8317bb24180d22e96091e7e7425acff624f02c 100644 --- a/src/widgets/spin_box.rs +++ b/src/widgets/spin_box.rs @@ -1,3 +1,5 @@ +use std::{fmt::Debug, fmt::Formatter, sync::Arc}; + use crate::{ core::{ render_command::RenderCommand, @@ -11,9 +13,22 @@ use kayak_core::{ styles::{LayoutType, StyleProp}, CursorIcon, OnLayout, }; +use kayak_render_macros::use_state; use crate::widgets::{Background, Clip, OnChange, Text}; +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum SpinBoxStyle { + Horizontal, + Vertical, +} + +impl Default for SpinBoxStyle { + fn default() -> Self { + SpinBoxStyle::Horizontal + } +} + #[derive(Debug, PartialEq, Clone)] pub struct SpinBoxProps { /// If true, prevents the widget from being focused (and consequently edited) @@ -22,6 +37,8 @@ pub struct SpinBoxProps { pub on_change: Option<OnChange>, /// The text to display when the user input is empty pub placeholder: Option<String>, + /// Whether spinbox is horizontally or vertically aligned. + pub spin_button_style: SpinBoxStyle, /// The user input /// /// This is a controlled state. You _must_ set this to the value to you wish to be displayed. @@ -32,25 +49,55 @@ pub struct SpinBoxProps { pub incr_str: String, /// Text on decrement button defaults to `<` pub decr_str: String, - pub children: Option<Children>, + /// Events on increment button press + pub on_incr_event: Option<OnEvent>, + /// Events on decrement button press + pub on_decr_event: Option<OnEvent>, + /// Events for text edit pub on_event: Option<OnEvent>, + /// Minimal value + pub min_val: f32, + /// Maximal value + pub max_val: f32, + pub children: Option<Children>, pub on_layout: Option<OnLayout>, pub focusable: Option<bool>, } +impl SpinBoxProps { + pub fn get_float(&self) -> f32 { + self.value.parse::<f32>().unwrap_or_default() + } + + pub fn get_int(&self) -> i16 { + let temp_float = self.get_float(); + if temp_float > f32::from(i16::MAX) { + i16::MAX + } else if temp_float < f32::from(i16::MIN) { + i16::MIN + } else { + temp_float.round() as i16 + } + } +} impl Default for SpinBoxProps { fn default() -> SpinBoxProps { - SpinBoxProps { - incr_str: ">".into(), - decr_str: "<".into(), + SpinBoxProps { + incr_str: "+".into(), + decr_str: "-".into(), disabled: Default::default(), - on_change: Default::default(), + on_change: Default::default(), placeholder: Default::default(), value: Default::default(), styles: Default::default(), + spin_button_style: Default::default(), children: Default::default(), + on_incr_event: Default::default(), + on_decr_event: Default::default(), on_event: Default::default(), + min_val: f32::MIN, + max_val: f32::MAX, on_layout: Default::default(), focusable: Default::default(), } @@ -106,6 +153,9 @@ pub fn SpinBox(props: SpinBoxProps) { on_change, placeholder, value, + max_val, + min_val, + spin_button_style, .. } = props.clone(); @@ -180,11 +230,19 @@ pub fn SpinBox(props: SpinBoxProps) { } }; - let button_style = Some(Style { - height: Units::Pixels(24.0).into(), - width: Units::Pixels(24.0).into(), - ..Default::default() - }); + let button_style = match spin_button_style { + SpinBoxStyle::Horizontal => Some(Style { + height: Units::Pixels(24.0).into(), + width: Units::Pixels(24.0).into(), + ..Default::default() + }), + SpinBoxStyle::Vertical => Some(Style { + height: Units::Pixels(12.0).into(), + width: Units::Pixels(24.0).into(), + + ..Default::default() + }), + }; let value = if value.is_empty() { placeholder.unwrap_or_else(|| value.clone()) @@ -192,30 +250,97 @@ pub fn SpinBox(props: SpinBoxProps) { value }; - let inline_style = Style { + let row = Style { layout_type: StyleProp::Value(LayoutType::Row), ..Style::default() }; + let col = Style { + layout_type: StyleProp::Value(LayoutType::Column), + height: Units::Stretch(100.0).into(), + width: Units::Pixels(26.0).into(), + ..Style::default() + }; + let incr_str = props.clone().incr_str; let decr_str = props.clone().decr_str; - rsx! { - <Background styles={Some(background_styles)}> - <Clip styles={Some(inline_style)}> - <Button styles={button_style}> - <Text content={decr_str} /> - </Button> - <Text - content={value} - size={14.0} - styles={Some(text_styles)} - /> - <Button styles={button_style}> - <Text content={incr_str} /> - </Button> - </Clip> - </Background> + let (spin_value, set_val, _) = use_state!(value); + let x = spin_value.parse::<f32>().unwrap_or_default(); + let decr_fn = set_val.clone(); + let incr_fn = set_val.clone(); + + let incr_event = if let Some(event) = props.clone().on_incr_event { + event + } else { + OnEvent::new(move |_, event| match event.event_type { + EventType::Click(_) => { + if x >= max_val { + return; + } + incr_fn((x + 1.0f32).to_string()); + } + _ => {} + }) + }; + + let decr_event = if let Some(event) = props.clone().on_decr_event { + event + } else { + OnEvent::new(move |_, event| match event.event_type { + EventType::Click(_) => { + if x <= min_val { + return; + } + decr_fn((x - 1.0f32).to_string()); + } + _ => {} + }) + }; + + match spin_button_style { + SpinBoxStyle::Horizontal => { + rsx! { + <Background styles={Some(background_styles)}> + <Clip styles={Some(row)}> + <Button styles={button_style} on_event={Some(decr_event)}> + <Text content={decr_str} /> + </Button> + <Text + content={spin_value} + size={14.0} + styles={Some(text_styles)} + /> + <Button styles={button_style} on_event={Some(incr_event)}> + <Text content={incr_str} /> + </Button> + </Clip> + </Background> + } + } + SpinBoxStyle::Vertical => { + rsx! { + <Background styles={Some(background_styles)}> + + <Clip styles={Some(row)}> + <Text + content={spin_value} + size={14.0} + styles={Some(text_styles)} + /> + <Clip styles={Some(col)}> + + <Button styles={button_style} on_event={Some(incr_event)}> + <Text content={incr_str} size={11.0} /> + </Button> + <Button styles={button_style} on_event={Some(decr_event)}> + <Text content={decr_str} size={11.0}/> + </Button> + </Clip> + </Clip> + </Background> + } + } } }