...
 
Commits (8)
......@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### 0.3.1-alpha3
* NumericBox widget
* Update caret position on TextBox by mouse click
* Text input support for ', /, \, [, ], {, }
* Multiple window support
......
......@@ -1340,6 +1340,14 @@ name = "pathfinder_text"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"dces 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"orbtk-api 0.3.1-alpha3",
"orbtk-proc-macros 0.3.1-alpha3",
"orbtk-render 0.3.1-alpha3",
"orbtk-shell 0.3.1-alpha3",
"orbtk-theme 0.3.1-alpha3",
"orbtk-utils 0.3.1-alpha3",
"rust_decimal 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"font-kit 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pathfinder_content 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"pathfinder_geometry 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
......@@ -1703,6 +1711,15 @@ dependencies = [
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rust_decimal"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rustc_version"
version = "0.2.3"
......@@ -2546,6 +2563,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum ron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5"
"checksum rust-argon2 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017"
"checksum rust_decimal 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26b5f52edf35045e96b07aa29822bf4ce8495295fd5610270f85ab1f26df7ba5"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum rusttype 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "310942406a39981bed7e12b09182a221a29e0990f3e7e0c971f131922ed135d5"
"checksum rusttype 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f61411055101f7b60ecf1041d87fb74205fb20b0c7a723f07ef39174cf6b4c0"
......
......@@ -62,6 +62,11 @@ pub enum Key {
Dot,
QuestionMark,
ExclamationMark,
NumpadDivide,
NumpadMultiply,
NumpadSubtract,
NumpadAdd,
NumpadEnter,
Slash,
Backslash,
Quote,
......@@ -142,6 +147,10 @@ impl From<Key> for &'static str {
Key::Dot => ".",
Key::QuestionMark => "?",
Key::ExclamationMark => "!",
Key::NumpadDivide => "/",
Key::NumpadMultiply => "*",
Key::NumpadSubtract => "-",
Key::NumpadAdd => "+",
Key::Slash => "/",
Key::Quote => "'",
Key::Backslash => "\\",
......@@ -225,6 +234,10 @@ impl From<Key> for Option<u8> {
Key::Dot => Some(b'.'),
Key::QuestionMark => Some(b'?'),
Key::ExclamationMark => Some(b'!'),
Key::NumpadDivide => Some(b'/'),
Key::NumpadMultiply => Some(b'*'),
Key::NumpadSubtract => Some(b'-'),
Key::NumpadAdd => Some(b'+'),
Key::Slash => Some(b'/'),
Key::Quote => Some(b'\''),
Key::Backslash => Some(b'\\'),
......@@ -338,6 +351,10 @@ impl From<char> for Key {
'\u{f702}' => Key::Left,
'\u{f703}' => Key::Right,
'\u{8}' => Key::Backspace,
'/' => Key::NumpadDivide,
'*' => Key::NumpadMultiply,
'-' => Key::NumpadSubtract,
'+' => Key::NumpadAdd,
_ => Key::Unknown,
}
}
......
......@@ -251,4 +251,31 @@ progress_bar_indicator {
border-radius: 1;
height: 24;
width: 0;
}
numeric_box {
background: transparent;
border-color: #647b91;
border-radius: 3;
border-width: 1;
height: 32;
}
numeric_box_input {
border-color: #647b91;
border-radius: 3;
border-width: 1;
max-width: 64;
}
numeric_box_button {
border-color: transparent;
border-width: 0;
border-radius: 0;
font-size: 13;
max-height: 14;
padding-left: 0;
padding-right: 0;
padding-top: 0;
padding-bottom: 0;
}
\ No newline at end of file
......@@ -17,4 +17,5 @@ orbtk-theme = { path = "../theme", version = "0.3.1-alpha3" }
orbtk-render = { path = "../render", version = "0.3.1-alpha3" }
orbtk-utils = { path = "../utils", version = "0.3.1-alpha3" }
orbtk-proc-macros = { version = "0.3.1-alpha3", path = "../proc-macros" }
lazy_static = "1.4"
\ No newline at end of file
rust_decimal = "1.5.0"
lazy_static = "1.4"
......@@ -24,6 +24,7 @@ pub use self::grid::*;
pub use self::image_widget::*;
pub use self::items_widget::*;
pub use self::list_view::*;
pub use self::numeric_box::*;
pub use self::popup::*;
pub use self::progress_bar::*;
pub use self::scroll_bar::*;
......@@ -49,6 +50,7 @@ mod grid;
mod image_widget;
mod items_widget;
mod list_view;
mod numeric_box;
mod popup;
mod progress_bar;
mod scroll_bar;
......
use super::behaviors::MouseBehavior;
use crate::prelude::*;
use crate::shell::{Key, KeyEvent};
use core::f64::MAX;
use rust_decimal::prelude::*;
pub static ID_INPUT: &'static str = "numeric_box_input";
pub static ELEMENT_INPUT: &'static str = "numeric_box_input";
pub static ELEMENT_BTN: &'static str = "numeric_box_button";
// one mouse up scroll is delta.y = 12.0
static ONE_SCROLL: f64 = 12.0;
enum InputAction {
Inc,
Dec,
ChangeByKey(KeyEvent),
ChangeByMouseScroll(Point),
Focus,
}
#[derive(Default, AsAny)]
struct NumericBoxState {
action: Option<InputAction>,
pub input: Entity,
min: Decimal,
max: Decimal,
step: Decimal,
current_value: Decimal,
}
impl NumericBoxState {
fn action(&mut self, action: InputAction) {
self.action = Some(action);
}
fn change_val(&mut self, new_value: Decimal, ctx: &mut Context<'_>) {
if new_value >= self.min && new_value <= self.max {
self.current_value = new_value;
ctx.get_widget(self.input)
.set::<String16>("text", String16::from(self.current_value.to_string()));
}
}
fn request_focus(&self, ctx: &mut Context<'_>) {
if !ctx.widget().get::<bool>("focused") {
ctx.widget().set::<bool>("focused", true);
ctx.push_event_by_window(FocusEvent::RequestFocus(ctx.entity));
}
}
}
fn default_or(key: &str, default_value: f64, ctx: &mut Context<'_>) -> Decimal {
let property = ctx.widget().clone_or_default(key);
match Decimal::from_f64(property) {
Some(val) => val,
None => Decimal::from_f64(default_value).unwrap(),
}
}
impl State for NumericBoxState {
fn init(&mut self, _: &mut Registry, ctx: &mut Context<'_>) {
self.input = ctx.entity_of_child(ID_INPUT).expect(
"NumericBoxState
.init(): the child input could not be found!",
);
self.min = default_or("min", 0.0, ctx);
self.max = default_or("max", MAX, ctx);
self.step = default_or("step", 1.0, ctx);
self.current_value = default_or("val", 0.0, ctx);
let init_value = String16::from(self.current_value.to_string());
ctx.get_widget(self.input)
.set::<String16>("text", init_value);
}
// TODO: let the user type the value, or select it for cut, copy, paste operations
fn update(&mut self, _: &mut Registry, ctx: &mut Context<'_>) {
if let Some(action) = &self.action {
match action {
InputAction::Inc => {
self.change_val(self.current_value + self.step, ctx);
}
InputAction::Dec => {
self.change_val(self.current_value - self.step, ctx);
}
InputAction::ChangeByKey(key_event) => match key_event.key {
Key::Up | Key::NumpadAdd => {
self.change_val(self.current_value + self.step, ctx);
}
Key::Down | Key::NumpadSubtract => {
self.change_val(self.current_value - self.step, ctx);
}
_ => {}
},
InputAction::ChangeByMouseScroll(delta) => {
match Decimal::from_f64(delta.y / ONE_SCROLL) {
Some(scroll_count) => {
self.change_val(self.current_value + (self.step * scroll_count), ctx);
}
None => {}
}
}
InputAction::Focus => {
self.request_focus(ctx);
}
}
self.action = None;
}
}
}
widget!(
/// `NumericBox` is used to let the user increase or decrease
/// the value of the input by a given, fixed value called `step` until it reaches the upper or
/// lower bounds.
/// The widget can be controlled by clicking on the two control buttons, or the keybaord's
/// Up and Down, Numpad+ and Numpad- keys, or the mouse scroll.
/// Note: after the widget is initialized, changing the min, max or step properties has no effect.
///
/// # Examples:
/// Create a NumericBox with default values:
/// ```rust
/// NumericBox::create().build(ctx)
/// ```
///
/// Create a NumericBox with custom values:
/// ```rust
/// NumericBox::create().min(10.0).max(100.0).val(50.0).step(5.0).build(ctx)
/// ```
NumericBox<NumericBoxState>: KeyDownHandler {
/// Sets or shares the background color property
background: Brush,
/// Sets or shares the border color property
border_brush: Brush,
/// Sets or shares the border width property
border_width: Thickness,
/// Sets or shares the border radius property
border_radius: f64,
/// Sets or shares the focused property
focused: bool,
/// Sets or shares the foreground color property
foreground: Brush,
/// Sets or shares the minimum allowed value property
min: f64,
/// Sets or shares the maximum allowed value property
max: f64,
/// Sets or shares the stepping value property
step: f64,
/// Sets or shares the current value property
val: f64
}
);
impl Template for NumericBox {
fn template(self, id: Entity, ctx: &mut BuildContext) -> Self {
self.name("NumericBox")
.background("transparent")
.foreground(colors::LINK_WATER_COLOR)
.border_brush("#647b91")
.border_width(1.0)
.border_radius(3.0)
.element("numeric_box")
.focused(false)
.height(32.0)
.margin(4.0)
.min(0.0)
.max(MAX)
.step(1.0)
.val(0.0)
.width(100.0)
.child(
MouseBehavior::create()
.on_mouse_down(move |states, _| {
states
.get_mut::<NumericBoxState>(id)
.action(InputAction::Focus);
true
})
.on_scroll(move |states, delta| {
states
.get_mut::<NumericBoxState>(id)
.action(InputAction::ChangeByMouseScroll(delta));
true
})
.build(ctx),
)
.child(
Stack::create()
.orientation("horizontal")
.spacing(0.0)
.child(
TextBox::create()
.border_brush(id)
.border_radius(id)
.border_width(id)
.element(ELEMENT_INPUT)
.enabled(false)
.id(ID_INPUT)
.max_width(64.0)
.text("0")
.build(ctx),
)
.child(
Stack::create()
.orientation("vertical")
.spacing(0.0)
.child(
Button::create()
.border_brush("transparent")
.border_radius(0.0)
.border_width(0.0)
.class("single_content")
.element(ELEMENT_BTN)
.max_width(32.0)
.margin(1.0)
.padding(0.0)
.text("+")
.on_click(move |states, _| {
states
.get_mut::<NumericBoxState>(id)
.action(InputAction::Inc);
true
})
.build(ctx),
)
.child(
Button::create()
.border_brush("transparent")
.border_radius(0.0)
.border_width(0.0)
.class("single_content")
.element(ELEMENT_BTN)
.max_width(32.0)
.margin(1.0)
.padding(0.0)
.text("-")
.on_click(move |states, _| {
states
.get_mut::<NumericBoxState>(id)
.action(InputAction::Dec);
true
})
.build(ctx),
)
.build(ctx),
)
.build(ctx),
)
.on_key_down(move |states, event| -> bool {
states
.get_mut::<NumericBoxState>(id)
.action(InputAction::ChangeByKey(event));
false
})
}
fn render_object(&self) -> Box<dyn RenderObject> {
Box::new(RectangleRenderObject)
}
}
use orbtk::prelude::*;
widget!(MainView);
impl Template for MainView {
fn template(self, _id: Entity, ctx: &mut BuildContext) -> Self {
self.child(
Stack::create()
.spacing(8.0)
.orientation("vertical")
.horizontal_alignment("center")
.child(
TextBlock::create()
.text("Tyre pressure")
.font_size(20.0)
.build(ctx),
)
.child(
NumericBox::create()
.max(123.0)
.step(0.123)
.val(0.123)
.build(ctx),
)
.child(Button::create().text("Blow air").build(ctx))
.build(ctx),
)
}
}
fn main() {
Application::new()
.window(|ctx| {
Window::create()
.title("OrbTk - NumericBox example")
.position((100.0, 100.0))
.size(420.0, 730.0)
.resizeable(true)
.child(MainView::create().build(ctx))
.build(ctx)
})
.run();
}