wip: improve text input

This commit is contained in:
Ashley Wulber 2023-08-17 12:46:23 -04:00
parent 80fe195a2a
commit 4a554e9ec5
No known key found for this signature in database
GPG key ID: 5216D4F46A90A820
2 changed files with 173 additions and 69 deletions

View file

@ -15,8 +15,8 @@ use cosmic::{
widget::{ widget::{
button, cosmic_container, header_bar, icon, inline_input, nav_bar, nav_bar_toggle, button, cosmic_container, header_bar, icon, inline_input, nav_bar, nav_bar_toggle,
rectangle_tracker::{rectangle_tracker_subscription, RectangleTracker, RectangleUpdate}, rectangle_tracker::{rectangle_tracker_subscription, RectangleTracker, RectangleUpdate},
scrollable, search_input, segmented_button, segmented_selection, settings, text_input, scrollable, search_input, secure_input, segmented_button, segmented_selection, settings,
IconSource, text_input, IconSource,
}, },
Element, ElementExt, Element, ElementExt,
}; };
@ -129,6 +129,7 @@ pub struct Window {
pub selection: segmented_button::SingleSelectModel, pub selection: segmented_button::SingleSelectModel,
timeline: Timeline, timeline: Timeline,
input_value: String, input_value: String,
secure_input_visible: bool,
} }
impl Window { impl Window {
@ -191,6 +192,7 @@ pub enum Message {
Selection(segmented_button::Entity), Selection(segmented_button::Entity),
Tick(Instant), Tick(Instant),
InputChanged(String), InputChanged(String),
ToggleVisible,
} }
impl Window { impl Window {
@ -319,6 +321,9 @@ impl Application for Window {
Message::InputChanged(v) => { Message::InputChanged(v) => {
self.input_value = v; self.input_value = v;
} }
Message::ToggleVisible => {
self.secure_input_visible = !self.secure_input_visible;
}
} }
Command::none() Command::none()
@ -488,11 +493,14 @@ impl Application for Window {
)) ))
.add(settings::item( .add(settings::item(
"Text Input", "Text Input",
text_input("test", &self.input_value) secure_input(
.start_icon(icon("document-properties-symbolic", 16).into()) "test",
.end_icon(icon("document-properties-symbolic", 16).into()) &self.input_value,
.label("Test Label") Some(Message::ToggleVisible),
.helper_text("helper_text") !self.secure_input_visible,
)
.label("Test Secure Input Label")
.helper_text("password")
.width(Length::Fill) .width(Length::Fill)
.on_input(Message::InputChanged), .on_input(Message::InputChanged),
)) ))
@ -501,7 +509,7 @@ impl Application for Window {
search_input( search_input(
"search for stuff", "search for stuff",
&self.input_value, &self.input_value,
Message::InputChanged("".to_string()), Some(Message::InputChanged("".to_string())),
) )
.width(Length::Fill) .width(Length::Fill)
.on_input(Message::InputChanged), .on_input(Message::InputChanged),

View file

@ -46,45 +46,84 @@ where
TextInput::new(placeholder, value) TextInput::new(placeholder, value)
} }
/// Creates a new expandable search [`TextInput`].
///
/// [`TextInput`]: widget::TextInput
pub fn expandable_search_input<'a, Message>(
placeholder: &str,
value: &str,
) -> TextInput<'a, Message>
where
Message: Clone,
{
TextInput::new(placeholder, value).style(super::style::TextInput::Default)
}
/// Creates a new search [`TextInput`]. /// Creates a new search [`TextInput`].
/// ///
/// [`TextInput`]: widget::TextInput /// [`TextInput`]: widget::TextInput
pub fn search_input<'a, Message>( pub fn search_input<'a, Message>(
placeholder: &str, placeholder: &str,
value: &str, value: &str,
on_clear: Message, on_clear: Option<Message>,
) -> TextInput<'a, Message> ) -> TextInput<'a, Message>
where where
Message: Clone + 'static, Message: Clone + 'static,
{ {
let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs()); let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
TextInput::new(placeholder, value) let input = TextInput::new(placeholder, value)
.padding([0, spacing, 0, spacing])
.style(super::style::TextInput::Search) .style(super::style::TextInput::Search)
.start_icon( .start_icon(
iced_widget::container(crate::widget::icon("system-search-symbolic", 16)) iced_widget::container(
.padding([spacing, spacing, spacing, 2 * spacing]) crate::widget::icon("system-search-symbolic", 16)
.into(), .style(crate::theme::Svg::Symbolic),
) )
.end_icon( .padding([spacing, spacing, spacing, spacing])
.into(),
);
if let Some(msg) = on_clear {
input.end_icon(
crate::widget::button::button(crate::theme::Button::Text) crate::widget::button::button(crate::theme::Button::Text)
.icon(crate::theme::Svg::Symbolic, "edit-clear-symbolic", 16) .icon(crate::theme::Svg::Symbolic, "edit-clear-symbolic", 16)
.on_press(on_clear) .on_press(msg)
.padding([spacing, 2 * spacing, spacing, spacing]) .padding([spacing, spacing, spacing, spacing])
.into(), .into(),
) )
} else {
input
}
}
/// Creates a new search [`TextInput`].
///
/// [`TextInput`]: widget::TextInput
pub fn secure_input<'a, Message>(
placeholder: &str,
value: &str,
on_visible_toggle: Option<Message>,
hidden: bool,
) -> TextInput<'a, Message>
where
Message: Clone + 'static,
{
let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
let mut input = TextInput::new(placeholder, value)
.padding([0, spacing, 0, spacing])
.style(super::style::TextInput::Default)
.start_icon(
iced_widget::container(
crate::widget::icon("system-lock-screen-symbolic", 16)
.style(crate::theme::Svg::Symbolic),
)
.padding([spacing, spacing, spacing, spacing])
.into(),
);
if hidden {
input = input.password();
}
if let Some(msg) = on_visible_toggle {
input.end_icon(
crate::widget::button::button(crate::theme::Button::Text)
.icon(
crate::theme::Svg::Symbolic,
"document-properties-symbolic",
16,
)
.on_press(msg)
.padding([spacing, spacing, spacing, spacing])
.into(),
)
} else {
input
}
} }
/// Creates a new inline [`TextInput`]. /// Creates a new inline [`TextInput`].
@ -94,7 +133,11 @@ pub fn inline_input<'a, Message>(value: &str) -> TextInput<'a, Message>
where where
Message: Clone, Message: Clone,
{ {
TextInput::new("", value).style(super::style::TextInput::Inline) let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
TextInput::new("", value)
.style(super::style::TextInput::Inline)
.padding([spacing, spacing, spacing, spacing])
} }
const SUPPORTED_MIME_TYPES: &[&str; 6] = &[ const SUPPORTED_MIME_TYPES: &[&str; 6] = &[
@ -170,6 +213,8 @@ where
/// - a placeholder, /// - a placeholder,
/// - the current value /// - the current value
pub fn new(placeholder: &str, value: &str) -> Self { pub fn new(placeholder: &str, value: &str) -> Self {
let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
TextInput { TextInput {
id: None, id: None,
placeholder: String::from(placeholder), placeholder: String::from(placeholder),
@ -177,7 +222,7 @@ where
is_secure: false, is_secure: false,
font: None, font: None,
width: Length::Fill, width: Length::Fill,
padding: Padding::new(5.0), padding: [spacing, spacing, spacing, spacing].into(),
size: None, size: None,
helper_size: 10.0, helper_size: 10.0,
helper_line_height: text::LineHeight::from(14.0), helper_line_height: text::LineHeight::from(14.0),
@ -502,10 +547,15 @@ where
shell: &mut Shell<'_, Message>, shell: &mut Shell<'_, Message>,
viewport: &Rectangle, viewport: &Rectangle,
) -> event::Status { ) -> event::Status {
if let (Some(start_icon), Some(tree)) = (self.start_icon.as_mut(), tree.children.get_mut(0)) let text_layout = self.text_layout(layout);
{ let mut child_state = tree.children.iter_mut();
if matches!( if let (Some(start_icon), Some(tree)) = (self.start_icon.as_mut(), child_state.next()) {
start_icon.as_widget_mut().on_event( let mut children = text_layout.children();
children.next();
let start_icon_layout = children.next().unwrap();
if cursor_position.is_over(start_icon_layout.bounds()) {
return start_icon.as_widget_mut().on_event(
tree, tree,
event.clone(), event.clone(),
layout, layout,
@ -513,17 +563,18 @@ where
renderer, renderer,
clipboard, clipboard,
shell, shell,
viewport viewport,
), );
event::Status::Captured
) {
return event::Status::Captured;
} }
} }
if let (Some(end_icon), Some(tree)) = (self.end_element.as_mut(), tree.children.get_mut(2)) if let (Some(end_icon), Some(tree)) = (self.end_element.as_mut(), child_state.next()) {
{ let mut children = text_layout.children();
if matches!( children.next();
end_icon.as_widget_mut().on_event( children.next();
let end_icon_layout = children.next().unwrap();
if cursor_position.is_over(end_icon_layout.bounds()) {
return end_icon.as_widget_mut().on_event(
tree, tree,
event.clone(), event.clone(),
layout, layout,
@ -531,17 +582,14 @@ where
renderer, renderer,
clipboard, clipboard,
shell, shell,
viewport viewport,
), );
event::Status::Captured
) {
return event::Status::Captured;
} }
} }
update( update(
event, event,
self.text_layout(layout), text_layout.children().next().unwrap(),
cursor_position, cursor_position,
renderer, renderer,
clipboard, clipboard,
@ -600,13 +648,51 @@ where
fn mouse_interaction( fn mouse_interaction(
&self, &self,
_state: &Tree, state: &Tree,
layout: Layout<'_>, layout: Layout<'_>,
cursor_position: mouse::Cursor, cursor_position: mouse::Cursor,
_viewport: &Rectangle, viewport: &Rectangle,
_renderer: &crate::Renderer, renderer: &crate::Renderer,
) -> mouse::Interaction { ) -> mouse::Interaction {
let layout = self.text_layout(layout); let layout = self.text_layout(layout);
let mut index = 0;
if let (Some(start_icon), Some(tree)) =
(self.start_icon.as_ref(), state.children.get(index))
{
let mut children = layout.children();
children.next();
let start_icon_layout = children.next().unwrap();
if cursor_position.is_over(start_icon_layout.bounds()) {
return start_icon.mouse_interaction(
tree,
layout,
cursor_position,
viewport,
renderer,
);
}
index += 1;
}
if let (Some(end_icon), Some(tree)) = (self.end_element.as_ref(), state.children.get(index))
{
let mut children = layout.children();
children.next();
children.next();
let end_icon_layout = children.next().unwrap();
if cursor_position.is_over(end_icon_layout.bounds()) {
return end_icon.mouse_interaction(
tree,
layout,
cursor_position,
viewport,
renderer,
);
}
}
mouse_interaction(layout, cursor_position, self.on_input.is_none()) mouse_interaction(layout, cursor_position, self.on_input.is_none())
} }
} }
@ -720,35 +806,42 @@ pub fn layout<Message>(
} else { } else {
(0.0, None) (0.0, None)
}; };
let limits = limits.width(width).pad(padding).height(height); let text_limits = limits.width(width).pad(padding).height(text_size * 1.2);
let text_bounds = limits.resolve(Size::ZERO); let text_bounds = text_limits.resolve(Size::ZERO);
let mut text_node = let mut text_node =
layout::Node::new(text_bounds - Size::new(start_icon_width + end_icon_width, 0.0)); layout::Node::new(text_bounds - Size::new(start_icon_width + end_icon_width, 0.0));
text_node.move_to(Point::new(padding.left + start_icon_width, padding.top)); text_node.move_to(Point::new(
padding.left + start_icon_width,
padding.top + ((height - text_size * 1.2) / 2.0).max(0.0),
));
let mut node_list: Vec<_> = Vec::with_capacity(3); let mut node_list: Vec<_> = Vec::with_capacity(3);
let text_node_bounds = text_node.bounds(); let text_node_bounds = text_node.bounds();
node_list.push(text_node); node_list.push(text_node);
if let Some(mut start_icon) = start_icon.take() { if let Some(mut start_icon) = start_icon.take() {
start_icon.move_to(Point::new(padding.left, padding.top)); start_icon.move_to(Point::new(
padding.left,
padding.top + ((text_size * 1.2 - start_icon.bounds().height) / 2.0).max(0.0),
));
node_list.push(start_icon); node_list.push(start_icon);
} }
if let Some(mut end_icon) = end_icon.take() { if let Some(mut end_icon) = end_icon.take() {
end_icon.move_to(Point::new( end_icon.move_to(Point::new(
text_node_bounds.x + text_node_bounds.width + icon_spacing, text_node_bounds.x + text_node_bounds.width,
padding.top, padding.top + ((text_size * 1.2 - end_icon.bounds().height) / 2.0).max(0.0),
)); ));
node_list.push(end_icon); node_list.push(end_icon);
} }
let node = let input_limits = limits.width(width).pad(padding).height(height);
layout::Node::with_children(text_bounds.pad(padding), node_list).translate(text_pos); let input_bounds = input_limits.resolve(Size::ZERO);
let y_pos = node.bounds().y + node.bounds().height + f32::from(spacing); let input_node = layout::Node::with_children(input_bounds, node_list).translate(text_pos);
nodes.push(node); let y_pos = input_node.bounds().y + input_node.bounds().height + f32::from(spacing);
nodes.push(input_node);
Vector::new(0.0, y_pos) Vector::new(0.0, y_pos)
} else { } else {
@ -1598,8 +1691,7 @@ pub fn draw<'a, Message>(
) { ) {
// all children should be icon images // all children should be icon images
let children = &tree.children; let children = &tree.children;
let start_icon_tree = children.get(0);
let end_icon_tree = children.get(1);
let state = tree.state.downcast_ref::<State>(); let state = tree.state.downcast_ref::<State>();
let secure_value = is_secure.then(|| value.secure()); let secure_value = is_secure.then(|| value.secure());
let value = secure_value.as_ref().unwrap_or(value); let value = secure_value.as_ref().unwrap_or(value);
@ -1695,7 +1787,8 @@ pub fn draw<'a, Message>(
shaping: text::Shaping::Advanced, shaping: text::Shaping::Advanced,
}); });
} }
let mut child_index = 0;
let start_icon_tree = children.get(child_index);
// draw the start icon in the text input // draw the start icon in the text input
if let (Some(icon), Some(tree)) = (icon, start_icon_tree) { if let (Some(icon), Some(tree)) = (icon, start_icon_tree) {
let icon_layout = children_layout.next().unwrap(); let icon_layout = children_layout.next().unwrap();
@ -1711,6 +1804,7 @@ pub fn draw<'a, Message>(
cursor_position, cursor_position,
viewport, viewport,
); );
child_index += 1;
} }
let text = value.to_string(); let text = value.to_string();
@ -1861,6 +1955,8 @@ pub fn draw<'a, Message>(
render(renderer); render(renderer);
} }
let end_icon_tree = children.get(child_index);
// draw the end icon in the text input // draw the end icon in the text input
if let (Some(icon), Some(tree)) = (end_element, end_icon_tree) { if let (Some(icon), Some(tree)) = (end_element, end_icon_tree) {
let icon_layout = children_layout.next().unwrap(); let icon_layout = children_layout.next().unwrap();