wip: text inputs with icons and buttons
This commit is contained in:
parent
d15999e5db
commit
80fe195a2a
2 changed files with 255 additions and 165 deletions
|
|
@ -13,9 +13,10 @@ use cosmic::{
|
||||||
iced_widget::text,
|
iced_widget::text,
|
||||||
theme::{self, Theme},
|
theme::{self, Theme},
|
||||||
widget::{
|
widget::{
|
||||||
button, cosmic_container, header_bar, 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, segmented_button, segmented_selection, settings, text_input, IconSource,
|
scrollable, search_input, segmented_button, segmented_selection, settings, text_input,
|
||||||
|
IconSource,
|
||||||
},
|
},
|
||||||
Element, ElementExt,
|
Element, ElementExt,
|
||||||
};
|
};
|
||||||
|
|
@ -488,25 +489,29 @@ impl Application for Window {
|
||||||
.add(settings::item(
|
.add(settings::item(
|
||||||
"Text Input",
|
"Text Input",
|
||||||
text_input("test", &self.input_value)
|
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")
|
.label("Test Label")
|
||||||
.helper_text("helper_text")
|
.helper_text("helper_text")
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.on_input(Message::InputChanged),
|
.on_input(Message::InputChanged),
|
||||||
))
|
))
|
||||||
// .add(settings::item(
|
.add(settings::item(
|
||||||
// "Text Input",
|
"Text Input",
|
||||||
// text_input("test", &self.input_value)
|
search_input(
|
||||||
// .helper_text("helper_text")
|
"search for stuff",
|
||||||
// .width(Length::Fill)
|
&self.input_value,
|
||||||
// .on_input(Message::InputChanged),
|
Message::InputChanged("".to_string()),
|
||||||
// ))
|
)
|
||||||
// .add(settings::item(
|
.width(Length::Fill)
|
||||||
// "Text Input",
|
.on_input(Message::InputChanged),
|
||||||
// text_input("test", &self.input_value)
|
))
|
||||||
// .helper_text("helper_text")
|
.add(settings::item(
|
||||||
// .width(Length::Fill)
|
"Text Input",
|
||||||
// .on_input(Message::InputChanged),
|
inline_input(&self.input_value)
|
||||||
// ))
|
.width(Length::Fill)
|
||||||
|
.on_input(Message::InputChanged),
|
||||||
|
))
|
||||||
.into(),
|
.into(),
|
||||||
])
|
])
|
||||||
.into();
|
.into();
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,13 @@ use super::editor::Editor;
|
||||||
use super::style::StyleSheet;
|
use super::style::StyleSheet;
|
||||||
pub use super::value::Value;
|
pub use super::value::Value;
|
||||||
|
|
||||||
use iced_core::{alignment, Background};
|
use apply::Also;
|
||||||
|
use iced::Limits;
|
||||||
use iced_core::event::{self, Event};
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::keyboard;
|
use iced_core::keyboard;
|
||||||
use iced_core::layout;
|
use iced_core::layout;
|
||||||
use iced_core::mouse::{self, click};
|
use iced_core::mouse::{self, click};
|
||||||
use iced_core::renderer;
|
use iced_core::renderer::{self, Renderer as CoreRenderer};
|
||||||
use iced_core::text::{self, Renderer, Text};
|
use iced_core::text::{self, Renderer, Text};
|
||||||
use iced_core::time::{Duration, Instant};
|
use iced_core::time::{Duration, Instant};
|
||||||
use iced_core::touch;
|
use iced_core::touch;
|
||||||
|
|
@ -22,6 +23,7 @@ use iced_core::widget::operation::{self, Operation};
|
||||||
use iced_core::widget::tree::{self, Tree};
|
use iced_core::widget::tree::{self, Tree};
|
||||||
use iced_core::widget::Id;
|
use iced_core::widget::Id;
|
||||||
use iced_core::window;
|
use iced_core::window;
|
||||||
|
use iced_core::{alignment, Background};
|
||||||
use iced_core::{
|
use iced_core::{
|
||||||
Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
|
Clipboard, Color, Element, Layout, Length, Padding, Pixels, Point, Rectangle, Shell, Size,
|
||||||
Vector, Widget,
|
Vector, Widget,
|
||||||
|
|
@ -37,12 +39,9 @@ use sctk::reexports::client::protocol::wl_data_device_manager::DndAction;
|
||||||
/// Creates a new [`TextInput`].
|
/// Creates a new [`TextInput`].
|
||||||
///
|
///
|
||||||
/// [`TextInput`]: widget::TextInput
|
/// [`TextInput`]: widget::TextInput
|
||||||
pub fn text_input<'a, Message>(
|
pub fn text_input<'a, Message>(placeholder: &str, value: &str) -> TextInput<'a, Message>
|
||||||
placeholder: &str,
|
|
||||||
value: &str,
|
|
||||||
) -> TextInput<'a, Message>
|
|
||||||
where
|
where
|
||||||
Message: Clone
|
Message: Clone,
|
||||||
{
|
{
|
||||||
TextInput::new(placeholder, value)
|
TextInput::new(placeholder, value)
|
||||||
}
|
}
|
||||||
|
|
@ -55,36 +54,47 @@ pub fn expandable_search_input<'a, Message>(
|
||||||
value: &str,
|
value: &str,
|
||||||
) -> TextInput<'a, Message>
|
) -> TextInput<'a, Message>
|
||||||
where
|
where
|
||||||
Message: Clone
|
Message: Clone,
|
||||||
{
|
{
|
||||||
TextInput::new(placeholder, value)
|
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,
|
||||||
) -> TextInput<'a, Message>
|
) -> TextInput<'a, Message>
|
||||||
where
|
where
|
||||||
Message: Clone
|
Message: Clone + 'static,
|
||||||
{
|
{
|
||||||
|
let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
|
||||||
TextInput::new(placeholder, value)
|
TextInput::new(placeholder, value)
|
||||||
|
.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(
|
||||||
|
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])
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new inline [`TextInput`].
|
/// Creates a new inline [`TextInput`].
|
||||||
///
|
///
|
||||||
/// [`TextInput`]: widget::TextInput
|
/// [`TextInput`]: widget::TextInput
|
||||||
pub fn inline_input<'a, Message>(
|
pub fn inline_input<'a, Message>(value: &str) -> TextInput<'a, Message>
|
||||||
placeholder: &str,
|
|
||||||
value: &str,
|
|
||||||
) -> TextInput<'a, Message>
|
|
||||||
where
|
where
|
||||||
Message: Clone
|
Message: Clone,
|
||||||
{
|
{
|
||||||
TextInput::new(placeholder, value)
|
TextInput::new("", value).style(super::style::TextInput::Inline)
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUPPORTED_MIME_TYPES: &[&str; 6] = &[
|
const SUPPORTED_MIME_TYPES: &[&str; 6] = &[
|
||||||
|
|
@ -138,7 +148,8 @@ pub struct TextInput<'a, Message> {
|
||||||
on_input: Option<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_paste: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||||
on_submit: Option<Message>,
|
on_submit: Option<Message>,
|
||||||
icon: Option<Icon<<crate::Renderer as iced_core::text::Renderer>::Font>>,
|
start_icon: Option<Element<'a, Message, crate::Renderer>>,
|
||||||
|
end_element: Option<Element<'a, Message, crate::Renderer>>,
|
||||||
style: <<crate::Renderer as iced_core::Renderer>::Theme as StyleSheet>::Style,
|
style: <<crate::Renderer as iced_core::Renderer>::Theme as StyleSheet>::Style,
|
||||||
// (text_input::State, mime_type, dnd_action) -> Message
|
// (text_input::State, mime_type, dnd_action) -> Message
|
||||||
on_create_dnd_source: Option<Box<dyn Fn(State) -> Message + 'a>>,
|
on_create_dnd_source: Option<Box<dyn Fn(State) -> Message + 'a>>,
|
||||||
|
|
@ -173,7 +184,8 @@ where
|
||||||
on_input: None,
|
on_input: None,
|
||||||
on_paste: None,
|
on_paste: None,
|
||||||
on_submit: None,
|
on_submit: None,
|
||||||
icon: None,
|
start_icon: None,
|
||||||
|
end_element: None,
|
||||||
error: None,
|
error: None,
|
||||||
style: super::style::TextInput::default(),
|
style: super::style::TextInput::default(),
|
||||||
on_dnd_command_produced: None,
|
on_dnd_command_produced: None,
|
||||||
|
|
@ -256,12 +268,15 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Icon`] of the [`TextInput`].
|
/// Sets the start [`Icon`] of the [`TextInput`].
|
||||||
pub fn icon(
|
pub fn start_icon(mut self, icon: Element<'a, Message, crate::Renderer>) -> Self {
|
||||||
mut self,
|
self.start_icon = Some(icon);
|
||||||
icon: Icon<<crate::Renderer as iced_core::text::Renderer>::Font>,
|
self
|
||||||
) -> Self {
|
}
|
||||||
self.icon = Some(icon);
|
|
||||||
|
/// Sets the end [`Icon`] of the [`TextInput`].
|
||||||
|
pub fn end_icon(mut self, icon: Element<'a, Message, crate::Renderer>) -> Self {
|
||||||
|
self.end_element = Some(icon);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,14 +325,15 @@ where
|
||||||
theme,
|
theme,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
tree.state.downcast_ref::<State>(),
|
tree,
|
||||||
value.unwrap_or(&self.value),
|
value.unwrap_or(&self.value),
|
||||||
&self.placeholder,
|
&self.placeholder,
|
||||||
self.size,
|
self.size,
|
||||||
self.font,
|
self.font,
|
||||||
self.on_input.is_none(),
|
self.on_input.is_none(),
|
||||||
self.is_secure,
|
self.is_secure,
|
||||||
self.icon.as_ref(),
|
self.start_icon.as_ref(),
|
||||||
|
self.end_element.as_ref(),
|
||||||
&self.style,
|
&self.style,
|
||||||
self.dnd_icon,
|
self.dnd_icon,
|
||||||
self.line_height,
|
self.line_height,
|
||||||
|
|
@ -325,7 +341,8 @@ where
|
||||||
self.label,
|
self.label,
|
||||||
self.helper_text,
|
self.helper_text,
|
||||||
self.helper_size,
|
self.helper_size,
|
||||||
self.helper_line_height
|
self.helper_line_height,
|
||||||
|
&layout.bounds(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,6 +415,21 @@ where
|
||||||
state.is_pasting = None;
|
state.is_pasting = None;
|
||||||
state.dragging_state = None;
|
state.dragging_state = None;
|
||||||
}
|
}
|
||||||
|
let mut children: Vec<_> = self
|
||||||
|
.start_icon
|
||||||
|
.iter_mut()
|
||||||
|
.chain(self.end_element.iter_mut())
|
||||||
|
.map(iced_core::Element::as_widget_mut)
|
||||||
|
.collect();
|
||||||
|
tree.diff_children(children.as_mut_slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn children(&self) -> Vec<Tree> {
|
||||||
|
self.start_icon
|
||||||
|
.iter()
|
||||||
|
.chain(self.end_element.iter())
|
||||||
|
.map(|icon| Tree::new(icon))
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn width(&self) -> Length {
|
fn width(&self) -> Length {
|
||||||
|
|
@ -435,12 +467,13 @@ where
|
||||||
self.width,
|
self.width,
|
||||||
self.padding,
|
self.padding,
|
||||||
self.size,
|
self.size,
|
||||||
self.icon.as_ref(),
|
self.start_icon.as_ref(),
|
||||||
|
self.end_element.as_ref(),
|
||||||
self.line_height,
|
self.line_height,
|
||||||
self.label,
|
self.label,
|
||||||
self.helper_text,
|
self.helper_text,
|
||||||
self.helper_size,
|
self.helper_size,
|
||||||
self.helper_line_height
|
self.helper_line_height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -467,11 +500,48 @@ where
|
||||||
renderer: &crate::Renderer,
|
renderer: &crate::Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
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))
|
||||||
|
{
|
||||||
|
if matches!(
|
||||||
|
start_icon.as_widget_mut().on_event(
|
||||||
|
tree,
|
||||||
|
event.clone(),
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
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 matches!(
|
||||||
|
end_icon.as_widget_mut().on_event(
|
||||||
|
tree,
|
||||||
|
event.clone(),
|
||||||
|
layout,
|
||||||
|
cursor_position,
|
||||||
|
renderer,
|
||||||
|
clipboard,
|
||||||
|
shell,
|
||||||
|
viewport
|
||||||
|
),
|
||||||
|
event::Status::Captured
|
||||||
|
) {
|
||||||
|
return event::Status::Captured;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update(
|
update(
|
||||||
event,
|
event,
|
||||||
layout,
|
self.text_layout(layout),
|
||||||
cursor_position,
|
cursor_position,
|
||||||
renderer,
|
renderer,
|
||||||
clipboard,
|
clipboard,
|
||||||
|
|
@ -500,21 +570,22 @@ where
|
||||||
_style: &renderer::Style,
|
_style: &renderer::Style,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: mouse::Cursor,
|
cursor_position: mouse::Cursor,
|
||||||
_viewport: &Rectangle,
|
viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
draw(
|
draw(
|
||||||
renderer,
|
renderer,
|
||||||
theme,
|
theme,
|
||||||
layout,
|
layout,
|
||||||
cursor_position,
|
cursor_position,
|
||||||
tree.state.downcast_ref::<State>(),
|
tree,
|
||||||
&self.value,
|
&self.value,
|
||||||
&self.placeholder,
|
&self.placeholder,
|
||||||
self.size,
|
self.size,
|
||||||
self.font,
|
self.font,
|
||||||
self.on_input.is_none(),
|
self.on_input.is_none(),
|
||||||
self.is_secure,
|
self.is_secure,
|
||||||
self.icon.as_ref(),
|
self.start_icon.as_ref(),
|
||||||
|
self.end_element.as_ref(),
|
||||||
&self.style,
|
&self.style,
|
||||||
self.dnd_icon,
|
self.dnd_icon,
|
||||||
self.line_height,
|
self.line_height,
|
||||||
|
|
@ -523,6 +594,7 @@ where
|
||||||
self.helper_text,
|
self.helper_text,
|
||||||
self.helper_size,
|
self.helper_size,
|
||||||
self.helper_line_height,
|
self.helper_line_height,
|
||||||
|
viewport,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -548,30 +620,6 @@ 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`].
|
/// Produces a [`Command`] that focuses the [`TextInput`] with the given [`Id`].
|
||||||
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
|
pub fn focus<Message: 'static>(id: Id) -> Command<Message> {
|
||||||
Command::widget(operation::focusable::focus(id))
|
Command::widget(operation::focusable::focus(id))
|
||||||
|
|
@ -603,22 +651,21 @@ pub fn select_all<Message: 'static>(id: Id) -> Command<Message> {
|
||||||
/// Computes the layout of a [`TextInput`].
|
/// Computes the layout of a [`TextInput`].
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn layout<Renderer>(
|
#[allow(clippy::too_many_lines)]
|
||||||
renderer: &Renderer,
|
pub fn layout<Message>(
|
||||||
|
renderer: &crate::Renderer,
|
||||||
limits: &layout::Limits,
|
limits: &layout::Limits,
|
||||||
width: Length,
|
width: Length,
|
||||||
padding: Padding,
|
padding: Padding,
|
||||||
size: Option<f32>,
|
size: Option<f32>,
|
||||||
icon: Option<&Icon<Renderer::Font>>,
|
start_icon: Option<&Element<'_, Message, crate::Renderer>>,
|
||||||
|
end_icon: Option<&Element<'_, Message, crate::Renderer>>,
|
||||||
line_height: text::LineHeight,
|
line_height: text::LineHeight,
|
||||||
label: Option<&str>,
|
label: Option<&str>,
|
||||||
helper_text: Option<&str>,
|
helper_text: Option<&str>,
|
||||||
helper_text_size: f32,
|
helper_text_size: f32,
|
||||||
helper_text_line_height: text::LineHeight,
|
helper_text_line_height: text::LineHeight,
|
||||||
) -> layout::Node
|
) -> layout::Node {
|
||||||
where
|
|
||||||
Renderer: text::Renderer,
|
|
||||||
{
|
|
||||||
let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
|
let spacing = THEME.with(|t| t.borrow().cosmic().space_xxs());
|
||||||
let mut nodes = Vec::with_capacity(3);
|
let mut nodes = Vec::with_capacity(3);
|
||||||
|
|
||||||
|
|
@ -644,42 +691,62 @@ where
|
||||||
let text_size = size.unwrap_or_else(|| renderer.default_size());
|
let text_size = size.unwrap_or_else(|| renderer.default_size());
|
||||||
let padding = padding.fit(Size::ZERO, limits.max());
|
let padding = padding.fit(Size::ZERO, limits.max());
|
||||||
|
|
||||||
let helper_pos = if let Some(icon) = icon {
|
let helper_pos = if start_icon.is_some() || end_icon.is_some() {
|
||||||
let limits = limits.width(width).pad(padding).height(text_size * 1.2);
|
// TODO configurable icon spacing, maybe via appearance
|
||||||
let text_bounds = limits.resolve(Size::ZERO);
|
let mut height = text_size * 1.2;
|
||||||
|
let icon_spacing = 8.0;
|
||||||
let icon_width = renderer.measure_width(
|
let (start_icon_width, mut start_icon) = if let Some(icon) = start_icon.as_ref() {
|
||||||
&icon.code_point.to_string(),
|
let icon_node = icon.layout(
|
||||||
icon.size.unwrap_or_else(|| renderer.default_size()),
|
renderer,
|
||||||
icon.font,
|
&Limits::NONE
|
||||||
text::Shaping::Advanced,
|
.width(icon.as_widget().width())
|
||||||
|
.height(icon.as_widget().height()),
|
||||||
);
|
);
|
||||||
|
height = height.max(icon_node.bounds().height);
|
||||||
let mut text_node =
|
(icon_node.bounds().width + icon_spacing, Some(icon_node))
|
||||||
layout::Node::new(text_bounds - Size::new(icon_width + icon.spacing, 0.0));
|
} else {
|
||||||
|
(0.0, None)
|
||||||
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,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let node = layout::Node::with_children(text_bounds.pad(padding), vec![text_node, icon_node]).translate(text_pos);
|
let (end_icon_width, mut end_icon) = if let Some(icon) = end_icon.as_ref() {
|
||||||
|
let icon_node = icon.layout(
|
||||||
|
renderer,
|
||||||
|
&Limits::NONE
|
||||||
|
.width(icon.as_widget().width())
|
||||||
|
.height(icon.as_widget().height()),
|
||||||
|
);
|
||||||
|
height = height.max(icon_node.bounds().height);
|
||||||
|
(icon_node.bounds().width + icon_spacing, Some(icon_node))
|
||||||
|
} else {
|
||||||
|
(0.0, None)
|
||||||
|
};
|
||||||
|
let limits = limits.width(width).pad(padding).height(height);
|
||||||
|
|
||||||
|
let text_bounds = 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));
|
||||||
|
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));
|
||||||
|
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,
|
||||||
|
));
|
||||||
|
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);
|
let y_pos = node.bounds().y + node.bounds().height + f32::from(spacing);
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
|
|
||||||
|
|
@ -691,7 +758,8 @@ where
|
||||||
let mut text = layout::Node::new(text_bounds);
|
let mut text = layout::Node::new(text_bounds);
|
||||||
text.move_to(Point::new(padding.left, padding.top));
|
text.move_to(Point::new(padding.left, padding.top));
|
||||||
|
|
||||||
let node = layout::Node::with_children(text_bounds.pad(padding), vec![text]).translate(text_pos);
|
let node =
|
||||||
|
layout::Node::with_children(text_bounds.pad(padding), vec![text]).translate(text_pos);
|
||||||
let y_pos = node.bounds().y + node.bounds().height + f32::from(spacing);
|
let y_pos = node.bounds().y + node.bounds().height + f32::from(spacing);
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
|
|
||||||
|
|
@ -699,7 +767,10 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(helper_text) = helper_text {
|
if let Some(helper_text) = helper_text {
|
||||||
let limits = limits.width(width).pad(padding).height(helper_text_size * 1.2);
|
let limits = limits
|
||||||
|
.width(width)
|
||||||
|
.pad(padding)
|
||||||
|
.height(helper_text_size * 1.2);
|
||||||
let text_bounds = limits.resolve(Size::ZERO);
|
let text_bounds = limits.resolve(Size::ZERO);
|
||||||
|
|
||||||
let helper_text_size = renderer.measure(
|
let helper_text_size = renderer.measure(
|
||||||
|
|
@ -715,7 +786,10 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut size = nodes.iter().fold(Size::ZERO, |size, node| {
|
let mut size = nodes.iter().fold(Size::ZERO, |size, node| {
|
||||||
Size::new(size.width.max(node.bounds().width), size.height + node.bounds().height)
|
Size::new(
|
||||||
|
size.width.max(node.bounds().width),
|
||||||
|
size.height + node.bounds().height,
|
||||||
|
)
|
||||||
});
|
});
|
||||||
size.height += (nodes.len() - 1) as f32 * f32::from(spacing);
|
size.height += (nodes.len() - 1) as f32 * f32::from(spacing);
|
||||||
let limits = limits.width(width).pad(padding).height(size.height);
|
let limits = limits.width(width).pad(padding).height(size.height);
|
||||||
|
|
@ -732,7 +806,7 @@ where
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
pub fn update<'a, Message, Renderer>(
|
pub fn update<'a, Message, Renderer>(
|
||||||
event: Event,
|
event: Event,
|
||||||
layout: Layout<'_>,
|
text_layout: Layout<'_>,
|
||||||
cursor_position: mouse::Cursor,
|
cursor_position: mouse::Cursor,
|
||||||
renderer: &Renderer,
|
renderer: &Renderer,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
|
|
@ -759,7 +833,7 @@ where
|
||||||
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
| Event::Touch(touch::Event::FingerPressed { .. }) => {
|
||||||
let state = state();
|
let state = state();
|
||||||
let is_clicked = cursor_position.is_over(layout.bounds()) && on_input.is_some();
|
let is_clicked = cursor_position.is_over(text_layout.bounds()) && on_input.is_some();
|
||||||
|
|
||||||
state.is_focused = if is_clicked {
|
state.is_focused = if is_clicked {
|
||||||
state.is_focused.or_else(|| {
|
state.is_focused.or_else(|| {
|
||||||
|
|
@ -779,7 +853,6 @@ where
|
||||||
let Some(pos) = cursor_position.position() else {
|
let Some(pos) = cursor_position.position() else {
|
||||||
return event::Status::Ignored;
|
return event::Status::Ignored;
|
||||||
};
|
};
|
||||||
let text_layout = layout.children().next().unwrap();
|
|
||||||
let target = pos.x - text_layout.bounds().x;
|
let target = pos.x - text_layout.bounds().x;
|
||||||
|
|
||||||
let click = mouse::Click::new(pos, state.last_click);
|
let click = mouse::Click::new(pos, state.last_click);
|
||||||
|
|
@ -807,7 +880,6 @@ where
|
||||||
surface_ids,
|
surface_ids,
|
||||||
on_input,
|
on_input,
|
||||||
) {
|
) {
|
||||||
let text_bounds = layout.children().next().unwrap().bounds();
|
|
||||||
let actual_size = size.unwrap_or_else(|| renderer.default_size());
|
let actual_size = size.unwrap_or_else(|| renderer.default_size());
|
||||||
|
|
||||||
let left = start.min(end);
|
let left = start.min(end);
|
||||||
|
|
@ -815,7 +887,7 @@ where
|
||||||
|
|
||||||
let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
|
let (left_position, _left_offset) = measure_cursor_and_scroll_offset(
|
||||||
renderer,
|
renderer,
|
||||||
text_bounds,
|
text_layout.bounds(),
|
||||||
value,
|
value,
|
||||||
actual_size,
|
actual_size,
|
||||||
left,
|
left,
|
||||||
|
|
@ -824,7 +896,7 @@ where
|
||||||
|
|
||||||
let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
|
let (right_position, _right_offset) = measure_cursor_and_scroll_offset(
|
||||||
renderer,
|
renderer,
|
||||||
text_bounds,
|
text_layout.bounds(),
|
||||||
value,
|
value,
|
||||||
actual_size,
|
actual_size,
|
||||||
right,
|
right,
|
||||||
|
|
@ -833,10 +905,10 @@ where
|
||||||
|
|
||||||
let width = right_position - left_position;
|
let width = right_position - left_position;
|
||||||
let selection_bounds = Rectangle {
|
let selection_bounds = Rectangle {
|
||||||
x: text_bounds.x + left_position,
|
x: text_layout.bounds().x + left_position,
|
||||||
y: text_bounds.y,
|
y: text_layout.bounds().y,
|
||||||
width,
|
width,
|
||||||
height: text_bounds.height,
|
height: text_layout.bounds().height,
|
||||||
};
|
};
|
||||||
|
|
||||||
if cursor_position.is_over(selection_bounds) {
|
if cursor_position.is_over(selection_bounds) {
|
||||||
|
|
@ -969,7 +1041,6 @@ where
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
if matches!(state.dragging_state, Some(DraggingState::Selection)) {
|
if matches!(state.dragging_state, Some(DraggingState::Selection)) {
|
||||||
let text_layout = layout.children().next().unwrap();
|
|
||||||
let target = position.x - text_layout.bounds().x;
|
let target = position.x - text_layout.bounds().x;
|
||||||
|
|
||||||
let value: Value = if is_secure {
|
let value: Value = if is_secure {
|
||||||
|
|
@ -1255,8 +1326,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = state();
|
let state = state();
|
||||||
let bounds = layout.bounds();
|
let is_clicked = text_layout.bounds().contains(Point {
|
||||||
let is_clicked = bounds.contains(Point {
|
|
||||||
x: x as f32,
|
x: x as f32,
|
||||||
y: y as f32,
|
y: y as f32,
|
||||||
});
|
});
|
||||||
|
|
@ -1284,7 +1354,6 @@ where
|
||||||
accepted: DndAction::Move.union(DndAction::Copy),
|
accepted: DndAction::Move.union(DndAction::Copy),
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
let text_layout = layout.children().next().unwrap();
|
|
||||||
let target = x as f32 - text_layout.bounds().x;
|
let target = x as f32 - text_layout.bounds().x;
|
||||||
state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), DndAction::None);
|
state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), DndAction::None);
|
||||||
// existing logic for setting the selection
|
// existing logic for setting the selection
|
||||||
|
|
@ -1323,8 +1392,7 @@ where
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = state();
|
let state = state();
|
||||||
let bounds = layout.bounds();
|
let is_clicked = text_layout.bounds().contains(Point {
|
||||||
let is_clicked = bounds.contains(Point {
|
|
||||||
x: x as f32,
|
x: x as f32,
|
||||||
y: y as f32,
|
y: y as f32,
|
||||||
});
|
});
|
||||||
|
|
@ -1367,7 +1435,6 @@ where
|
||||||
state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), action);
|
state.dnd_offer = DndOfferState::HandlingOffer(mime_types.clone(), action);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let text_layout = layout.children().next().unwrap();
|
|
||||||
let target = x as f32 - text_layout.bounds().x;
|
let target = x as f32 - text_layout.bounds().x;
|
||||||
// existing logic for setting the selection
|
// existing logic for setting the selection
|
||||||
let position = if target > 0.0 {
|
let position = if target > 0.0 {
|
||||||
|
|
@ -1408,10 +1475,8 @@ where
|
||||||
.iter()
|
.iter()
|
||||||
.find(|m| mime_types.contains(&(**m).to_string()))
|
.find(|m| mime_types.contains(&(**m).to_string()))
|
||||||
else {
|
else {
|
||||||
|
|
||||||
state.dnd_offer = DndOfferState::None;
|
state.dnd_offer = DndOfferState::None;
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
|
|
||||||
};
|
};
|
||||||
state.dnd_offer = DndOfferState::Dropped;
|
state.dnd_offer = DndOfferState::Dropped;
|
||||||
shell.publish(on_dnd_command_produced(Box::new(move || {
|
shell.publish(on_dnd_command_produced(Box::new(move || {
|
||||||
|
|
@ -1507,20 +1572,21 @@ where
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn draw<Renderer>(
|
pub fn draw<'a, Message>(
|
||||||
renderer: &mut Renderer,
|
renderer: &mut crate::Renderer,
|
||||||
theme: &Renderer::Theme,
|
theme: &crate::Theme,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
cursor_position: mouse::Cursor,
|
cursor_position: mouse::Cursor,
|
||||||
state: &State,
|
tree: &Tree,
|
||||||
value: &Value,
|
value: &Value,
|
||||||
placeholder: &str,
|
placeholder: &str,
|
||||||
size: Option<f32>,
|
size: Option<f32>,
|
||||||
font: Option<Renderer::Font>,
|
font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
|
||||||
is_disabled: bool,
|
is_disabled: bool,
|
||||||
is_secure: bool,
|
is_secure: bool,
|
||||||
icon: Option<&Icon<Renderer::Font>>,
|
icon: Option<&Element<'a, Message, crate::Renderer>>,
|
||||||
style: &<Renderer::Theme as StyleSheet>::Style,
|
end_element: Option<&Element<'a, Message, crate::Renderer>>,
|
||||||
|
style: &<crate::Theme as StyleSheet>::Style,
|
||||||
dnd_icon: bool,
|
dnd_icon: bool,
|
||||||
line_height: text::LineHeight,
|
line_height: text::LineHeight,
|
||||||
error: Option<&str>,
|
error: Option<&str>,
|
||||||
|
|
@ -1528,10 +1594,13 @@ pub fn draw<Renderer>(
|
||||||
helper_text: Option<&str>,
|
helper_text: Option<&str>,
|
||||||
helper_text_size: f32,
|
helper_text_size: f32,
|
||||||
helper_line_height: text::LineHeight,
|
helper_line_height: text::LineHeight,
|
||||||
) where
|
viewport: &Rectangle,
|
||||||
Renderer: text::Renderer,
|
) {
|
||||||
Renderer::Theme: StyleSheet,
|
// 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::<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);
|
||||||
|
|
||||||
|
|
@ -1556,7 +1625,6 @@ pub fn draw<Renderer>(
|
||||||
(None, layout, None)
|
(None, layout, None)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
let mut children_layout = layout.children();
|
let mut children_layout = layout.children();
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let text_bounds = children_layout.next().unwrap().bounds();
|
let text_bounds = children_layout.next().unwrap().bounds();
|
||||||
|
|
@ -1629,20 +1697,20 @@ pub fn draw<Renderer>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw the start icon in the text input
|
// draw the start icon in the text input
|
||||||
if let Some(icon) = icon {
|
if let (Some(icon), Some(tree)) = (icon, start_icon_tree) {
|
||||||
let icon_layout = children_layout.next().unwrap();
|
let icon_layout = children_layout.next().unwrap();
|
||||||
|
|
||||||
renderer.fill_text(Text {
|
icon.as_widget().draw(
|
||||||
content: &icon.code_point.to_string(),
|
tree,
|
||||||
size: icon.size.unwrap_or_else(|| renderer.default_size()),
|
renderer,
|
||||||
font: icon.font,
|
theme,
|
||||||
color: appearance.icon_color,
|
&renderer::Style {
|
||||||
bounds: icon_layout.bounds(),
|
text_color: appearance.icon_color,
|
||||||
horizontal_alignment: alignment::Horizontal::Left,
|
},
|
||||||
vertical_alignment: alignment::Vertical::Top,
|
icon_layout,
|
||||||
line_height: text::LineHeight::default(),
|
cursor_position,
|
||||||
shaping: text::Shaping::Advanced,
|
viewport,
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = value.to_string();
|
let text = value.to_string();
|
||||||
|
|
@ -1761,7 +1829,7 @@ pub fn draw<Renderer>(
|
||||||
theme.value_color(style)
|
theme.value_color(style)
|
||||||
};
|
};
|
||||||
|
|
||||||
let render = |renderer: &mut Renderer| {
|
let render = |renderer: &mut crate::Renderer| {
|
||||||
if let Some((cursor, color)) = cursor {
|
if let Some((cursor, color)) = cursor {
|
||||||
renderer.fill_quad(cursor, color);
|
renderer.fill_quad(cursor, color);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1793,6 +1861,23 @@ pub fn draw<Renderer>(
|
||||||
render(renderer);
|
render(renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
icon.as_widget().draw(
|
||||||
|
tree,
|
||||||
|
renderer,
|
||||||
|
theme,
|
||||||
|
&renderer::Style {
|
||||||
|
text_color: appearance.icon_color,
|
||||||
|
},
|
||||||
|
icon_layout,
|
||||||
|
cursor_position,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// draw the helper text if it exists
|
// draw the helper text if it exists
|
||||||
if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
|
if let (Some(helper_text_layout), Some(helper_text)) = (helper_text_layout, helper_text) {
|
||||||
renderer.fill_text(Text {
|
renderer.fill_text(Text {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue