Skip to content

Commit e8b325c

Browse files
committed
Creating typed_input (a text input but sending message only when it is valid for the given type) Adding suport for dots in number input
2 parents 38df62a + 0ab9259 commit e8b325c

File tree

8 files changed

+454
-45
lines changed

8 files changed

+454
-45
lines changed

Cargo.lock

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ tab_bar = []
2727
tabs = ["tab_bar"]
2828
time_picker = ["chrono", "icons", "iced/canvas"]
2929
wrap = []
30-
number_input = ["num-format", "num-traits"]
30+
number_input = ["num-format", "num-traits", "typed_input"]
31+
typed_input = []
3132
selection_list = []
3233
menu = []
3334
quad = []
@@ -82,6 +83,7 @@ members = [
8283
"examples/badge",
8384
"examples/card",
8485
"examples/number_input",
86+
"examples/typed_input",
8587
"examples/date_picker",
8688
"examples/color_picker",
8789
"examples/grid",

examples/number_input/src/main.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,13 @@ fn main() -> iced::Result {
3131
impl NumberInputDemo {
3232
fn update(&mut self, message: self::Message) {
3333
let Message::NumInpChanged(val) = message;
34+
println!("Value changed to {:?}", val);
3435
self.value = val;
3536
}
3637

3738
fn view(&self) -> Element<Message> {
3839
let lb_minute = Text::new("Number Input:");
39-
let txt_minute = number_input(self.value, 0.0..250.0, Message::NumInpChanged)
40+
let txt_minute = number_input(self.value, -10.0..250.0, Message::NumInpChanged)
4041
.style(number_input::number_input::primary)
4142
.step(0.5);
4243

examples/typed_input/Cargo.toml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[package]
2+
name = "typed_input"
3+
version = "0.1.0"
4+
authors = ["Ultraxime <[email protected]>"]
5+
edition = "2021"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
[dependencies]
10+
iced_aw = { workspace = true, features = [
11+
"typed_input",
12+
"icons",
13+
] }
14+
15+
iced.workspace=true

examples/typed_input/src/main.rs

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use iced::{
2+
widget::{Container, Row, Text},
3+
Alignment, Element, Length,
4+
};
5+
use iced_aw::widgets::typed_input;
6+
7+
#[derive(Default, Debug)]
8+
pub struct TypedInputDemo {
9+
value: f32,
10+
}
11+
12+
#[derive(Debug, Clone)]
13+
pub enum Message {
14+
TypedInpChanged(f32),
15+
}
16+
17+
fn main() -> iced::Result {
18+
iced::application(
19+
"Typed Input example",
20+
TypedInputDemo::update,
21+
TypedInputDemo::view,
22+
)
23+
.window_size(iced::Size {
24+
width: 250.0,
25+
height: 200.0,
26+
})
27+
.font(iced_aw::BOOTSTRAP_FONT_BYTES)
28+
.run()
29+
}
30+
31+
impl TypedInputDemo {
32+
fn update(&mut self, message: self::Message) {
33+
let Message::TypedInpChanged(val) = message;
34+
println!("Value changed to {:?}", val);
35+
self.value = val;
36+
}
37+
38+
fn view(&self) -> Element<Message> {
39+
let lb_minute = Text::new("Typed Input:");
40+
let txt_minute = typed_input::TypedInput::new(self.value, Message::TypedInpChanged);
41+
42+
Container::new(
43+
Row::new()
44+
.spacing(10)
45+
.align_items(Alignment::Center)
46+
.push(lb_minute)
47+
.push(txt_minute),
48+
)
49+
.width(Length::Fill)
50+
.height(Length::Fill)
51+
.center_x(Length::Fill)
52+
.center_y(Length::Fill)
53+
.into()
54+
}
55+
}

src/widgets.rs

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ pub mod number_input;
2323
pub type NumberInput<'a, T, Message, Theme, Renderer> =
2424
number_input::NumberInput<'a, T, Message, Theme, Renderer>;
2525

26+
#[cfg(feature = "typed_input")]
27+
pub mod typed_input;
28+
2629
#[cfg(feature = "card")]
2730
pub mod card;
2831
#[cfg(feature = "card")]

src/widgets/number_input.rs

+55-43
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use iced::{
1717
widget::{
1818
text::LineHeight,
1919
text_input::{self, cursor, Value},
20-
Column, Container, Row, Text, TextInput,
20+
Column, Container, Row, Text,
2121
},
2222
Alignment, Background, Border, Color, Element, Event, Length, Padding, Pixels, Point,
2323
Rectangle, Shadow, Size,
@@ -37,6 +37,7 @@ pub use crate::{
3737
StyleFn,
3838
},
3939
};
40+
use crate::widgets::typed_input::TypedInput;
4041

4142
/// The default padding
4243
const DEFAULT_PADDING: f32 = 5.0;
@@ -81,7 +82,7 @@ where
8182
/// The text size of the [`NumberInput`].
8283
size: Option<f32>,
8384
/// The underlying element of the [`NumberInput`].
84-
content: TextInput<'a, Message, Theme, Renderer>,
85+
content: TypedInput<'a, T, Message, Theme, Renderer>,
8586
/// The ``on_change`` event of the [`NumberInput`].
8687
on_change: Box<dyn Fn(T) -> Message>,
8788
/// The style of the [`NumberInput`].
@@ -116,9 +117,6 @@ where
116117
T: 'static,
117118
{
118119
let padding = DEFAULT_PADDING;
119-
let convert_to_num = move |s: String| {
120-
on_changed(T::from_str(&s).unwrap_or(if s.is_empty() { T::zero() } else { value }))
121-
};
122120

123121
Self {
124122
value,
@@ -127,8 +125,7 @@ where
127125
max: Self::set_max(bounds.end_bound()),
128126
padding,
129127
size: None,
130-
content: TextInput::new("", format!("{value}").as_str())
131-
.on_input(convert_to_num)
128+
content: TypedInput::new(value, on_changed)
132129
.padding(padding)
133130
.width(Length::Fixed(127.0))
134131
.class(Theme::default_input()),
@@ -346,7 +343,7 @@ where
346343
.shrink(padding);
347344
let content = self
348345
.content
349-
.layout(&mut tree.children[0], renderer, &limits, None);
346+
.layout(&mut tree.children[0], renderer, &limits);
350347
let limits2 = Limits::new(Size::new(0.0, 0.0), content.size());
351348
let txt_size = self.size.unwrap_or_else(|| renderer.default_size().0);
352349

@@ -456,10 +453,15 @@ where
456453

457454
let child = state.children.get_mut(0).expect("fail to get child");
458455
let text_input = child
456+
.children
457+
.get_mut(0)
458+
.expect("fail to get text input")
459459
.state
460460
.downcast_mut::<text_input::State<Renderer::Paragraph>>();
461461
let modifiers = state.state.downcast_mut::<ModifierState>();
462462

463+
let current_text = self.content.text().to_string();
464+
463465
let mut forward_to_text = |event, shell, child, clipboard| {
464466
self.content.on_event(
465467
child, event, content, cursor, renderer, clipboard, shell, viewport,
@@ -485,39 +487,43 @@ where
485487
forward_to_text(event, shell, child, clipboard)
486488
} else if text == "\u{8}" {
487489
// Backspace
488-
if T::zero().eq(&self.value) {
489-
event::Status::Ignored
490-
} else {
491-
let mut new_val = self.value.to_string();
492-
match text_input.cursor().state(&Value::new(&new_val)) {
493-
cursor::State::Index(idx)
494-
if idx >= 1 && idx <= new_val.len() =>
495-
{
496-
_ = new_val.remove(idx - 1);
497-
}
498-
cursor::State::Selection { start, end }
499-
if start <= new_val.len() && end <= new_val.len() =>
500-
{
501-
new_val.replace_range(start.min(end)..start.max(end), "");
502-
}
503-
_ => return event::Status::Ignored,
490+
if current_text == T::zero().to_string() {
491+
return event::Status::Ignored;
492+
}
493+
let mut new_val = current_text;
494+
match text_input.cursor().state(&Value::new(&new_val)) {
495+
cursor::State::Index(idx)
496+
if idx >= 1 && idx <= new_val.len() =>
497+
{
498+
_ = new_val.remove(idx - 1);
504499
}
505-
506-
if new_val.is_empty() {
507-
new_val = T::zero().to_string();
500+
cursor::State::Selection { start, end }
501+
if start <= new_val.len() && end <= new_val.len() =>
502+
{
503+
new_val.replace_range(start.min(end)..start.max(end), "");
508504
}
505+
_ => return event::Status::Ignored,
506+
}
509507

510-
match T::from_str(&new_val) {
511-
Ok(val)
512-
if (self.min..self.max).contains(&val)
513-
&& val != self.value =>
514-
{
515-
self.value = val;
516-
forward_to_text(event, shell, child, clipboard)
517-
}
518-
Ok(_) => event::Status::Captured,
519-
_ => event::Status::Ignored,
508+
if new_val.is_empty() {
509+
new_val = T::zero().to_string();
510+
}
511+
512+
match T::from_str(&new_val) {
513+
Ok(val)
514+
if (self.min..self.max).contains(&val)
515+
&& val != self.value =>
516+
{
517+
self.value = val;
518+
forward_to_text(event, shell, child, clipboard)
520519
}
520+
Ok(val)
521+
if (self.min..self.max).contains(&val) =>
522+
{
523+
forward_to_text(event, shell, child, clipboard)
524+
}
525+
Ok(_) => event::Status::Captured,
526+
_ => event::Status::Ignored,
521527
}
522528
} else {
523529
let input = if text == "\u{16}" {
@@ -526,15 +532,15 @@ where
526532
Some(paste) => paste,
527533
None => return event::Status::Ignored,
528534
}
529-
} else if text.parse::<i64>().is_err() && text != "-" {
535+
} else if text.parse::<i64>().is_err() && text != "-" && text != "." {
530536
return event::Status::Ignored;
531537
} else {
532538
text.to_string()
533539
};
534540

535541
let input = input.trim();
536542

537-
let mut new_val = self.value.to_string();
543+
let mut new_val = current_text;
538544
match text_input.cursor().state(&Value::new(&new_val)) {
539545
cursor::State::Index(idx) if idx <= new_val.len() => {
540546
new_val.insert_str(idx, input);
@@ -554,7 +560,11 @@ where
554560
self.value = val;
555561
forward_to_text(event, shell, child, clipboard)
556562
}
557-
Ok(_) => event::Status::Captured,
563+
Ok(val)
564+
if (self.min..self.max).contains(&val) =>
565+
forward_to_text(event, shell, child, clipboard),
566+
Ok(_) =>
567+
event::Status::Captured,
558568
_ => event::Status::Ignored,
559569
}
560570
}
@@ -569,7 +579,9 @@ where
569579
event::Status::Captured
570580
}
571581
keyboard::Key::Named(
572-
keyboard::key::Named::ArrowLeft | keyboard::key::Named::ArrowRight,
582+
keyboard::key::Named::ArrowLeft | keyboard::key::Named::ArrowRight |
583+
keyboard::key::Named::Home |
584+
keyboard::key::Named::End,
573585
) => forward_to_text(event, shell, child, clipboard),
574586
_ => event::Status::Ignored,
575587
},
@@ -661,7 +673,7 @@ where
661673
state: &Tree,
662674
renderer: &mut Renderer,
663675
theme: &Theme,
664-
_style: &renderer::Style,
676+
style: &renderer::Style,
665677
layout: Layout<'_>,
666678
cursor: Cursor,
667679
viewport: &Rectangle,
@@ -684,9 +696,9 @@ where
684696
&state.children[0],
685697
renderer,
686698
theme,
699+
style,
687700
content_layout,
688701
cursor,
689-
None,
690702
viewport,
691703
);
692704
let is_decrease_disabled = self.value <= self.min || self.min == self.max;

0 commit comments

Comments
 (0)