wip: improve text input
This commit is contained in:
parent
80fe195a2a
commit
4a554e9ec5
2 changed files with 173 additions and 69 deletions
|
|
@ -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,20 +493,23 @@ 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,
|
||||||
.width(Length::Fill)
|
)
|
||||||
.on_input(Message::InputChanged),
|
.label("Test Secure Input Label")
|
||||||
|
.helper_text("password")
|
||||||
|
.width(Length::Fill)
|
||||||
|
.on_input(Message::InputChanged),
|
||||||
))
|
))
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Text Input",
|
"Text Input",
|
||||||
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),
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue