Draft Shell:request_redraw API
... and implement `TextInput` cursor blink 🎉
This commit is contained in:
parent
7ccd87c36b
commit
7354f68b3c
12 changed files with 267 additions and 111 deletions
|
|
@ -22,11 +22,14 @@ 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,
|
||||
};
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
pub use iced_style::text_input::{Appearance, StyleSheet};
|
||||
|
||||
/// A field that can be filled with text.
|
||||
|
|
@ -425,7 +428,16 @@ where
|
|||
let state = state();
|
||||
let is_clicked = layout.bounds().contains(cursor_position);
|
||||
|
||||
state.is_focused = is_clicked;
|
||||
state.is_focused = if is_clicked {
|
||||
let now = Instant::now();
|
||||
|
||||
Some(Focus {
|
||||
at: now,
|
||||
last_draw: now,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if is_clicked {
|
||||
let text_layout = layout.children().next().unwrap();
|
||||
|
|
@ -541,26 +553,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.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.at = Instant::now();
|
||||
|
||||
match key_code {
|
||||
keyboard::KeyCode::Enter
|
||||
|
|
@ -721,7 +737,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 +758,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 +781,21 @@ where
|
|||
|
||||
state.keyboard_modifiers = modifiers;
|
||||
}
|
||||
Event::Window(window::Event::RedrawRequested(now)) => {
|
||||
let state = state();
|
||||
|
||||
if let Some(focus) = &mut state.is_focused {
|
||||
focus.last_draw = now;
|
||||
|
||||
let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
|
||||
- (now - focus.at).as_millis()
|
||||
% CURSOR_BLINK_INTERVAL_MILLIS;
|
||||
|
||||
shell.request_redraw(
|
||||
now + Duration::from_millis(millis_until_redraw as u64),
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
|
|
@ -820,7 +851,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 +864,13 @@ pub fn draw<Renderer>(
|
|||
font.clone(),
|
||||
);
|
||||
|
||||
(
|
||||
let is_cursor_visible = ((focus.last_draw - focus.at)
|
||||
.as_millis()
|
||||
/ CURSOR_BLINK_INTERVAL_MILLIS)
|
||||
% 2
|
||||
== 0;
|
||||
|
||||
let cursor = if is_cursor_visible {
|
||||
Some((
|
||||
renderer::Quad {
|
||||
bounds: Rectangle {
|
||||
|
|
@ -847,9 +884,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 +998,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 +1007,12 @@ pub struct State {
|
|||
// TODO: Add stateful horizontal scrolling offset
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Focus {
|
||||
at: Instant,
|
||||
last_draw: Instant,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new [`State`], representing an unfocused [`TextInput`].
|
||||
pub fn new() -> Self {
|
||||
|
|
@ -976,7 +1022,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 +1033,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 +1043,19 @@ impl State {
|
|||
|
||||
/// Focuses the [`TextInput`].
|
||||
pub fn focus(&mut self) {
|
||||
self.is_focused = true;
|
||||
let now = Instant::now();
|
||||
|
||||
self.is_focused = Some(Focus {
|
||||
at: now,
|
||||
last_draw: 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 +1208,5 @@ where
|
|||
)
|
||||
.map(text::Hit::cursor)
|
||||
}
|
||||
|
||||
const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue