improv(text_input): optimize, fix, and improve the text inputs
This commit is contained in:
parent
92b2756e26
commit
c538d672df
4 changed files with 291 additions and 209 deletions
|
|
@ -27,6 +27,7 @@ pub enum State {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Cursor {
|
impl Default for Cursor {
|
||||||
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
state: State::Index(0),
|
state: State::Index(0),
|
||||||
|
|
@ -37,6 +38,7 @@ impl Default for Cursor {
|
||||||
impl Cursor {
|
impl Cursor {
|
||||||
/// Returns the [`State`] of the [`Cursor`].
|
/// Returns the [`State`] of the [`Cursor`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[inline(never)]
|
||||||
pub fn state(&self, value: &Value) -> State {
|
pub fn state(&self, value: &Value) -> State {
|
||||||
match self.state {
|
match self.state {
|
||||||
State::Index(index) => State::Index(index.min(value.len())),
|
State::Index(index) => State::Index(index.min(value.len())),
|
||||||
|
|
@ -57,6 +59,7 @@ impl Cursor {
|
||||||
///
|
///
|
||||||
/// `start` is guaranteed to be <= than `end`.
|
/// `start` is guaranteed to be <= than `end`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
pub fn selection(&self, value: &Value) -> Option<(usize, usize)> {
|
pub fn selection(&self, value: &Value) -> Option<(usize, usize)> {
|
||||||
match self.state(value) {
|
match self.state(value) {
|
||||||
State::Selection { start, end } => Some((start.min(end), start.max(end))),
|
State::Selection { start, end } => Some((start.min(end), start.max(end))),
|
||||||
|
|
@ -64,18 +67,22 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn move_to(&mut self, position: usize) {
|
pub(crate) fn move_to(&mut self, position: usize) {
|
||||||
self.state = State::Index(position);
|
self.state = State::Index(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn move_right(&mut self, value: &Value) {
|
pub(crate) fn move_right(&mut self, value: &Value) {
|
||||||
self.move_right_by_amount(value, 1);
|
self.move_right_by_amount(value, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn move_right_by_words(&mut self, value: &Value) {
|
pub(crate) fn move_right_by_words(&mut self, value: &Value) {
|
||||||
self.move_to(value.next_end_of_word(self.right(value)));
|
self.move_to(value.next_end_of_word(self.right(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn move_right_by_amount(&mut self, value: &Value, amount: usize) {
|
pub(crate) fn move_right_by_amount(&mut self, value: &Value, amount: usize) {
|
||||||
match self.state(value) {
|
match self.state(value) {
|
||||||
State::Index(index) => self.move_to(index.saturating_add(amount).min(value.len())),
|
State::Index(index) => self.move_to(index.saturating_add(amount).min(value.len())),
|
||||||
|
|
@ -83,6 +90,7 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn move_left(&mut self, value: &Value) {
|
pub(crate) fn move_left(&mut self, value: &Value) {
|
||||||
match self.state(value) {
|
match self.state(value) {
|
||||||
State::Index(index) if index > 0 => self.move_to(index - 1),
|
State::Index(index) if index > 0 => self.move_to(index - 1),
|
||||||
|
|
@ -91,18 +99,21 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn move_left_by_words(&mut self, value: &Value) {
|
pub(crate) fn move_left_by_words(&mut self, value: &Value) {
|
||||||
self.move_to(value.previous_start_of_word(self.left(value)));
|
self.move_to(value.previous_start_of_word(self.left(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn select_range(&mut self, start: usize, end: usize) {
|
pub(crate) fn select_range(&mut self, start: usize, end: usize) {
|
||||||
if start == end {
|
self.state = if start == end {
|
||||||
self.state = State::Index(start);
|
State::Index(start)
|
||||||
} else {
|
} else {
|
||||||
self.state = State::Selection { start, end };
|
State::Selection { start, end }
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn select_left(&mut self, value: &Value) {
|
pub(crate) fn select_left(&mut self, value: &Value) {
|
||||||
match self.state(value) {
|
match self.state(value) {
|
||||||
State::Index(index) if index > 0 => self.select_range(index, index - 1),
|
State::Index(index) if index > 0 => self.select_range(index, index - 1),
|
||||||
|
|
@ -111,6 +122,7 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn select_right(&mut self, value: &Value) {
|
pub(crate) fn select_right(&mut self, value: &Value) {
|
||||||
match self.state(value) {
|
match self.state(value) {
|
||||||
State::Index(index) if index < value.len() => self.select_range(index, index + 1),
|
State::Index(index) if index < value.len() => self.select_range(index, index + 1),
|
||||||
|
|
@ -121,6 +133,7 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn select_left_by_words(&mut self, value: &Value) {
|
pub(crate) fn select_left_by_words(&mut self, value: &Value) {
|
||||||
match self.state(value) {
|
match self.state(value) {
|
||||||
State::Index(index) => self.select_range(index, value.previous_start_of_word(index)),
|
State::Index(index) => self.select_range(index, value.previous_start_of_word(index)),
|
||||||
|
|
@ -130,6 +143,7 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn select_right_by_words(&mut self, value: &Value) {
|
pub(crate) fn select_right_by_words(&mut self, value: &Value) {
|
||||||
match self.state(value) {
|
match self.state(value) {
|
||||||
State::Index(index) => self.select_range(index, value.next_end_of_word(index)),
|
State::Index(index) => self.select_range(index, value.next_end_of_word(index)),
|
||||||
|
|
@ -139,10 +153,12 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn select_all(&mut self, value: &Value) {
|
pub(crate) fn select_all(&mut self, value: &Value) {
|
||||||
self.select_range(0, value.len());
|
self.select_range(0, value.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn start(&self, value: &Value) -> usize {
|
pub(crate) fn start(&self, value: &Value) -> usize {
|
||||||
let start = match self.state {
|
let start = match self.state {
|
||||||
State::Index(index) => index,
|
State::Index(index) => index,
|
||||||
|
|
@ -152,6 +168,7 @@ impl Cursor {
|
||||||
start.min(value.len())
|
start.min(value.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub(crate) fn end(&self, value: &Value) -> usize {
|
pub(crate) fn end(&self, value: &Value) -> usize {
|
||||||
let end = match self.state {
|
let end = match self.state {
|
||||||
State::Index(index) => index,
|
State::Index(index) => index,
|
||||||
|
|
@ -161,6 +178,7 @@ impl Cursor {
|
||||||
end.min(value.len())
|
end.min(value.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn left(&self, value: &Value) -> usize {
|
fn left(&self, value: &Value) -> usize {
|
||||||
match self.state(value) {
|
match self.state(value) {
|
||||||
State::Index(index) => index,
|
State::Index(index) => index,
|
||||||
|
|
@ -168,6 +186,7 @@ impl Cursor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn right(&self, value: &Value) -> usize {
|
fn right(&self, value: &Value) -> usize {
|
||||||
match self.state(value) {
|
match self.state(value) {
|
||||||
State::Index(index) => index,
|
State::Index(index) => index,
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,13 @@ pub struct Editor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Editor<'a> {
|
impl<'a> Editor<'a> {
|
||||||
|
#[inline]
|
||||||
pub fn new(value: &'a mut Value, cursor: &'a mut Cursor) -> Editor<'a> {
|
pub fn new(value: &'a mut Value, cursor: &'a mut Cursor) -> Editor<'a> {
|
||||||
Editor { value, cursor }
|
Editor { value, cursor }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
pub fn contents(&self) -> String {
|
pub fn contents(&self) -> String {
|
||||||
self.value.to_string()
|
self.value.to_string()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,9 @@ use super::style::StyleSheet;
|
||||||
pub use super::value::Value;
|
pub use super::value::Value;
|
||||||
|
|
||||||
use apply::Apply;
|
use apply::Apply;
|
||||||
|
use iced::Limits;
|
||||||
use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent};
|
use iced::clipboard::dnd::{DndAction, DndEvent, OfferEvent, SourceEvent};
|
||||||
use iced::clipboard::mime::AsMimeTypes;
|
use iced::clipboard::mime::AsMimeTypes;
|
||||||
use iced::Limits;
|
|
||||||
use iced_core::event::{self, Event};
|
use iced_core::event::{self, Event};
|
||||||
use iced_core::mouse::{self, click};
|
use iced_core::mouse::{self, click};
|
||||||
use iced_core::overlay::Group;
|
use iced_core::overlay::Group;
|
||||||
|
|
@ -28,18 +28,18 @@ use iced_core::renderer::{self, Renderer as CoreRenderer};
|
||||||
use iced_core::text::{self, Paragraph, Renderer, Text};
|
use iced_core::text::{self, Paragraph, Renderer, Text};
|
||||||
use iced_core::time::{Duration, Instant};
|
use iced_core::time::{Duration, Instant};
|
||||||
use iced_core::touch;
|
use iced_core::touch;
|
||||||
|
use iced_core::widget::Id;
|
||||||
use iced_core::widget::operation::{self, Operation};
|
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::window;
|
use iced_core::window;
|
||||||
use iced_core::{alignment, Background};
|
use iced_core::{Background, alignment};
|
||||||
use iced_core::{keyboard, Border, Shadow};
|
use iced_core::{Border, Shadow, keyboard};
|
||||||
use iced_core::{layout, overlay};
|
|
||||||
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,
|
||||||
};
|
};
|
||||||
use iced_runtime::{task, Action, Task};
|
use iced_core::{layout, overlay};
|
||||||
|
use iced_runtime::{Action, Task, task};
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
// Prevents two inputs from being focused at the same time.
|
// Prevents two inputs from being focused at the same time.
|
||||||
|
|
@ -59,7 +59,7 @@ where
|
||||||
TextInput::new(placeholder, value)
|
TextInput::new(placeholder, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A text label whiich can transform into a text input on activation.
|
/// A text label which can transform into a text input on activation.
|
||||||
pub fn editable_input<'a, Message: Clone + 'static>(
|
pub fn editable_input<'a, Message: Clone + 'static>(
|
||||||
placeholder: impl Into<Cow<'a, str>>,
|
placeholder: impl Into<Cow<'a, str>>,
|
||||||
text: impl Into<Cow<'a, str>>,
|
text: impl Into<Cow<'a, str>>,
|
||||||
|
|
@ -182,7 +182,7 @@ pub struct TextInput<'a, Message> {
|
||||||
placeholder: Cow<'a, str>,
|
placeholder: Cow<'a, str>,
|
||||||
value: Value,
|
value: Value,
|
||||||
is_secure: bool,
|
is_secure: bool,
|
||||||
is_editable: bool,
|
is_editable_variant: bool,
|
||||||
is_read_only: bool,
|
is_read_only: bool,
|
||||||
select_on_focus: bool,
|
select_on_focus: bool,
|
||||||
font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
|
font: Option<<crate::Renderer as iced_core::text::Renderer>::Font>,
|
||||||
|
|
@ -193,8 +193,11 @@ pub struct TextInput<'a, Message> {
|
||||||
label: Option<Cow<'a, str>>,
|
label: Option<Cow<'a, str>>,
|
||||||
helper_text: Option<Cow<'a, str>>,
|
helper_text: Option<Cow<'a, str>>,
|
||||||
error: Option<Cow<'a, str>>,
|
error: Option<Cow<'a, str>>,
|
||||||
|
on_focus: Option<Message>,
|
||||||
|
on_unfocus: Option<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_tab: Option<Message>,
|
||||||
on_submit: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
on_submit: Option<Box<dyn Fn(String) -> Message + 'a>>,
|
||||||
on_toggle_edit: Option<Box<dyn Fn(bool) -> Message + 'a>>,
|
on_toggle_edit: Option<Box<dyn Fn(bool) -> Message + 'a>>,
|
||||||
leading_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
|
leading_icon: Option<Element<'a, Message, crate::Theme, crate::Renderer>>,
|
||||||
|
|
@ -228,7 +231,7 @@ where
|
||||||
placeholder: placeholder.into(),
|
placeholder: placeholder.into(),
|
||||||
value: Value::new(v.as_ref()),
|
value: Value::new(v.as_ref()),
|
||||||
is_secure: false,
|
is_secure: false,
|
||||||
is_editable: false,
|
is_editable_variant: false,
|
||||||
is_read_only: false,
|
is_read_only: false,
|
||||||
select_on_focus: false,
|
select_on_focus: false,
|
||||||
font: None,
|
font: None,
|
||||||
|
|
@ -237,9 +240,12 @@ where
|
||||||
size: None,
|
size: None,
|
||||||
helper_size: 10.0,
|
helper_size: 10.0,
|
||||||
helper_line_height: text::LineHeight::Absolute(14.0.into()),
|
helper_line_height: text::LineHeight::Absolute(14.0.into()),
|
||||||
|
on_focus: None,
|
||||||
|
on_unfocus: None,
|
||||||
on_input: None,
|
on_input: None,
|
||||||
on_paste: None,
|
on_paste: None,
|
||||||
on_submit: None,
|
on_submit: None,
|
||||||
|
on_tab: None,
|
||||||
on_toggle_edit: None,
|
on_toggle_edit: None,
|
||||||
leading_icon: None,
|
leading_icon: None,
|
||||||
trailing_icon: None,
|
trailing_icon: None,
|
||||||
|
|
@ -256,6 +262,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn dnd_id(&self) -> u128 {
|
fn dnd_id(&self) -> u128 {
|
||||||
match &self.id.0 {
|
match &self.id.0 {
|
||||||
iced_core::id::Internal::Custom(id, _) | iced_core::id::Internal::Unique(id) => {
|
iced_core::id::Internal::Custom(id, _) | iced_core::id::Internal::Unique(id) => {
|
||||||
|
|
@ -267,7 +274,8 @@ where
|
||||||
|
|
||||||
/// Sets the input to be always active.
|
/// Sets the input to be always active.
|
||||||
/// This makes it behave as if it was always focused.
|
/// This makes it behave as if it was always focused.
|
||||||
pub fn always_active(mut self) -> Self {
|
#[inline]
|
||||||
|
pub const fn always_active(mut self) -> Self {
|
||||||
self.always_active = true;
|
self.always_active = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -285,6 +293,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Id`] of the [`TextInput`].
|
/// Sets the [`Id`] of the [`TextInput`].
|
||||||
|
#[inline]
|
||||||
pub fn id(mut self, id: Id) -> Self {
|
pub fn id(mut self, id: Id) -> Self {
|
||||||
self.id = id;
|
self.id = id;
|
||||||
self
|
self
|
||||||
|
|
@ -303,66 +312,85 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the [`TextInput`] into a secure password input.
|
/// Converts the [`TextInput`] into a secure password input.
|
||||||
pub fn password(mut self) -> Self {
|
#[inline]
|
||||||
|
pub const fn password(mut self) -> Self {
|
||||||
self.is_secure = true;
|
self.is_secure = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn editable(mut self) -> Self {
|
/// Applies behaviors unique to the `editable_input` variable.
|
||||||
self.is_editable = true;
|
#[inline]
|
||||||
|
pub(crate) const fn editable(mut self) -> Self {
|
||||||
|
self.is_editable_variant = true;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn editing(mut self, enable: bool) -> Self {
|
#[inline]
|
||||||
|
pub const fn editing(mut self, enable: bool) -> Self {
|
||||||
self.is_read_only = !enable;
|
self.is_read_only = !enable;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selects all text when the text input is focused
|
/// Selects all text when the text input is focused
|
||||||
pub fn select_on_focus(mut self, select_on_focus: bool) -> Self {
|
#[inline]
|
||||||
|
pub const fn select_on_focus(mut self, select_on_focus: bool) -> Self {
|
||||||
self.select_on_focus = select_on_focus;
|
self.select_on_focus = select_on_focus;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emits a message when an unfocused text input has been focused by click.
|
||||||
|
///
|
||||||
|
/// This will not trigger if the input was focused externally by the application.
|
||||||
|
#[inline]
|
||||||
|
pub fn on_focus(mut self, on_focus: Message) -> Self {
|
||||||
|
self.on_focus = Some(on_focus);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits a message when a focused text input has been unfocused via the Tab or Esc key.
|
||||||
|
///
|
||||||
|
/// This will not trigger if the input was unfocused externally by the application.
|
||||||
|
#[inline]
|
||||||
|
pub fn on_unfocus(mut self, on_unfocus: Message) -> Self {
|
||||||
|
self.on_unfocus = Some(on_unfocus);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the message that should be produced when some text is typed into
|
/// Sets the message that should be produced when some text is typed into
|
||||||
/// the [`TextInput`].
|
/// the [`TextInput`].
|
||||||
///
|
///
|
||||||
/// If this method is not called, the [`TextInput`] will be disabled.
|
/// If this method is not called, the [`TextInput`] will be disabled.
|
||||||
pub fn on_input<F>(mut self, callback: F) -> Self
|
pub fn on_input(mut self, callback: impl Fn(String) -> Message + 'a) -> Self {
|
||||||
where
|
|
||||||
F: 'a + Fn(String) -> Message,
|
|
||||||
{
|
|
||||||
self.on_input = Some(Box::new(callback));
|
self.on_input = Some(Box::new(callback));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the message that should be produced when the [`TextInput`] is
|
/// Emits a message when a focused text input receives the Enter/Return key.
|
||||||
/// focused and the enter key is pressed.
|
pub fn on_submit(mut self, callback: impl Fn(String) -> Message + 'a) -> Self {
|
||||||
pub fn on_submit<F>(self, callback: F) -> Self
|
self.on_submit = Some(Box::new(callback));
|
||||||
where
|
|
||||||
F: 'a + Fn(String) -> Message,
|
|
||||||
{
|
|
||||||
self.on_submit_maybe(Some(Box::new(callback)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Maybe sets the message that should be produced when the [`TextInput`] is
|
|
||||||
/// focused and the enter key is pressed.
|
|
||||||
pub fn on_submit_maybe<F>(mut self, callback: Option<F>) -> Self
|
|
||||||
where
|
|
||||||
F: 'a + Fn(String) -> Message,
|
|
||||||
{
|
|
||||||
if let Some(callback) = callback {
|
|
||||||
self.on_submit = Some(Box::new(callback));
|
|
||||||
} else {
|
|
||||||
self.on_submit = None;
|
|
||||||
}
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_toggle_edit<F>(mut self, callback: F) -> Self
|
/// Optionally emits a message when a focused text input receives the Enter/Return key.
|
||||||
where
|
pub fn on_submit_maybe(self, callback: Option<impl Fn(String) -> Message + 'a>) -> Self {
|
||||||
F: 'a + Fn(bool) -> Message,
|
if let Some(callback) = callback {
|
||||||
{
|
self.on_submit(callback)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits a message when the Tab key has been captured, which prevents focus from changing.
|
||||||
|
///
|
||||||
|
/// If you do no want to capture the Tab key, use [`TextInput::on_unfocus`] instead.
|
||||||
|
#[inline]
|
||||||
|
pub fn on_tab(mut self, on_tab: Message) -> Self {
|
||||||
|
self.on_tab = Some(on_tab);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emits a message when the editable state of the input changes.
|
||||||
|
pub fn on_toggle_edit(mut self, callback: impl Fn(bool) -> Message + 'a) -> Self {
|
||||||
self.on_toggle_edit = Some(Box::new(callback));
|
self.on_toggle_edit = Some(Box::new(callback));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -377,12 +405,17 @@ where
|
||||||
/// Sets the [`Font`] of the [`TextInput`].
|
/// Sets the [`Font`] of the [`TextInput`].
|
||||||
///
|
///
|
||||||
/// [`Font`]: text::Renderer::Font
|
/// [`Font`]: text::Renderer::Font
|
||||||
pub fn font(mut self, font: <crate::Renderer as iced_core::text::Renderer>::Font) -> Self {
|
#[inline]
|
||||||
|
pub const fn font(
|
||||||
|
mut self,
|
||||||
|
font: <crate::Renderer as iced_core::text::Renderer>::Font,
|
||||||
|
) -> Self {
|
||||||
self.font = Some(font);
|
self.font = Some(font);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the start [`Icon`] of the [`TextInput`].
|
/// Sets the start [`Icon`] of the [`TextInput`].
|
||||||
|
#[inline]
|
||||||
pub fn leading_icon(
|
pub fn leading_icon(
|
||||||
mut self,
|
mut self,
|
||||||
icon: Element<'a, Message, crate::Theme, crate::Renderer>,
|
icon: Element<'a, Message, crate::Theme, crate::Renderer>,
|
||||||
|
|
@ -392,6 +425,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the end [`Icon`] of the [`TextInput`].
|
/// Sets the end [`Icon`] of the [`TextInput`].
|
||||||
|
#[inline]
|
||||||
pub fn trailing_icon(
|
pub fn trailing_icon(
|
||||||
mut self,
|
mut self,
|
||||||
icon: Element<'a, Message, crate::Theme, crate::Renderer>,
|
icon: Element<'a, Message, crate::Theme, crate::Renderer>,
|
||||||
|
|
@ -407,7 +441,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the [`Padding`] of the [`TextInput`].
|
/// Sets the [`Padding`] of the [`TextInput`].
|
||||||
pub fn padding<P: Into<Padding>>(mut self, padding: P) -> Self {
|
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||||
self.padding = padding.into();
|
self.padding = padding.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -425,8 +459,9 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the text input to manage its input value or not
|
/// Sets the text input to manage its input value or not
|
||||||
pub fn manage_value(mut self, manage_value: bool) -> Self {
|
#[inline]
|
||||||
self.manage_value = true;
|
pub const fn manage_value(mut self, manage_value: bool) -> Self {
|
||||||
|
self.manage_value = manage_value;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,6 +470,7 @@ where
|
||||||
///
|
///
|
||||||
/// [`Renderer`]: text::Renderer
|
/// [`Renderer`]: text::Renderer
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[inline]
|
||||||
pub fn draw(
|
pub fn draw(
|
||||||
&self,
|
&self,
|
||||||
tree: &Tree,
|
tree: &Tree,
|
||||||
|
|
@ -484,13 +520,15 @@ where
|
||||||
/// Sets the window id of the [`TextInput`] and the window id of the drag icon.
|
/// Sets the window id of the [`TextInput`] and the window id of the drag icon.
|
||||||
/// Both ids are required to be unique.
|
/// Both ids are required to be unique.
|
||||||
/// This is required for the dnd to work.
|
/// This is required for the dnd to work.
|
||||||
pub fn surface_ids(mut self, window_id: (window::Id, window::Id)) -> Self {
|
#[inline]
|
||||||
|
pub const fn surface_ids(mut self, window_id: (window::Id, window::Id)) -> Self {
|
||||||
self.surface_ids = Some(window_id);
|
self.surface_ids = Some(window_id);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the mode of this [`TextInput`] to be a drag and drop icon.
|
/// Sets the mode of this [`TextInput`] to be a drag and drop icon.
|
||||||
pub fn dnd_icon(mut self, dnd_icon: bool) -> Self {
|
#[inline]
|
||||||
|
pub const fn dnd_icon(mut self, dnd_icon: bool) -> Self {
|
||||||
self.dnd_icon = dnd_icon;
|
self.dnd_icon = dnd_icon;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -508,6 +546,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the layout node of the actual text input
|
/// Get the layout node of the actual text input
|
||||||
|
|
||||||
fn text_layout<'b>(&'a self, layout: Layout<'b>) -> Layout<'b> {
|
fn text_layout<'b>(&'a self, layout: Layout<'b>) -> Layout<'b> {
|
||||||
if self.dnd_icon {
|
if self.dnd_icon {
|
||||||
layout
|
layout
|
||||||
|
|
@ -525,10 +564,12 @@ impl<Message> Widget<Message, crate::Theme, crate::Renderer> for TextInput<'_, M
|
||||||
where
|
where
|
||||||
Message: Clone + 'static,
|
Message: Clone + 'static,
|
||||||
{
|
{
|
||||||
|
#[inline]
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
tree::Tag::of::<State>()
|
tree::Tag::of::<State>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn state(&self) -> tree::State {
|
fn state(&self) -> tree::State {
|
||||||
tree::State::new(State::new(
|
tree::State::new(State::new(
|
||||||
self.is_secure,
|
self.is_secure,
|
||||||
|
|
@ -540,6 +581,7 @@ where
|
||||||
|
|
||||||
fn diff(&mut self, tree: &mut Tree) {
|
fn diff(&mut self, tree: &mut Tree) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
|
||||||
if !self.manage_value || !self.value.is_empty() && state.tracked_value != self.value {
|
if !self.manage_value || !self.value.is_empty() && state.tracked_value != self.value {
|
||||||
state.tracked_value = self.value.clone();
|
state.tracked_value = self.value.clone();
|
||||||
} else if self.value.is_empty() {
|
} else if self.value.is_empty() {
|
||||||
|
|
@ -586,8 +628,6 @@ where
|
||||||
state.dirty = true;
|
state.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.is_read_only = state.is_read_only;
|
|
||||||
|
|
||||||
if self.always_active && state.is_focused.is_none() {
|
if self.always_active && state.is_focused.is_none() {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
|
|
@ -607,12 +647,18 @@ where
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if !state.is_focused.as_ref().map_or(false, |f| {
|
if !state
|
||||||
f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get())
|
.is_focused
|
||||||
}) {
|
.as_ref()
|
||||||
state.is_focused = None;
|
.map(|f| f.updated_at == LAST_FOCUS_UPDATE.with(|f| f.get()))
|
||||||
|
.unwrap_or_default()
|
||||||
|
{
|
||||||
|
state.unfocus();
|
||||||
|
// state.is_read_only = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.is_read_only = state.is_read_only;
|
||||||
|
|
||||||
// Stop pasting if input becomes disabled
|
// Stop pasting if input becomes disabled
|
||||||
if !self.manage_value && self.on_input.is_none() {
|
if !self.manage_value && self.on_input.is_none() {
|
||||||
state.is_pasting = None;
|
state.is_pasting = None;
|
||||||
|
|
@ -635,6 +681,7 @@ where
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn size(&self) -> Size<Length> {
|
fn size(&self) -> Size<Length> {
|
||||||
Size {
|
Size {
|
||||||
width: self.width,
|
width: self.width,
|
||||||
|
|
@ -790,7 +837,8 @@ where
|
||||||
let size = self.size.unwrap_or_else(|| renderer.default_size().0);
|
let size = self.size.unwrap_or_else(|| renderer.default_size().0);
|
||||||
let line_height = self.line_height;
|
let line_height = self.line_height;
|
||||||
|
|
||||||
if self.is_editable {
|
// Disables editing of the editable variant when clicking outside of it.
|
||||||
|
if self.is_editable_variant {
|
||||||
if let Some(ref on_edit) = self.on_toggle_edit {
|
if let Some(ref on_edit) = self.on_toggle_edit {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
if !state.is_read_only && state.is_focused.is_none() {
|
if !state.is_read_only && state.is_focused.is_none() {
|
||||||
|
|
@ -800,34 +848,38 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculates the layout of the trailing icon button element.
|
||||||
if !tree.children.is_empty() {
|
if !tree.children.is_empty() {
|
||||||
let index = tree.children.len() - 1;
|
let index = tree.children.len() - 1;
|
||||||
if let (Some(trailing_icon), Some(tree)) =
|
if let (Some(trailing_icon), Some(tree)) =
|
||||||
(self.trailing_icon.as_mut(), tree.children.get_mut(index))
|
(self.trailing_icon.as_mut(), tree.children.get_mut(index))
|
||||||
{
|
{
|
||||||
let children = text_layout.children();
|
trailing_icon_layout = Some(text_layout.children().last().unwrap());
|
||||||
trailing_icon_layout = Some(children.last().unwrap());
|
|
||||||
|
|
||||||
if let Some(trailing_layout) = trailing_icon_layout {
|
// Enable custom buttons defined on the trailing icon position to be handled.
|
||||||
if cursor_position.is_over(trailing_layout.bounds()) {
|
if !self.is_editable_variant {
|
||||||
let res = trailing_icon.as_widget_mut().on_event(
|
if let Some(trailing_layout) = trailing_icon_layout {
|
||||||
tree,
|
if cursor_position.is_over(trailing_layout.bounds()) {
|
||||||
event.clone(),
|
let res = trailing_icon.as_widget_mut().on_event(
|
||||||
trailing_layout,
|
tree,
|
||||||
cursor_position,
|
event.clone(),
|
||||||
renderer,
|
trailing_layout,
|
||||||
clipboard,
|
cursor_position,
|
||||||
shell,
|
renderer,
|
||||||
viewport,
|
clipboard,
|
||||||
);
|
shell,
|
||||||
|
viewport,
|
||||||
|
);
|
||||||
|
|
||||||
if res == event::Status::Captured {
|
if res == event::Status::Captured {
|
||||||
return res;
|
return res;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let dnd_id = self.dnd_id();
|
let dnd_id = self.dnd_id();
|
||||||
let id = Widget::id(self);
|
let id = Widget::id(self);
|
||||||
update(
|
update(
|
||||||
|
|
@ -841,11 +893,14 @@ where
|
||||||
&mut self.value,
|
&mut self.value,
|
||||||
size,
|
size,
|
||||||
font,
|
font,
|
||||||
|
self.is_editable_variant,
|
||||||
self.is_secure,
|
self.is_secure,
|
||||||
self.is_editable,
|
self.on_focus.as_ref(),
|
||||||
|
self.on_unfocus.as_ref(),
|
||||||
self.on_input.as_deref(),
|
self.on_input.as_deref(),
|
||||||
self.on_paste.as_deref(),
|
self.on_paste.as_deref(),
|
||||||
self.on_submit.as_deref(),
|
self.on_submit.as_deref(),
|
||||||
|
self.on_tab.as_ref(),
|
||||||
self.on_toggle_edit.as_deref(),
|
self.on_toggle_edit.as_deref(),
|
||||||
|| tree.state.downcast_mut::<State>(),
|
|| tree.state.downcast_mut::<State>(),
|
||||||
self.on_create_dnd_source.as_deref(),
|
self.on_create_dnd_source.as_deref(),
|
||||||
|
|
@ -856,6 +911,7 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn draw(
|
fn draw(
|
||||||
&self,
|
&self,
|
||||||
tree: &Tree,
|
tree: &Tree,
|
||||||
|
|
@ -908,9 +964,7 @@ where
|
||||||
if let (Some(leading_icon), Some(tree)) =
|
if let (Some(leading_icon), Some(tree)) =
|
||||||
(self.leading_icon.as_ref(), state.children.get(index))
|
(self.leading_icon.as_ref(), state.children.get(index))
|
||||||
{
|
{
|
||||||
let mut children = layout.children();
|
let leading_icon_layout = layout.children().nth(1).unwrap();
|
||||||
children.next();
|
|
||||||
let leading_icon_layout = children.next().unwrap();
|
|
||||||
|
|
||||||
if cursor_position.is_over(leading_icon_layout.bounds()) {
|
if cursor_position.is_over(leading_icon_layout.bounds()) {
|
||||||
return leading_icon.as_widget().mouse_interaction(
|
return leading_icon.as_widget().mouse_interaction(
|
||||||
|
|
@ -954,19 +1008,21 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn id(&self) -> Option<Id> {
|
fn id(&self) -> Option<Id> {
|
||||||
Some(self.id.clone())
|
Some(self.id.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn set_id(&mut self, id: Id) {
|
fn set_id(&mut self, id: Id) {
|
||||||
self.id = id;
|
self.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drag_destinations(
|
fn drag_destinations(
|
||||||
&self,
|
&self,
|
||||||
state: &Tree,
|
_state: &Tree,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
renderer: &crate::Renderer,
|
_renderer: &crate::Renderer,
|
||||||
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
dnd_rectangles: &mut iced_core::clipboard::DndDestinationRectangles,
|
||||||
) {
|
) {
|
||||||
if let Some(input) = layout.children().last() {
|
if let Some(input) = layout.children().last() {
|
||||||
|
|
@ -1251,18 +1307,21 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
id: Option<Id>,
|
id: Option<Id>,
|
||||||
event: Event,
|
event: Event,
|
||||||
text_layout: Layout<'_>,
|
text_layout: Layout<'_>,
|
||||||
trailing_icon_layout: Option<Layout<'_>>,
|
edit_button_layout: Option<Layout<'_>>,
|
||||||
cursor: mouse::Cursor,
|
cursor: mouse::Cursor,
|
||||||
clipboard: &mut dyn Clipboard,
|
clipboard: &mut dyn Clipboard,
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
value: &mut Value,
|
value: &mut Value,
|
||||||
size: f32,
|
size: f32,
|
||||||
font: <crate::Renderer as iced_core::text::Renderer>::Font,
|
font: <crate::Renderer as iced_core::text::Renderer>::Font,
|
||||||
|
is_editable_variant: bool,
|
||||||
is_secure: bool,
|
is_secure: bool,
|
||||||
is_editable: bool,
|
on_focus: Option<&Message>,
|
||||||
|
on_unfocus: Option<&Message>,
|
||||||
on_input: Option<&dyn Fn(String) -> Message>,
|
on_input: Option<&dyn Fn(String) -> Message>,
|
||||||
on_paste: Option<&dyn Fn(String) -> Message>,
|
on_paste: Option<&dyn Fn(String) -> Message>,
|
||||||
on_submit: Option<&dyn Fn(String) -> Message>,
|
on_submit: Option<&dyn Fn(String) -> Message>,
|
||||||
|
on_tab: Option<&Message>,
|
||||||
on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
|
on_toggle_edit: Option<&dyn Fn(bool) -> Message>,
|
||||||
state: impl FnOnce() -> &'a mut State,
|
state: impl FnOnce() -> &'a mut State,
|
||||||
#[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
|
#[allow(unused_variables)] on_start_dnd_source: Option<&dyn Fn(State) -> Message>,
|
||||||
|
|
@ -1285,9 +1344,15 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
|
|
||||||
// NOTE: Clicks must be captured to prevent mouse areas behind them handling the same clicks.
|
// NOTE: Clicks must be captured to prevent mouse areas behind them handling the same clicks.
|
||||||
|
|
||||||
|
/// Mark a branch as cold
|
||||||
|
#[inline]
|
||||||
|
#[cold]
|
||||||
|
fn cold() {}
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
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 { .. }) => {
|
||||||
|
cold();
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
let click_position = if on_input.is_some() || manage_value {
|
let click_position = if on_input.is_some() || manage_value {
|
||||||
|
|
@ -1296,18 +1361,30 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if click_position.is_some() {
|
|
||||||
state.is_focused = state.is_focused.or_else(|| {
|
|
||||||
let now = Instant::now();
|
|
||||||
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
|
||||||
Some(Focus {
|
|
||||||
updated_at: now,
|
|
||||||
now,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(cursor_position) = click_position {
|
if let Some(cursor_position) = click_position {
|
||||||
|
// Check if the edit button was clicked.
|
||||||
|
if state.dragging_state == None
|
||||||
|
&& edit_button_layout.map_or(false, |l| cursor.is_over(l.bounds()))
|
||||||
|
{
|
||||||
|
if is_editable_variant {
|
||||||
|
state.is_read_only = !state.is_read_only;
|
||||||
|
state.move_cursor_to_end();
|
||||||
|
|
||||||
|
if let Some(on_toggle_edit) = on_toggle_edit {
|
||||||
|
shell.publish(on_toggle_edit(!state.is_read_only));
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
|
state.is_focused = Some(Focus {
|
||||||
|
updated_at: now,
|
||||||
|
now,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return event::Status::Captured;
|
||||||
|
}
|
||||||
|
|
||||||
let target = cursor_position.x - text_layout.bounds().x;
|
let target = cursor_position.x - text_layout.bounds().x;
|
||||||
|
|
||||||
let click =
|
let click =
|
||||||
|
|
@ -1324,7 +1401,7 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
// single click that is on top of the selected text
|
// single click that is on top of the selected text
|
||||||
// is the click on selected text?
|
// is the click on selected text?
|
||||||
|
|
||||||
if manage_value || on_input.is_some() {
|
if on_input.is_some() || manage_value {
|
||||||
let left = start.min(end);
|
let left = start.min(end);
|
||||||
let right = end.max(start);
|
let right = end.max(start);
|
||||||
|
|
||||||
|
|
@ -1393,40 +1470,14 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
update_cache(state, &unsecured_value);
|
update_cache(state, &unsecured_value);
|
||||||
} else {
|
} else {
|
||||||
update_cache(state, value);
|
update_cache(state, value);
|
||||||
// existing logic for setting the selection
|
state.setting_selection(value, text_layout.bounds(), target);
|
||||||
let position = if target > 0.0 {
|
|
||||||
find_cursor_position(text_layout.bounds(), value, state, target)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
state.cursor.move_to(position.unwrap_or(0));
|
|
||||||
state.dragging_state = Some(DraggingState::Selection);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// existing logic for setting the selection
|
state.setting_selection(value, text_layout.bounds(), target);
|
||||||
let position = if target > 0.0 {
|
|
||||||
update_cache(state, value);
|
|
||||||
find_cursor_position(text_layout.bounds(), value, state, target)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
state.cursor.move_to(position.unwrap_or(0));
|
|
||||||
state.dragging_state = Some(DraggingState::Selection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(None, click::Kind::Single, _) => {
|
(None, click::Kind::Single, _) => {
|
||||||
// existing logic for setting the selection
|
state.setting_selection(value, text_layout.bounds(), target);
|
||||||
let position = if target > 0.0 {
|
|
||||||
update_cache(state, value);
|
|
||||||
find_cursor_position(text_layout.bounds(), value, state, target)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
state.cursor.move_to(position.unwrap_or(0));
|
|
||||||
state.dragging_state = Some(DraggingState::Selection);
|
|
||||||
}
|
}
|
||||||
(None | Some(DraggingState::Selection), click::Kind::Double, _) => {
|
(None | Some(DraggingState::Selection), click::Kind::Double, _) => {
|
||||||
update_cache(state, value);
|
update_cache(state, value);
|
||||||
|
|
@ -1455,93 +1506,41 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable write mode when an editable input label is clicked
|
// Focus on click of the text input, and ensure that the input is writable.
|
||||||
if is_editable
|
if state.is_focused.is_none()
|
||||||
&& state.is_read_only
|
|
||||||
&& matches!(state.dragging_state, None | Some(DraggingState::Selection))
|
&& matches!(state.dragging_state, None | Some(DraggingState::Selection))
|
||||||
{
|
{
|
||||||
state.is_read_only = false;
|
if let Some(on_focus) = on_focus {
|
||||||
|
shell.publish(on_focus.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
if state.is_read_only {
|
||||||
|
state.is_read_only = false;
|
||||||
|
if let Some(on_toggle_edit) = on_toggle_edit {
|
||||||
|
let message = (on_toggle_edit)(true);
|
||||||
|
shell.publish(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
|
|
||||||
state.is_focused = Some(Focus {
|
state.is_focused = Some(Focus {
|
||||||
updated_at: now,
|
updated_at: now,
|
||||||
now,
|
now,
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(on_toggle_edit) = on_toggle_edit {
|
|
||||||
let message = (on_toggle_edit)(!state.is_read_only);
|
|
||||||
shell.publish(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
state.last_click = Some(click);
|
state.last_click = Some(click);
|
||||||
|
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
} else {
|
||||||
|
state.unfocus();
|
||||||
let mut is_trailing_clicked = false;
|
|
||||||
if is_editable {
|
|
||||||
if let Some(trailing_layout) = trailing_icon_layout {
|
|
||||||
is_trailing_clicked = cursor.is_over(trailing_layout.bounds());
|
|
||||||
if is_trailing_clicked {
|
|
||||||
if on_toggle_edit.is_some() {
|
|
||||||
let Some(pos) = cursor.position() else {
|
|
||||||
return event::Status::Ignored;
|
|
||||||
};
|
|
||||||
|
|
||||||
let click =
|
|
||||||
mouse::Click::new(pos, mouse::Button::Left, state.last_click);
|
|
||||||
|
|
||||||
match (
|
|
||||||
&state.dragging_state,
|
|
||||||
click.kind(),
|
|
||||||
state.cursor().state(value),
|
|
||||||
) {
|
|
||||||
(None, click::Kind::Single, _) => {
|
|
||||||
state.is_read_only = !state.is_read_only;
|
|
||||||
if let Some(on_toggle_edit) = on_toggle_edit {
|
|
||||||
let message = (on_toggle_edit)(!state.is_read_only);
|
|
||||||
shell.publish(message);
|
|
||||||
|
|
||||||
let now = Instant::now();
|
|
||||||
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
|
||||||
state.is_focused = Some(Focus {
|
|
||||||
updated_at: now,
|
|
||||||
now,
|
|
||||||
});
|
|
||||||
|
|
||||||
state.move_cursor_to_end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
state.dragging_state = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Condition met when mouse click is completely outside of the widget.
|
|
||||||
if !is_trailing_clicked && click_position.is_none() {
|
|
||||||
state.is_focused = None;
|
|
||||||
state.dragging_state = None;
|
|
||||||
state.is_pasting = None;
|
|
||||||
state.keyboard_modifiers = keyboard::Modifiers::default();
|
|
||||||
state.is_read_only = true;
|
|
||||||
|
|
||||||
// Ensure clicks outside emit the toggle edit message.
|
|
||||||
if let Some(on_toggle_edit) = on_toggle_edit {
|
|
||||||
let message = (on_toggle_edit)(false);
|
|
||||||
shell.publish(message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
| Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
|
| Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => {
|
||||||
|
cold();
|
||||||
let state = state();
|
let state = state();
|
||||||
state.dragging_state = None;
|
state.dragging_state = None;
|
||||||
|
|
||||||
|
|
@ -1573,14 +1572,10 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
let state = state();
|
let state = state();
|
||||||
|
|
||||||
if let Some(focus) = &mut state.is_focused {
|
if let Some(focus) = &mut state.is_focused {
|
||||||
if !manage_value && on_input.is_none() {
|
if state.is_read_only || (!manage_value && on_input.is_none()) {
|
||||||
return event::Status::Ignored;
|
return event::Status::Ignored;
|
||||||
};
|
};
|
||||||
|
|
||||||
if state.is_read_only {
|
|
||||||
return event::Status::Ignored;
|
|
||||||
}
|
|
||||||
|
|
||||||
let modifiers = state.keyboard_modifiers;
|
let modifiers = state.keyboard_modifiers;
|
||||||
focus.updated_at = Instant::now();
|
focus.updated_at = Instant::now();
|
||||||
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
|
LAST_FOCUS_UPDATE.with(|x| x.set(focus.updated_at));
|
||||||
|
|
@ -1750,6 +1745,7 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
let contents = editor.contents();
|
let contents = editor.contents();
|
||||||
let unsecured_value = Value::new(&contents);
|
let unsecured_value = Value::new(&contents);
|
||||||
state.tracked_value = unsecured_value.clone();
|
state.tracked_value = unsecured_value.clone();
|
||||||
|
|
||||||
if let Some(on_input) = on_input {
|
if let Some(on_input) = on_input {
|
||||||
let message = if let Some(paste) = &on_paste {
|
let message = if let Some(paste) = &on_paste {
|
||||||
(paste)(contents)
|
(paste)(contents)
|
||||||
|
|
@ -1759,6 +1755,7 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
|
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.is_pasting = Some(content);
|
state.is_pasting = Some(content);
|
||||||
|
|
||||||
let value = if is_secure {
|
let value = if is_secure {
|
||||||
|
|
@ -1775,17 +1772,34 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
state.cursor.select_all(value);
|
state.cursor.select_all(value);
|
||||||
}
|
}
|
||||||
keyboard::Key::Named(keyboard::key::Named::Escape) => {
|
keyboard::Key::Named(keyboard::key::Named::Escape) => {
|
||||||
state.is_focused = None;
|
state.unfocus();
|
||||||
state.dragging_state = None;
|
state.is_read_only = true;
|
||||||
state.is_pasting = None;
|
|
||||||
|
|
||||||
state.keyboard_modifiers = keyboard::Modifiers::default();
|
if let Some(on_unfocus) = on_unfocus {
|
||||||
|
shell.publish(on_unfocus.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboard::Key::Named(keyboard::key::Named::Tab) => {
|
||||||
|
if let Some(on_tab) = on_tab {
|
||||||
|
// Allow the application to decide how the event is handled.
|
||||||
|
// This could be to connect the text input to another text input.
|
||||||
|
// Or to connect the text input to a button.
|
||||||
|
shell.publish(on_tab.clone());
|
||||||
|
} else {
|
||||||
|
state.unfocus();
|
||||||
|
state.is_read_only = true;
|
||||||
|
|
||||||
|
if let Some(on_unfocus) = on_unfocus {
|
||||||
|
shell.publish(on_unfocus.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
return event::Status::Ignored;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboard::Key::Named(
|
keyboard::Key::Named(
|
||||||
keyboard::key::Named::ArrowUp
|
keyboard::key::Named::ArrowUp | keyboard::key::Named::ArrowDown,
|
||||||
| keyboard::key::Named::ArrowDown
|
|
||||||
| keyboard::key::Named::Tab,
|
|
||||||
) => {
|
) => {
|
||||||
return event::Status::Ignored;
|
return event::Status::Ignored;
|
||||||
}
|
}
|
||||||
|
|
@ -1819,8 +1833,6 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
unsecured_value
|
unsecured_value
|
||||||
};
|
};
|
||||||
update_cache(state, &value);
|
update_cache(state, &value);
|
||||||
|
|
||||||
return event::Status::Captured;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
|
@ -1869,8 +1881,10 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
|
Event::Dnd(DndEvent::Source(SourceEvent::Finished | SourceEvent::Cancelled)) => {
|
||||||
|
cold();
|
||||||
let state = state();
|
let state = state();
|
||||||
if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
|
if matches!(state.dragging_state, Some(DraggingState::Dnd(..))) {
|
||||||
|
// TODO: restore value in text input
|
||||||
state.dragging_state = None;
|
state.dragging_state = None;
|
||||||
return event::Status::Captured;
|
return event::Status::Captured;
|
||||||
}
|
}
|
||||||
|
|
@ -1885,6 +1899,7 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
surface,
|
surface,
|
||||||
},
|
},
|
||||||
)) if rectangle == Some(dnd_id) => {
|
)) if rectangle == Some(dnd_id) => {
|
||||||
|
cold();
|
||||||
let state = state();
|
let state = state();
|
||||||
let is_clicked = text_layout.bounds().contains(Point {
|
let is_clicked = text_layout.bounds().contains(Point {
|
||||||
x: x as f32,
|
x: x as f32,
|
||||||
|
|
@ -1934,6 +1949,7 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
}
|
}
|
||||||
#[cfg(feature = "wayland")]
|
#[cfg(feature = "wayland")]
|
||||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => {
|
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Drop)) if rectangle == Some(dnd_id) => {
|
||||||
|
cold();
|
||||||
let state = state();
|
let state = state();
|
||||||
if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
|
if let DndOfferState::HandlingOffer(mime_types, _action) = state.dnd_offer.clone() {
|
||||||
let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
|
let Some(mime_type) = SUPPORTED_TEXT_MIME_TYPES
|
||||||
|
|
@ -1953,6 +1969,7 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
rectangle,
|
rectangle,
|
||||||
OfferEvent::Leave | OfferEvent::LeaveDestination,
|
OfferEvent::Leave | OfferEvent::LeaveDestination,
|
||||||
)) if rectangle == Some(dnd_id) => {
|
)) if rectangle == Some(dnd_id) => {
|
||||||
|
cold();
|
||||||
let state = state();
|
let state = state();
|
||||||
// ASHLEY TODO we should be able to reset but for now we don't if we are handling a
|
// ASHLEY TODO we should be able to reset but for now we don't if we are handling a
|
||||||
// drop
|
// drop
|
||||||
|
|
@ -1968,6 +1985,7 @@ pub fn update<'a, Message: Clone + 'static>(
|
||||||
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
|
Event::Dnd(DndEvent::Offer(rectangle, OfferEvent::Data { data, mime_type }))
|
||||||
if rectangle == Some(dnd_id) =>
|
if rectangle == Some(dnd_id) =>
|
||||||
{
|
{
|
||||||
|
cold();
|
||||||
let state = state();
|
let state = state();
|
||||||
if let DndOfferState::Dropped = state.dnd_offer.clone() {
|
if let DndOfferState::Dropped = state.dnd_offer.clone() {
|
||||||
state.dnd_offer = DndOfferState::None;
|
state.dnd_offer = DndOfferState::None;
|
||||||
|
|
@ -2560,18 +2578,21 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether the [`TextInput`] is currently focused or not.
|
/// Returns whether the [`TextInput`] is currently focused or not.
|
||||||
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn is_focused(&self) -> bool {
|
pub fn is_focused(&self) -> bool {
|
||||||
self.is_focused.is_some()
|
self.is_focused.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`Cursor`] of the [`TextInput`].
|
/// Returns the [`Cursor`] of the [`TextInput`].
|
||||||
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn cursor(&self) -> Cursor {
|
pub fn cursor(&self) -> Cursor {
|
||||||
self.cursor
|
self.cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Focuses the [`TextInput`].
|
/// Focuses the [`TextInput`].
|
||||||
|
#[cold]
|
||||||
pub fn focus(&mut self) {
|
pub fn focus(&mut self) {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
LAST_FOCUS_UPDATE.with(|x| x.set(now));
|
||||||
|
|
@ -2589,63 +2610,90 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unfocuses the [`TextInput`].
|
/// Unfocuses the [`TextInput`].
|
||||||
pub fn unfocus(&mut self) {
|
#[cold]
|
||||||
|
pub(super) fn unfocus(&mut self) {
|
||||||
self.is_focused = None;
|
self.is_focused = None;
|
||||||
|
self.dragging_state = None;
|
||||||
|
self.is_pasting = None;
|
||||||
|
self.keyboard_modifiers = keyboard::Modifiers::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
|
/// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
|
||||||
|
#[inline]
|
||||||
pub fn move_cursor_to_front(&mut self) {
|
pub fn move_cursor_to_front(&mut self) {
|
||||||
self.cursor.move_to(0);
|
self.cursor.move_to(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text.
|
/// Moves the [`Cursor`] of the [`TextInput`] to the end of the input text.
|
||||||
|
#[inline]
|
||||||
pub fn move_cursor_to_end(&mut self) {
|
pub fn move_cursor_to_end(&mut self) {
|
||||||
self.cursor.move_to(usize::MAX);
|
self.cursor.move_to(usize::MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location.
|
/// Moves the [`Cursor`] of the [`TextInput`] to an arbitrary location.
|
||||||
|
#[inline]
|
||||||
pub fn move_cursor_to(&mut self, position: usize) {
|
pub fn move_cursor_to(&mut self, position: usize) {
|
||||||
self.cursor.move_to(position);
|
self.cursor.move_to(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Selects all the content of the [`TextInput`].
|
/// Selects all the content of the [`TextInput`].
|
||||||
|
#[inline]
|
||||||
pub fn select_all(&mut self) {
|
pub fn select_all(&mut self) {
|
||||||
self.cursor.select_range(0, usize::MAX);
|
self.cursor.select_range(0, usize::MAX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn setting_selection(&mut self, value: &Value, bounds: Rectangle<f32>, target: f32) {
|
||||||
|
let position = if target > 0.0 {
|
||||||
|
find_cursor_position(bounds, value, self, target)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
self.cursor.move_to(position.unwrap_or(0));
|
||||||
|
self.dragging_state = Some(DraggingState::Selection);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl operation::Focusable for State {
|
impl operation::Focusable for State {
|
||||||
|
#[inline]
|
||||||
fn is_focused(&self) -> bool {
|
fn is_focused(&self) -> bool {
|
||||||
Self::is_focused(self)
|
Self::is_focused(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn focus(&mut self) {
|
fn focus(&mut self) {
|
||||||
Self::focus(self);
|
Self::focus(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn unfocus(&mut self) {
|
fn unfocus(&mut self) {
|
||||||
Self::unfocus(self);
|
Self::unfocus(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl operation::TextInput for State {
|
impl operation::TextInput for State {
|
||||||
|
#[inline]
|
||||||
fn move_cursor_to_front(&mut self) {
|
fn move_cursor_to_front(&mut self) {
|
||||||
Self::move_cursor_to_front(self);
|
Self::move_cursor_to_front(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn move_cursor_to_end(&mut self) {
|
fn move_cursor_to_end(&mut self) {
|
||||||
Self::move_cursor_to_end(self);
|
Self::move_cursor_to_end(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn move_cursor_to(&mut self, position: usize) {
|
fn move_cursor_to(&mut self, position: usize) {
|
||||||
Self::move_cursor_to(self, position);
|
Self::move_cursor_to(self, position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn select_all(&mut self) {
|
fn select_all(&mut self) {
|
||||||
Self::select_all(self);
|
Self::select_all(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
fn measure_cursor_and_scroll_offset(
|
fn measure_cursor_and_scroll_offset(
|
||||||
paragraph: &impl text::Paragraph,
|
paragraph: &impl text::Paragraph,
|
||||||
text_bounds: Rectangle,
|
text_bounds: Rectangle,
|
||||||
|
|
@ -2662,6 +2710,7 @@ fn measure_cursor_and_scroll_offset(
|
||||||
|
|
||||||
/// Computes the position of the text cursor at the given X coordinate of
|
/// Computes the position of the text cursor at the given X coordinate of
|
||||||
/// a [`TextInput`].
|
/// a [`TextInput`].
|
||||||
|
#[inline(never)]
|
||||||
fn find_cursor_position(
|
fn find_cursor_position(
|
||||||
text_bounds: Rectangle,
|
text_bounds: Rectangle,
|
||||||
value: &Value,
|
value: &Value,
|
||||||
|
|
@ -2686,6 +2735,7 @@ fn find_cursor_position(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
fn replace_paragraph(
|
fn replace_paragraph(
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
layout: Layout<'_>,
|
layout: Layout<'_>,
|
||||||
|
|
@ -2715,6 +2765,7 @@ const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
|
||||||
mod platform {
|
mod platform {
|
||||||
use iced_core::keyboard;
|
use iced_core::keyboard;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
|
pub fn is_jump_modifier_pressed(modifiers: keyboard::Modifiers) -> bool {
|
||||||
if cfg!(target_os = "macos") {
|
if cfg!(target_os = "macos") {
|
||||||
modifiers.alt()
|
modifiers.alt()
|
||||||
|
|
@ -2724,6 +2775,7 @@ mod platform {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
|
fn offset(text_bounds: Rectangle, value: &Value, state: &State) -> f32 {
|
||||||
if state.is_focused() {
|
if state.is_focused() {
|
||||||
let cursor = state.cursor();
|
let cursor = state.cursor();
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,14 @@ impl Value {
|
||||||
///
|
///
|
||||||
/// A [`Value`] is empty when it contains no graphemes.
|
/// A [`Value`] is empty when it contains no graphemes.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.len() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the total amount of graphemes in the [`Value`].
|
/// Returns the total amount of graphemes in the [`Value`].
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.graphemes.len()
|
self.graphemes.len()
|
||||||
}
|
}
|
||||||
|
|
@ -75,6 +77,7 @@ impl Value {
|
||||||
/// Returns a new [`Value`] containing the graphemes from `start` until the
|
/// Returns a new [`Value`] containing the graphemes from `start` until the
|
||||||
/// given `end`.
|
/// given `end`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
pub fn select(&self, start: usize, end: usize) -> Self {
|
pub fn select(&self, start: usize, end: usize) -> Self {
|
||||||
let graphemes = self.graphemes[start.min(self.len())..end.min(self.len())].to_vec();
|
let graphemes = self.graphemes[start.min(self.len())..end.min(self.len())].to_vec();
|
||||||
|
|
||||||
|
|
@ -84,6 +87,7 @@ impl Value {
|
||||||
/// Returns a new [`Value`] containing the graphemes until the given
|
/// Returns a new [`Value`] containing the graphemes until the given
|
||||||
/// `index`.
|
/// `index`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
#[inline]
|
||||||
pub fn until(&self, index: usize) -> Self {
|
pub fn until(&self, index: usize) -> Self {
|
||||||
let graphemes = self.graphemes[..index.min(self.len())].to_vec();
|
let graphemes = self.graphemes[..index.min(self.len())].to_vec();
|
||||||
|
|
||||||
|
|
@ -91,6 +95,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a new `char` at the given grapheme `index`.
|
/// Inserts a new `char` at the given grapheme `index`.
|
||||||
|
#[inline]
|
||||||
pub fn insert(&mut self, index: usize, c: char) {
|
pub fn insert(&mut self, index: usize, c: char) {
|
||||||
self.graphemes.insert(index, c.to_string());
|
self.graphemes.insert(index, c.to_string());
|
||||||
|
|
||||||
|
|
@ -100,6 +105,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a bunch of graphemes at the given grapheme `index`.
|
/// Inserts a bunch of graphemes at the given grapheme `index`.
|
||||||
|
#[inline]
|
||||||
pub fn insert_many(&mut self, index: usize, mut value: Value) {
|
pub fn insert_many(&mut self, index: usize, mut value: Value) {
|
||||||
let _ = self
|
let _ = self
|
||||||
.graphemes
|
.graphemes
|
||||||
|
|
@ -107,11 +113,13 @@ impl Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the grapheme at the given `index`.
|
/// Removes the grapheme at the given `index`.
|
||||||
|
#[inline]
|
||||||
pub fn remove(&mut self, index: usize) {
|
pub fn remove(&mut self, index: usize) {
|
||||||
let _ = self.graphemes.remove(index);
|
let _ = self.graphemes.remove(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the graphemes from `start` to `end`.
|
/// Removes the graphemes from `start` to `end`.
|
||||||
|
#[inline]
|
||||||
pub fn remove_many(&mut self, start: usize, end: usize) {
|
pub fn remove_many(&mut self, start: usize, end: usize) {
|
||||||
let _ = self.graphemes.splice(start..end, std::iter::empty());
|
let _ = self.graphemes.splice(start..end, std::iter::empty());
|
||||||
}
|
}
|
||||||
|
|
@ -129,6 +137,7 @@ impl Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for Value {
|
impl ToString for Value {
|
||||||
|
#[inline]
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
self.graphemes.concat()
|
self.graphemes.concat()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue