Merge pull request #1744 from JungleTryne/disable-text-input

Add functionality to make `TextInput` disabled
This commit is contained in:
Héctor Ramón 2023-04-12 04:51:19 +02:00 committed by GitHub
commit ce8e92ca7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 143 additions and 95 deletions

View file

@ -12,4 +12,5 @@ pub enum Interaction {
Grabbing,
ResizingHorizontally,
ResizingVertically,
NotAllowed,
}

View file

@ -141,8 +141,8 @@ mod numeric_input {
.map(u32::to_string)
.as_deref()
.unwrap_or(""),
Event::InputChanged,
)
.on_input(Event::InputChanged)
.padding(10),
button("+", Event::IncrementPressed),
]

View file

@ -100,11 +100,10 @@ impl Program for Controls {
.size(14)
.style(Color::WHITE),
)
.push(text_input(
"Placeholder",
text,
Message::TextChanged,
)),
.push(
text_input("Placeholder", text)
.on_input(Message::TextChanged),
),
),
)
.into()

View file

@ -214,12 +214,9 @@ impl Sandbox for App {
column![
scrollable(options).height(Length::Fill),
row![
text_input(
"Add a new option",
&self.input,
Message::InputChanged,
)
.on_submit(Message::AddItem(self.input.clone())),
text_input("Add a new option", &self.input)
.on_input(Message::InputChanged)
.on_submit(Message::AddItem(self.input.clone())),
button(text(format!("Toggle Order ({})", self.order)))
.on_press(Message::ToggleOrder)
]

View file

@ -133,18 +133,16 @@ impl Application for App {
column![
column![
text("Email").size(12),
text_input(
"abc@123.com",
&self.email,
Message::Email
)
.on_submit(Message::Submit)
.padding(5),
text_input("abc@123.com", &self.email,)
.on_input(Message::Email)
.on_submit(Message::Submit)
.padding(5),
]
.spacing(5),
column![
text("Password").size(12),
text_input("", &self.password, Message::Password)
text_input("", &self.password)
.on_input(Message::Password)
.on_submit(Message::Submit)
.password()
.padding(5),

View file

@ -49,13 +49,11 @@ impl Sandbox for QRGenerator {
.size(70)
.style(Color::from([0.5, 0.5, 0.5]));
let input = text_input(
"Type the data of your QR code here...",
&self.data,
Message::DataChanged,
)
.size(30)
.padding(15);
let input =
text_input("Type the data of your QR code here...", &self.data)
.on_input(Message::DataChanged)
.size(30)
.padding(15);
let mut content = column![title, input]
.width(700)

View file

@ -90,13 +90,10 @@ impl Sandbox for Styling {
},
);
let text_input = text_input(
"Type something...",
&self.input_value,
Message::InputChanged,
)
.padding(10)
.size(20);
let text_input = text_input("Type something...", &self.input_value)
.on_input(Message::InputChanged)
.padding(10)
.size(20);
let button = button("Submit")
.padding(10)

View file

@ -119,13 +119,15 @@ impl Application for App {
column![
subtitle(
"Title",
text_input("", &self.editing.title, Message::Title)
text_input("", &self.editing.title)
.on_input(Message::Title)
.on_submit(Message::Add)
.into()
),
subtitle(
"Message",
text_input("", &self.editing.body, Message::Body)
text_input("", &self.editing.body)
.on_input(Message::Body)
.on_submit(Message::Add)
.into()
),

View file

@ -204,15 +204,12 @@ impl Application for Todos {
.style(Color::from([0.5, 0.5, 0.5]))
.horizontal_alignment(alignment::Horizontal::Center);
let input = text_input(
"What needs to be done?",
input_value,
Message::InputChanged,
)
.id(INPUT_ID.clone())
.padding(15)
.size(30)
.on_submit(Message::CreateTask);
let input = text_input("What needs to be done?", input_value)
.id(INPUT_ID.clone())
.on_input(Message::InputChanged)
.on_submit(Message::CreateTask)
.padding(15)
.size(30);
let controls = view_controls(tasks, *filter);
let filtered_tasks =
@ -375,14 +372,12 @@ impl Task {
.into()
}
TaskState::Editing => {
let text_input = text_input(
"Describe your task...",
&self.description,
TaskMessage::DescriptionEdited,
)
.id(Self::text_input_id(i))
.on_submit(TaskMessage::FinishEdition)
.padding(10);
let text_input =
text_input("Describe your task...", &self.description)
.id(Self::text_input_id(i))
.on_input(TaskMessage::DescriptionEdited)
.on_submit(TaskMessage::FinishEdition)
.padding(10);
row![
text_input,

View file

@ -570,13 +570,10 @@ impl<'a> Step {
bytes: include_bytes!("../fonts/icons.ttf"),
};
let mut text_input = text_input(
"Type something to continue...",
value,
StepMessage::InputChanged,
)
.padding(10)
.size(30);
let mut text_input = text_input("Type something to continue...", value)
.on_input(StepMessage::InputChanged)
.padding(10)
.size(30);
if is_showing_icon {
text_input = text_input.icon(text_input::Icon {

View file

@ -125,12 +125,9 @@ impl Application for WebSocket {
};
let new_message_input = {
let mut input = text_input(
"Type a message...",
&self.new_message,
Message::NewMessageChanged,
)
.padding(10);
let mut input = text_input("Type a message...", &self.new_message)
.on_input(Message::NewMessageChanged)
.padding(10);
let mut button = button(
text("Send")

View file

@ -171,14 +171,13 @@ where
pub fn text_input<'a, Message, Renderer>(
placeholder: &str,
value: &str,
on_change: impl Fn(String) -> Message + 'a,
) -> widget::TextInput<'a, Message, Renderer>
where
Message: Clone,
Renderer: crate::text::Renderer,
Renderer::Theme: widget::text_input::StyleSheet,
{
widget::TextInput::new(placeholder, value, on_change)
widget::TextInput::new(placeholder, value)
}
/// Creates a new [`Slider`].

View file

@ -46,8 +46,8 @@ pub use iced_style::text_input::{Appearance, StyleSheet};
/// let input = TextInput::new(
/// "This is the placeholder...",
/// value,
/// Message::TextInputChanged,
/// )
/// .on_input(Message::TextInputChanged)
/// .padding(10);
/// ```
/// ![Text input drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/text_input.png?raw=true)
@ -65,7 +65,7 @@ where
width: Length,
padding: Padding,
size: Option<f32>,
on_change: Box<dyn Fn(String) -> Message + 'a>,
on_input: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
on_submit: Option<Message>,
icon: Option<Icon<Renderer::Font>>,
@ -82,12 +82,8 @@ where
///
/// It expects:
/// - a placeholder,
/// - the current value, and
/// - a function that produces a message when the [`TextInput`] changes.
pub fn new<F>(placeholder: &str, value: &str, on_change: F) -> Self
where
F: 'a + Fn(String) -> Message,
{
/// - the current value
pub fn new(placeholder: &str, value: &str) -> Self {
TextInput {
id: None,
placeholder: String::from(placeholder),
@ -97,7 +93,7 @@ where
width: Length::Fill,
padding: Padding::new(5.0),
size: None,
on_change: Box::new(on_change),
on_input: None,
on_paste: None,
on_submit: None,
icon: None,
@ -117,6 +113,25 @@ where
self
}
/// Sets the message that should be produced when some text is typed into
/// the [`TextInput`].
///
/// If this method is not called, the [`TextInput`] will be disabled.
pub fn on_input<F>(mut self, callback: F) -> Self
where
F: 'a + Fn(String) -> Message,
{
self.on_input = Some(Box::new(callback));
self
}
/// Sets the message that should be produced when the [`TextInput`] is
/// focused and the enter key is pressed.
pub fn on_submit(mut self, message: Message) -> Self {
self.on_submit = Some(message);
self
}
/// Sets the message that should be produced when some text is pasted into
/// the [`TextInput`].
pub fn on_paste(
@ -159,13 +174,6 @@ where
self
}
/// Sets the message that should be produced when the [`TextInput`] is
/// focused and the enter key is pressed.
pub fn on_submit(mut self, message: Message) -> Self {
self.on_submit = Some(message);
self
}
/// Sets the style of the [`TextInput`].
pub fn style(
mut self,
@ -198,6 +206,7 @@ where
&self.placeholder,
self.size,
&self.font,
self.on_input.is_none(),
self.is_secure,
self.icon.as_ref(),
&self.style,
@ -220,6 +229,18 @@ where
tree::State::new(State::new())
}
fn diff(&self, tree: &mut Tree) {
let state = tree.state.downcast_mut::<State>();
// Unfocus text input if it becomes disabled
if self.on_input.is_none() {
state.last_click = None;
state.is_focused = None;
state.is_pasting = None;
state.is_dragging = false;
}
}
fn width(&self) -> Length {
self.width
}
@ -277,7 +298,7 @@ where
self.size,
&self.font,
self.is_secure,
self.on_change.as_ref(),
self.on_input.as_deref(),
self.on_paste.as_deref(),
&self.on_submit,
|| tree.state.downcast_mut::<State>(),
@ -304,6 +325,7 @@ where
&self.placeholder,
self.size,
&self.font,
self.on_input.is_none(),
self.is_secure,
self.icon.as_ref(),
&self.style,
@ -318,7 +340,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor_position)
mouse_interaction(layout, cursor_position, self.on_input.is_none())
}
}
@ -492,7 +514,7 @@ pub fn update<'a, Message, Renderer>(
size: Option<f32>,
font: &Renderer::Font,
is_secure: bool,
on_change: &dyn Fn(String) -> Message,
on_input: Option<&dyn Fn(String) -> Message>,
on_paste: Option<&dyn Fn(String) -> Message>,
on_submit: &Option<Message>,
state: impl FnOnce() -> &'a mut State,
@ -505,7 +527,8 @@ where
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
| Event::Touch(touch::Event::FingerPressed { .. }) => {
let state = state();
let is_clicked = layout.bounds().contains(cursor_position);
let is_clicked =
layout.bounds().contains(cursor_position) && on_input.is_some();
state.is_focused = if is_clicked {
state.is_focused.or_else(|| {
@ -635,6 +658,8 @@ where
let state = state();
if let Some(focus) = &mut state.is_focused {
let Some(on_input) = on_input else { return event::Status::Ignored };
if state.is_pasting.is_none()
&& !state.keyboard_modifiers.command()
&& !c.is_control()
@ -643,7 +668,7 @@ where
editor.insert(c);
let message = (on_change)(editor.contents());
let message = (on_input)(editor.contents());
shell.publish(message);
focus.updated_at = Instant::now();
@ -656,6 +681,8 @@ where
let state = state();
if let Some(focus) = &mut state.is_focused {
let Some(on_input) = on_input else { return event::Status::Ignored };
let modifiers = state.keyboard_modifiers;
focus.updated_at = Instant::now();
@ -681,7 +708,7 @@ where
let mut editor = Editor::new(value, &mut state.cursor);
editor.backspace();
let message = (on_change)(editor.contents());
let message = (on_input)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::Delete => {
@ -701,7 +728,7 @@ where
let mut editor = Editor::new(value, &mut state.cursor);
editor.delete();
let message = (on_change)(editor.contents());
let message = (on_input)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::Left => {
@ -776,7 +803,7 @@ where
let mut editor = Editor::new(value, &mut state.cursor);
editor.delete();
let message = (on_change)(editor.contents());
let message = (on_input)(editor.contents());
shell.publish(message);
}
keyboard::KeyCode::V => {
@ -803,7 +830,7 @@ where
let message = if let Some(paste) = &on_paste {
(paste)(editor.contents())
} else {
(on_change)(editor.contents())
(on_input)(editor.contents())
};
shell.publish(message);
@ -897,6 +924,7 @@ pub fn draw<Renderer>(
placeholder: &str,
size: Option<f32>,
font: &Renderer::Font,
is_disabled: bool,
is_secure: bool,
icon: Option<&Icon<Renderer::Font>>,
style: &<Renderer::Theme as StyleSheet>::Style,
@ -914,7 +942,9 @@ pub fn draw<Renderer>(
let is_mouse_over = bounds.contains(cursor_position);
let appearance = if state.is_focused() {
let appearance = if is_disabled {
theme.disabled(style)
} else if state.is_focused() {
theme.focused(style)
} else if is_mouse_over {
theme.hovered(style)
@ -1057,6 +1087,8 @@ pub fn draw<Renderer>(
content: if text.is_empty() { placeholder } else { &text },
color: if text.is_empty() {
theme.placeholder_color(style)
} else if is_disabled {
theme.disabled_color(style)
} else {
theme.value_color(style)
},
@ -1085,9 +1117,14 @@ pub fn draw<Renderer>(
pub fn mouse_interaction(
layout: Layout<'_>,
cursor_position: Point,
is_disabled: bool,
) -> mouse::Interaction {
if layout.bounds().contains(cursor_position) {
mouse::Interaction::Text
if is_disabled {
mouse::Interaction::NotAllowed
} else {
mouse::Interaction::Text
}
} else {
mouse::Interaction::default()
}

View file

@ -33,6 +33,9 @@ pub trait StyleSheet {
/// Produces the [`Color`] of the value of a text input.
fn value_color(&self, style: &Self::Style) -> Color;
/// Produces the [`Color`] of the value of a disabled text input.
fn disabled_color(&self, style: &Self::Style) -> Color;
/// Produces the [`Color`] of the selection of a text input.
fn selection_color(&self, style: &Self::Style) -> Color;
@ -40,4 +43,7 @@ pub trait StyleSheet {
fn hovered(&self, style: &Self::Style) -> Appearance {
self.focused(style)
}
/// Produces the style of a disabled text input.
fn disabled(&self, style: &Self::Style) -> Appearance;
}

View file

@ -1093,4 +1093,28 @@ impl text_input::StyleSheet for Theme {
palette.primary.weak.color
}
fn disabled(&self, style: &Self::Style) -> text_input::Appearance {
if let TextInput::Custom(custom) = style {
return custom.disabled(self);
}
let palette = self.extended_palette();
text_input::Appearance {
background: palette.background.weak.color.into(),
border_radius: 2.0,
border_width: 1.0,
border_color: palette.background.strong.color,
icon_color: palette.background.strong.color,
}
}
fn disabled_color(&self, style: &Self::Style) -> Color {
if let TextInput::Custom(custom) = style {
return custom.disabled_color(self);
}
self.placeholder_color(style)
}
}

View file

@ -236,6 +236,7 @@ pub fn mouse_interaction(
winit::window::CursorIcon::EwResize
}
Interaction::ResizingVertically => winit::window::CursorIcon::NsResize,
Interaction::NotAllowed => winit::window::CursorIcon::NotAllowed,
}
}