Skip to content

Commit bad0ace

Browse files
authored
Merge pull request #11 from hangleang/number-inputs
Add ``NumberInput`` widget
2 parents 96aa773 + bb25cb5 commit bad0ace

File tree

10 files changed

+986
-1
lines changed

10 files changed

+986
-1
lines changed

Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ modal = []
2626
tab_bar = []
2727
tabs = ["tab_bar"]
2828
time_picker = ["chrono", "icon_text", "iced_graphics/canvas"]
29+
number_input = ["num-traits"]
2930

3031
default = [
3132
"badge",
@@ -42,6 +43,7 @@ default = [
4243

4344
[dependencies]
4445
iced_style = "0.3"
46+
num-traits = { version = "0.2.14", optional = true }
4547

4648
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
4749
iced_native = "0.4"
@@ -69,5 +71,6 @@ members = [
6971
#"examples/tabs",
7072
#"examples/tabs_min",
7173
"examples/time_picker",
72-
"examples/web"
74+
"examples/web",
75+
"examples/number_input",
7376
]

examples/number_input/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "number_input"
3+
version = "0.1.0"
4+
authors = ["leang27 <[email protected]>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
iced = "0.3"
11+
iced_aw = { path = "../..", default-features = false, features = ["number_input"] }

examples/number_input/src/main.rs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
use iced::{window, Align, Container, Element, Length, Row, Sandbox, Settings, Text};
2+
use iced_aw::number_input::{self, NumberInput};
3+
4+
#[derive(Default)]
5+
pub struct NumberInputDemo {
6+
state: number_input::State,
7+
value: f32,
8+
}
9+
10+
#[derive(Debug, Clone)]
11+
pub enum Message {
12+
NumInpChanged(f32),
13+
}
14+
15+
fn main() -> iced::Result {
16+
NumberInputDemo::run(Settings {
17+
default_text_size: 14,
18+
window: window::Settings {
19+
size: (250, 200),
20+
..Default::default()
21+
},
22+
..Settings::default()
23+
})
24+
}
25+
26+
impl Sandbox for NumberInputDemo {
27+
type Message = Message;
28+
29+
fn new() -> Self {
30+
Self {
31+
value: 27.0,
32+
..Self::default()
33+
}
34+
}
35+
36+
fn title(&self) -> String {
37+
String::from("Number Input Demo")
38+
}
39+
40+
fn update(&mut self, message: Message) {
41+
match message {
42+
Message::NumInpChanged(val) => {
43+
self.value = val;
44+
}
45+
}
46+
}
47+
48+
fn view(&mut self) -> Element<Message> {
49+
let lb_minute = Text::new("Number Input:");
50+
let txt_minute =
51+
NumberInput::new(&mut self.state, self.value, 255.0, Message::NumInpChanged)
52+
.step(0.5)
53+
.min(1.0)
54+
.input_style(style::CustomTextInput)
55+
.style(style::CustomNumInput);
56+
57+
Container::new(
58+
Row::new()
59+
.spacing(10)
60+
.align_items(Align::Center)
61+
.push(lb_minute)
62+
.push(txt_minute),
63+
)
64+
.width(Length::Fill)
65+
.height(Length::Fill)
66+
.center_x()
67+
.center_y()
68+
.into()
69+
}
70+
}
71+
72+
mod style {
73+
use iced::{text_input, Color};
74+
use iced_aw::number_input;
75+
76+
const BACKGROUND: Color = Color::from_rgb(238.0 / 255.0, 238.0 / 255.0, 238.0 / 255.0);
77+
const FOREGROUND: Color = Color::from_rgb(224.0 / 255.0, 224.0 / 255.0, 224.0 / 255.0);
78+
const HOVERED: Color = Color::from_rgb(129.0 / 255.0, 129.0 / 255.0, 129.0 / 255.0);
79+
const PRIMARY: Color = Color::from_rgb(12.0 / 255.0, 46.0 / 251.0, 179.0 / 255.0);
80+
81+
pub struct CustomNumInput;
82+
impl number_input::StyleSheet for CustomNumInput {
83+
fn active(&self) -> number_input::Style {
84+
number_input::Style {
85+
icon_color: PRIMARY,
86+
..number_input::Style::default()
87+
}
88+
}
89+
}
90+
91+
pub struct CustomTextInput;
92+
impl text_input::StyleSheet for CustomTextInput {
93+
fn active(&self) -> text_input::Style {
94+
text_input::Style {
95+
background: BACKGROUND.into(),
96+
border_color: PRIMARY,
97+
border_width: 1.0,
98+
border_radius: 5.5,
99+
..text_input::Style::default()
100+
}
101+
}
102+
103+
fn focused(&self) -> text_input::Style {
104+
let active = self.active();
105+
106+
text_input::Style {
107+
background: FOREGROUND.into(),
108+
..active
109+
}
110+
}
111+
112+
fn placeholder_color(&self) -> Color {
113+
HOVERED
114+
}
115+
116+
fn selection_color(&self) -> Color {
117+
HOVERED
118+
}
119+
120+
fn value_color(&self) -> Color {
121+
Color::BLACK
122+
}
123+
}
124+
}

src/graphics/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,8 @@ pub mod time_picker;
5353
#[doc(no_inline)]
5454
#[cfg(feature = "time_picker")]
5555
pub use time_picker::TimePicker;
56+
57+
#[cfg(feature = "number_input")]
58+
pub mod number_input;
59+
#[cfg(feature = "number_input")]
60+
pub use number_input::NumberInput;

src/graphics/number_input.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//! Display fields that can only be filled with numeric type.
2+
//!
3+
//! A [`NumberInput`] has some local [`State`].
4+
use crate::native::number_input::{self, ModifierState};
5+
use iced_graphics::backend::{self, Backend};
6+
use iced_graphics::{Primitive, Renderer};
7+
use iced_native::mouse;
8+
use iced_native::{Background, Color, HorizontalAlignment, Point, Rectangle, VerticalAlignment};
9+
10+
pub use crate::native::number_input::State;
11+
pub use crate::style::number_input::{Style, StyleSheet};
12+
13+
/// A field that can only be filled with numeric type.
14+
///
15+
/// This is an alias of an `iced_native` number input with an `iced_wgpu::Renderer`.
16+
pub type NumberInput<'a, T, Message, Backend> =
17+
number_input::NumberInput<'a, T, Message, Renderer<Backend>>;
18+
19+
impl<B> number_input::Renderer for Renderer<B>
20+
where
21+
B: Backend + backend::Text,
22+
{
23+
type Style = Box<dyn StyleSheet>;
24+
25+
const DEFAULT_PADDING: u16 = 5;
26+
27+
fn draw(
28+
&mut self,
29+
cursor_position: Point,
30+
state: &ModifierState,
31+
inc_bounds: Rectangle,
32+
dec_bounds: Rectangle,
33+
is_mouse_over: bool,
34+
is_decrease_disabled: bool,
35+
is_increase_disabled: bool,
36+
(content, _): Self::Output,
37+
style: &<Self as number_input::Renderer>::Style,
38+
font: Self::Font,
39+
) -> Self::Output {
40+
let mouse_over_decrease = dec_bounds.contains(cursor_position);
41+
let mouse_over_increase = inc_bounds.contains(cursor_position);
42+
43+
let decrease_btn_style = if is_decrease_disabled {
44+
style.disabled()
45+
} else if state.decrease_pressed {
46+
style.pressed()
47+
} else {
48+
style.active()
49+
};
50+
51+
let increase_btn_style = if is_increase_disabled {
52+
style.disabled()
53+
} else if state.increase_pressed {
54+
style.pressed()
55+
} else {
56+
style.active()
57+
};
58+
59+
// decrease button section
60+
let decrease_button_rect = Primitive::Quad {
61+
bounds: dec_bounds,
62+
background: decrease_btn_style
63+
.button_background
64+
.unwrap_or(Background::Color(Color::TRANSPARENT)),
65+
border_radius: 3.0,
66+
border_width: 0.,
67+
border_color: Color::TRANSPARENT,
68+
};
69+
let decrease_text = Primitive::Text {
70+
content: String::from("\u{25bc}"),
71+
bounds: Rectangle {
72+
x: dec_bounds.center_x(),
73+
y: dec_bounds.center_y(),
74+
..dec_bounds
75+
},
76+
font,
77+
size: dec_bounds.height * 0.9,
78+
color: decrease_btn_style.icon_color,
79+
horizontal_alignment: HorizontalAlignment::Center,
80+
vertical_alignment: VerticalAlignment::Center,
81+
};
82+
let decrease_btn = Primitive::Group {
83+
primitives: vec![decrease_button_rect, decrease_text],
84+
};
85+
86+
// increase button section
87+
let increase_button_rect = Primitive::Quad {
88+
bounds: inc_bounds,
89+
background: increase_btn_style
90+
.button_background
91+
.unwrap_or(Background::Color(Color::TRANSPARENT)),
92+
border_radius: 3.0,
93+
border_width: 0.,
94+
border_color: Color::TRANSPARENT,
95+
};
96+
let increase_text = Primitive::Text {
97+
content: String::from("\u{25b2}"),
98+
bounds: Rectangle {
99+
x: inc_bounds.center_x(),
100+
y: inc_bounds.center_y(),
101+
..inc_bounds
102+
},
103+
font,
104+
size: inc_bounds.height * 0.9,
105+
color: increase_btn_style.icon_color,
106+
horizontal_alignment: HorizontalAlignment::Center,
107+
vertical_alignment: VerticalAlignment::Center,
108+
};
109+
let increase_btn = Primitive::Group {
110+
primitives: vec![increase_button_rect, increase_text],
111+
};
112+
113+
(
114+
Primitive::Group {
115+
primitives: vec![content, decrease_btn, increase_btn],
116+
},
117+
if (mouse_over_decrease && !is_decrease_disabled)
118+
|| (mouse_over_increase && !is_increase_disabled)
119+
{
120+
mouse::Interaction::Pointer
121+
} else if is_mouse_over {
122+
mouse::Interaction::Text
123+
} else {
124+
mouse::Interaction::default()
125+
},
126+
)
127+
}
128+
}

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ mod platform {
9898
#[doc(no_inline)]
9999
#[cfg(feature = "time_picker")]
100100
pub use {crate::graphics::time_picker, time_picker::TimePicker};
101+
102+
#[doc(no_inline)]
103+
#[cfg(feature = "number_input")]
104+
pub use {crate::graphics::number_input, number_input::NumberInput};
101105
}
102106
#[cfg(target_arch = "wasm32")]
103107
pub mod web;

src/native/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,8 @@ pub use tabs::Tabs;
5151
pub mod time_picker;
5252
#[cfg(feature = "time_picker")]
5353
pub use time_picker::TimePicker;
54+
55+
#[cfg(feature = "number_input")]
56+
pub mod number_input;
57+
#[cfg(feature = "number_input")]
58+
pub use number_input::NumberInput;

0 commit comments

Comments
 (0)