Merge branch 'master' into advanced-text

This commit is contained in:
Héctor Ramón Jiménez 2023-04-17 23:41:12 +02:00
commit 4bae457c37
No known key found for this signature in database
GPG key ID: 140CC052C94F138E
73 changed files with 1586 additions and 703 deletions

View file

@ -49,8 +49,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)
@ -68,9 +68,10 @@ 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>>,
style: <Renderer::Theme as StyleSheet>::Style,
}
@ -84,12 +85,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),
@ -99,9 +96,10 @@ 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,
style: Default::default(),
}
}
@ -118,6 +116,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(
@ -135,6 +152,13 @@ where
self.font = Some(font);
self
}
/// Sets the [`Icon`] of the [`TextInput`].
pub fn icon(mut self, icon: Icon<Renderer::Font>) -> Self {
self.icon = Some(icon);
self
}
/// Sets the width of the [`TextInput`].
pub fn width(mut self, width: impl Into<Length>) -> Self {
self.width = width.into();
@ -153,13 +177,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,
@ -192,7 +209,9 @@ where
&self.placeholder,
self.size,
self.font,
self.on_input.is_none(),
self.is_secure,
self.icon.as_ref(),
&self.style,
)
}
@ -213,6 +232,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
}
@ -226,7 +257,14 @@ where
renderer: &Renderer,
limits: &layout::Limits,
) -> layout::Node {
layout(renderer, limits, self.width, self.padding, self.size)
layout(
renderer,
limits,
self.width,
self.padding,
self.size,
self.icon.as_ref(),
)
}
fn operate(
@ -263,7 +301,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>(),
@ -290,7 +328,9 @@ where
&self.placeholder,
self.size,
self.font,
self.on_input.is_none(),
self.is_secure,
self.icon.as_ref(),
&self.style,
)
}
@ -303,7 +343,7 @@ where
_viewport: &Rectangle,
_renderer: &Renderer,
) -> mouse::Interaction {
mouse_interaction(layout, cursor_position)
mouse_interaction(layout, cursor_position, self.on_input.is_none())
}
}
@ -321,6 +361,30 @@ where
}
}
/// The content of the [`Icon`].
#[derive(Debug, Clone)]
pub struct Icon<Font> {
/// The font that will be used to display the `code_point`.
pub font: Font,
/// The unicode code point that will be used as the icon.
pub code_point: char,
/// The font size of the content.
pub size: Option<f32>,
/// The spacing between the [`Icon`] and the text in a [`TextInput`].
pub spacing: f32,
/// The side of a [`TextInput`] where to display the [`Icon`].
pub side: Side,
}
/// The side of a [`TextInput`].
#[derive(Debug, Clone)]
pub enum Side {
/// The left side of a [`TextInput`].
Left,
/// The right side of a [`TextInput`].
Right,
}
/// The identifier of a [`TextInput`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Id(widget::Id);
@ -383,6 +447,7 @@ pub fn layout<Renderer>(
width: Length,
padding: Padding,
size: Option<f32>,
icon: Option<&Icon<Renderer::Font>>,
) -> layout::Node
where
Renderer: text::Renderer,
@ -391,10 +456,51 @@ where
let padding = padding.fit(Size::ZERO, limits.max());
let limits = limits.width(width).pad(padding).height(text_size * 1.2);
let mut text = layout::Node::new(limits.resolve(Size::ZERO));
text.move_to(Point::new(padding.left, padding.top));
let text_bounds = limits.resolve(Size::ZERO);
layout::Node::with_children(text.size().pad(padding), vec![text])
if let Some(icon) = icon {
let icon_width = renderer.measure_width(
&icon.code_point.to_string(),
icon.size.unwrap_or_else(|| renderer.default_size()),
icon.font,
);
let mut text_node = layout::Node::new(
text_bounds - Size::new(icon_width + icon.spacing, 0.0),
);
let mut icon_node =
layout::Node::new(Size::new(icon_width, text_bounds.height));
match icon.side {
Side::Left => {
text_node.move_to(Point::new(
padding.left + icon_width + icon.spacing,
padding.top,
));
icon_node.move_to(Point::new(padding.left, padding.top));
}
Side::Right => {
text_node.move_to(Point::new(padding.left, padding.top));
icon_node.move_to(Point::new(
padding.left + text_bounds.width - icon_width,
padding.top,
));
}
};
layout::Node::with_children(
text_bounds.pad(padding),
vec![text_node, icon_node],
)
} else {
let mut text = layout::Node::new(text_bounds);
text.move_to(Point::new(padding.left, padding.top));
layout::Node::with_children(text_bounds.pad(padding), vec![text])
}
}
/// Processes an [`Event`] and updates the [`State`] of a [`TextInput`]
@ -410,7 +516,7 @@ pub fn update<'a, Message, Renderer>(
size: Option<f32>,
font: Option<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,
@ -423,7 +529,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(|| {
@ -553,6 +660,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()
@ -561,7 +670,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();
@ -574,6 +683,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();
@ -599,7 +710,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 => {
@ -619,7 +730,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 => {
@ -694,7 +805,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 => {
@ -721,7 +832,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);
@ -815,7 +926,9 @@ pub fn draw<Renderer>(
placeholder: &str,
size: Option<f32>,
font: Option<Renderer::Font>,
is_disabled: bool,
is_secure: bool,
icon: Option<&Icon<Renderer::Font>>,
style: &<Renderer::Theme as StyleSheet>::Style,
) where
Renderer: text::Renderer,
@ -825,11 +938,15 @@ pub fn draw<Renderer>(
let value = secure_value.as_ref().unwrap_or(value);
let bounds = layout.bounds();
let text_bounds = layout.children().next().unwrap().bounds();
let mut children_layout = layout.children();
let text_bounds = children_layout.next().unwrap().bounds();
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)
@ -847,6 +964,20 @@ pub fn draw<Renderer>(
appearance.background,
);
if let Some(icon) = icon {
let icon_layout = children_layout.next().unwrap();
renderer.fill_text(Text {
content: &icon.code_point.to_string(),
size: icon.size.unwrap_or_else(|| renderer.default_size()),
font: icon.font,
color: appearance.icon_color,
bounds: icon_layout.bounds(),
horizontal_alignment: alignment::Horizontal::Left,
vertical_alignment: alignment::Vertical::Top,
});
}
let text = value.to_string();
let font = font.unwrap_or_else(|| renderer.default_font());
let size = size.unwrap_or_else(|| renderer.default_size());
@ -959,6 +1090,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)
},
@ -987,9 +1120,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()
}