diff --git a/examples/cosmic-sctk/src/window.rs b/examples/cosmic-sctk/src/window.rs index 2b33d2a8..54d27757 100644 --- a/examples/cosmic-sctk/src/window.rs +++ b/examples/cosmic-sctk/src/window.rs @@ -15,8 +15,8 @@ use cosmic::{ widget::{ button, cosmic_container, header_bar, icon, inline_input, nav_bar, nav_bar_toggle, rectangle_tracker::{rectangle_tracker_subscription, RectangleTracker, RectangleUpdate}, - scrollable, search_input, segmented_button, segmented_selection, settings, text_input, - IconSource, + scrollable, search_input, secure_input, segmented_button, segmented_selection, settings, + text_input, IconSource, }, Element, ElementExt, }; @@ -129,6 +129,7 @@ pub struct Window { pub selection: segmented_button::SingleSelectModel, timeline: Timeline, input_value: String, + secure_input_visible: bool, } impl Window { @@ -191,6 +192,7 @@ pub enum Message { Selection(segmented_button::Entity), Tick(Instant), InputChanged(String), + ToggleVisible, } impl Window { @@ -319,6 +321,9 @@ impl Application for Window { Message::InputChanged(v) => { self.input_value = v; } + Message::ToggleVisible => { + self.secure_input_visible = !self.secure_input_visible; + } } Command::none() @@ -488,20 +493,23 @@ impl Application for Window { )) .add(settings::item( "Text Input", - text_input("test", &self.input_value) - .start_icon(icon("document-properties-symbolic", 16).into()) - .end_icon(icon("document-properties-symbolic", 16).into()) - .label("Test Label") - .helper_text("helper_text") - .width(Length::Fill) - .on_input(Message::InputChanged), + secure_input( + "test", + &self.input_value, + Some(Message::ToggleVisible), + !self.secure_input_visible, + ) + .label("Test Secure Input Label") + .helper_text("password") + .width(Length::Fill) + .on_input(Message::InputChanged), )) .add(settings::item( "Text Input", search_input( "search for stuff", &self.input_value, - Message::InputChanged("".to_string()), + Some(Message::InputChanged("".to_string())), ) .width(Length::Fill) .on_input(Message::InputChanged), diff --git a/src/widget/text_input/text_input.rs b/src/widget/text_input/text_input.rs index 5bda6237..c33c1a2f 100644 --- a/src/widget/text_input/text_input.rs +++ b/src/widget/text_input/text_input.rs @@ -46,45 +46,84 @@ where 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`]. /// /// [`TextInput`]: widget::TextInput pub fn search_input<'a, Message>( placeholder: &str, value: &str, - on_clear: Message, + on_clear: Option, ) -> TextInput<'a, Message> where Message: Clone + 'static, { 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) .start_icon( - iced_widget::container(crate::widget::icon("system-search-symbolic", 16)) - .padding([spacing, spacing, spacing, 2 * spacing]) - .into(), - ) - .end_icon( + iced_widget::container( + crate::widget::icon("system-search-symbolic", 16) + .style(crate::theme::Svg::Symbolic), + ) + .padding([spacing, spacing, spacing, spacing]) + .into(), + ); + + if let Some(msg) = on_clear { + input.end_icon( crate::widget::button::button(crate::theme::Button::Text) .icon(crate::theme::Svg::Symbolic, "edit-clear-symbolic", 16) - .on_press(on_clear) - .padding([spacing, 2 * spacing, spacing, spacing]) + .on_press(msg) + .padding([spacing, spacing, spacing, spacing]) .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, + 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`]. @@ -94,7 +133,11 @@ pub fn inline_input<'a, Message>(value: &str) -> TextInput<'a, Message> where 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] = &[ @@ -170,6 +213,8 @@ where /// - a placeholder, /// - the current value pub fn new(placeholder: &str, value: &str) -> Self { + let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs()); + TextInput { id: None, placeholder: String::from(placeholder), @@ -177,7 +222,7 @@ where is_secure: false, font: None, width: Length::Fill, - padding: Padding::new(5.0), + padding: [spacing, spacing, spacing, spacing].into(), size: None, helper_size: 10.0, helper_line_height: text::LineHeight::from(14.0), @@ -502,10 +547,15 @@ where shell: &mut Shell<'_, Message>, viewport: &Rectangle, ) -> event::Status { - if let (Some(start_icon), Some(tree)) = (self.start_icon.as_mut(), tree.children.get_mut(0)) - { - if matches!( - start_icon.as_widget_mut().on_event( + let text_layout = self.text_layout(layout); + let mut child_state = tree.children.iter_mut(); + if let (Some(start_icon), Some(tree)) = (self.start_icon.as_mut(), child_state.next()) { + 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, event.clone(), layout, @@ -513,17 +563,18 @@ where renderer, clipboard, shell, - viewport - ), - event::Status::Captured - ) { - return event::Status::Captured; + viewport, + ); } } - if let (Some(end_icon), Some(tree)) = (self.end_element.as_mut(), tree.children.get_mut(2)) - { - if matches!( - end_icon.as_widget_mut().on_event( + if let (Some(end_icon), Some(tree)) = (self.end_element.as_mut(), child_state.next()) { + let mut children = text_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.as_widget_mut().on_event( tree, event.clone(), layout, @@ -531,17 +582,14 @@ where renderer, clipboard, shell, - viewport - ), - event::Status::Captured - ) { - return event::Status::Captured; + viewport, + ); } } update( event, - self.text_layout(layout), + text_layout.children().next().unwrap(), cursor_position, renderer, clipboard, @@ -600,13 +648,51 @@ where fn mouse_interaction( &self, - _state: &Tree, + state: &Tree, layout: Layout<'_>, cursor_position: mouse::Cursor, - _viewport: &Rectangle, - _renderer: &crate::Renderer, + viewport: &Rectangle, + renderer: &crate::Renderer, ) -> mouse::Interaction { 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()) } } @@ -720,35 +806,42 @@ pub fn layout( } else { (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 = 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 text_node_bounds = text_node.bounds(); node_list.push(text_node); 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); } if let Some(mut end_icon) = end_icon.take() { end_icon.move_to(Point::new( - text_node_bounds.x + text_node_bounds.width + icon_spacing, - padding.top, + text_node_bounds.x + text_node_bounds.width, + padding.top + ((text_size * 1.2 - end_icon.bounds().height) / 2.0).max(0.0), )); node_list.push(end_icon); } - let node = - layout::Node::with_children(text_bounds.pad(padding), node_list).translate(text_pos); - let y_pos = node.bounds().y + node.bounds().height + f32::from(spacing); - nodes.push(node); + let input_limits = limits.width(width).pad(padding).height(height); + let input_bounds = input_limits.resolve(Size::ZERO); + let input_node = layout::Node::with_children(input_bounds, node_list).translate(text_pos); + let y_pos = input_node.bounds().y + input_node.bounds().height + f32::from(spacing); + nodes.push(input_node); Vector::new(0.0, y_pos) } else { @@ -1598,8 +1691,7 @@ pub fn draw<'a, Message>( ) { // all children should be icon images let children = &tree.children; - let start_icon_tree = children.get(0); - let end_icon_tree = children.get(1); + let state = tree.state.downcast_ref::(); let secure_value = is_secure.then(|| value.secure()); let value = secure_value.as_ref().unwrap_or(value); @@ -1695,7 +1787,8 @@ pub fn draw<'a, Message>( 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 if let (Some(icon), Some(tree)) = (icon, start_icon_tree) { let icon_layout = children_layout.next().unwrap(); @@ -1711,6 +1804,7 @@ pub fn draw<'a, Message>( cursor_position, viewport, ); + child_index += 1; } let text = value.to_string(); @@ -1861,6 +1955,8 @@ pub fn draw<'a, Message>( render(renderer); } + let end_icon_tree = children.get(child_index); + // draw the end icon in the text input if let (Some(icon), Some(tree)) = (end_element, end_icon_tree) { let icon_layout = children_layout.next().unwrap();