summaryrefslogtreecommitdiffstats
path: root/native/src/widget
diff options
context:
space:
mode:
Diffstat (limited to 'native/src/widget')
-rw-r--r--native/src/widget/text_input.rs103
1 files changed, 79 insertions, 24 deletions
diff --git a/native/src/widget/text_input.rs b/native/src/widget/text_input.rs
index 8b4514e3..8755b85d 100644
--- a/native/src/widget/text_input.rs
+++ b/native/src/widget/text_input.rs
@@ -18,10 +18,12 @@ use crate::layout;
use crate::mouse::{self, click};
use crate::renderer;
use crate::text::{self, Text};
+use crate::time::{Duration, Instant};
use crate::touch;
use crate::widget;
use crate::widget::operation::{self, Operation};
use crate::widget::tree::{self, Tree};
+use crate::window;
use crate::{
Clipboard, Color, Command, Element, Layout, Length, Padding, Point,
Rectangle, Shell, Size, Vector, Widget,
@@ -425,7 +427,18 @@ where
let state = state();
let is_clicked = layout.bounds().contains(cursor_position);
- state.is_focused = is_clicked;
+ state.is_focused = if is_clicked {
+ state.is_focused.or_else(|| {
+ let now = Instant::now();
+
+ Some(Focus {
+ updated_at: now,
+ now,
+ })
+ })
+ } else {
+ None
+ };
if is_clicked {
let text_layout = layout.children().next().unwrap();
@@ -541,26 +554,30 @@ where
Event::Keyboard(keyboard::Event::CharacterReceived(c)) => {
let state = state();
- if state.is_focused
- && state.is_pasting.is_none()
- && !state.keyboard_modifiers.command()
- && !c.is_control()
- {
- let mut editor = Editor::new(value, &mut state.cursor);
+ if let Some(focus) = &mut state.is_focused {
+ if state.is_pasting.is_none()
+ && !state.keyboard_modifiers.command()
+ && !c.is_control()
+ {
+ let mut editor = Editor::new(value, &mut state.cursor);
- editor.insert(c);
+ editor.insert(c);
- let message = (on_change)(editor.contents());
- shell.publish(message);
+ let message = (on_change)(editor.contents());
+ shell.publish(message);
- return event::Status::Captured;
+ focus.updated_at = Instant::now();
+
+ return event::Status::Captured;
+ }
}
}
Event::Keyboard(keyboard::Event::KeyPressed { key_code, .. }) => {
let state = state();
- if state.is_focused {
+ if let Some(focus) = &mut state.is_focused {
let modifiers = state.keyboard_modifiers;
+ focus.updated_at = Instant::now();
match key_code {
keyboard::KeyCode::Enter
@@ -721,7 +738,7 @@ where
state.cursor.select_all(value);
}
keyboard::KeyCode::Escape => {
- state.is_focused = false;
+ state.is_focused = None;
state.is_dragging = false;
state.is_pasting = None;
@@ -742,7 +759,7 @@ where
Event::Keyboard(keyboard::Event::KeyReleased { key_code, .. }) => {
let state = state();
- if state.is_focused {
+ if state.is_focused.is_some() {
match key_code {
keyboard::KeyCode::V => {
state.is_pasting = None;
@@ -765,6 +782,21 @@ where
state.keyboard_modifiers = modifiers;
}
+ Event::Window(window::Event::RedrawRequested(now)) => {
+ let state = state();
+
+ if let Some(focus) = &mut state.is_focused {
+ focus.now = now;
+
+ let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
+ - (now - focus.updated_at).as_millis()
+ % CURSOR_BLINK_INTERVAL_MILLIS;
+
+ shell.request_redraw(window::RedrawRequest::At(
+ now + Duration::from_millis(millis_until_redraw as u64),
+ ));
+ }
+ }
_ => {}
}
@@ -820,7 +852,7 @@ pub fn draw<Renderer>(
let text = value.to_string();
let size = size.unwrap_or_else(|| renderer.default_size());
- let (cursor, offset) = if state.is_focused() {
+ let (cursor, offset) = if let Some(focus) = &state.is_focused {
match state.cursor.state(value) {
cursor::State::Index(position) => {
let (text_value_width, offset) =
@@ -833,7 +865,13 @@ pub fn draw<Renderer>(
font.clone(),
);
- (
+ let is_cursor_visible = ((focus.now - focus.updated_at)
+ .as_millis()
+ / CURSOR_BLINK_INTERVAL_MILLIS)
+ % 2
+ == 0;
+
+ let cursor = if is_cursor_visible {
Some((
renderer::Quad {
bounds: Rectangle {
@@ -847,9 +885,12 @@ pub fn draw<Renderer>(
border_color: Color::TRANSPARENT,
},
theme.value_color(style),
- )),
- offset,
- )
+ ))
+ } else {
+ None
+ };
+
+ (cursor, offset)
}
cursor::State::Selection { start, end } => {
let left = start.min(end);
@@ -958,7 +999,7 @@ pub fn mouse_interaction(
/// The state of a [`TextInput`].
#[derive(Debug, Default, Clone)]
pub struct State {
- is_focused: bool,
+ is_focused: Option<Focus>,
is_dragging: bool,
is_pasting: Option<Value>,
last_click: Option<mouse::Click>,
@@ -967,6 +1008,12 @@ pub struct State {
// TODO: Add stateful horizontal scrolling offset
}
+#[derive(Debug, Clone, Copy)]
+struct Focus {
+ updated_at: Instant,
+ now: Instant,
+}
+
impl State {
/// Creates a new [`State`], representing an unfocused [`TextInput`].
pub fn new() -> Self {
@@ -976,7 +1023,7 @@ impl State {
/// Creates a new [`State`], representing a focused [`TextInput`].
pub fn focused() -> Self {
Self {
- is_focused: true,
+ is_focused: None,
is_dragging: false,
is_pasting: None,
last_click: None,
@@ -987,7 +1034,7 @@ impl State {
/// Returns whether the [`TextInput`] is currently focused or not.
pub fn is_focused(&self) -> bool {
- self.is_focused
+ self.is_focused.is_some()
}
/// Returns the [`Cursor`] of the [`TextInput`].
@@ -997,13 +1044,19 @@ impl State {
/// Focuses the [`TextInput`].
pub fn focus(&mut self) {
- self.is_focused = true;
+ let now = Instant::now();
+
+ self.is_focused = Some(Focus {
+ updated_at: now,
+ now,
+ });
+
self.move_cursor_to_end();
}
/// Unfocuses the [`TextInput`].
pub fn unfocus(&mut self) {
- self.is_focused = false;
+ self.is_focused = None;
}
/// Moves the [`Cursor`] of the [`TextInput`] to the front of the input text.
@@ -1156,3 +1209,5 @@ where
)
.map(text::Hit::cursor)
}
+
+const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;