summaryrefslogtreecommitdiffstats
path: root/widget
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-07-29 00:51:46 +0200
committerLibravatar Héctor Ramón Jiménez <hector@hecrj.dev>2024-07-29 00:52:26 +0200
commit695721e120a173817125265a35afbc49813db716 (patch)
treef7a0bf27fd21796c9c47a398b4f9b10ca76eacdc /widget
parentd1fa9537f6066b8e93a156bd51a9cafc7c0bec17 (diff)
downloadiced-695721e120a173817125265a35afbc49813db716.tar.gz
iced-695721e120a173817125265a35afbc49813db716.tar.bz2
iced-695721e120a173817125265a35afbc49813db716.zip
Implement blinking cursor for `text_editor`
Diffstat (limited to 'widget')
-rw-r--r--widget/src/text_editor.rs103
-rw-r--r--widget/src/text_input.rs15
2 files changed, 90 insertions, 28 deletions
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs
index 899dd22a..bc391be3 100644
--- a/widget/src/text_editor.rs
+++ b/widget/src/text_editor.rs
@@ -10,8 +10,10 @@ use crate::core::renderer;
use crate::core::text::editor::{Cursor, Editor as _};
use crate::core::text::highlighter::{self, Highlighter};
use crate::core::text::{self, LineHeight, Text};
+use crate::core::time::{Duration, Instant};
use crate::core::widget::operation;
use crate::core::widget::{self, Widget};
+use crate::core::window;
use crate::core::{
Background, Border, Color, Element, Length, Padding, Pixels, Point,
Rectangle, Shell, Size, SmolStr, Theme, Vector,
@@ -369,7 +371,7 @@ where
/// The state of a [`TextEditor`].
#[derive(Debug)]
pub struct State<Highlighter: text::Highlighter> {
- is_focused: bool,
+ focus: Option<Focus>,
last_click: Option<mouse::Click>,
drag_click: Option<mouse::click::Kind>,
partial_scroll: f32,
@@ -378,10 +380,39 @@ pub struct State<Highlighter: text::Highlighter> {
highlighter_format_address: usize,
}
+#[derive(Debug, Clone, Copy)]
+struct Focus {
+ updated_at: Instant,
+ now: Instant,
+ is_window_focused: bool,
+}
+
+impl Focus {
+ const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
+
+ fn now() -> Self {
+ let now = Instant::now();
+
+ Self {
+ updated_at: now,
+ now,
+ is_window_focused: true,
+ }
+ }
+
+ fn is_cursor_visible(&self) -> bool {
+ self.is_window_focused
+ && ((self.now - self.updated_at).as_millis()
+ / Self::CURSOR_BLINK_INTERVAL_MILLIS)
+ % 2
+ == 0
+ }
+}
+
impl<Highlighter: text::Highlighter> State<Highlighter> {
/// Returns whether the [`TextEditor`] is currently focused or not.
pub fn is_focused(&self) -> bool {
- self.is_focused
+ self.focus.is_some()
}
}
@@ -389,15 +420,21 @@ impl<Highlighter: text::Highlighter> operation::Focusable
for State<Highlighter>
{
fn is_focused(&self) -> bool {
- self.is_focused
+ self.focus.is_some()
}
fn focus(&mut self) {
- self.is_focused = true;
+ let now = Instant::now();
+
+ self.focus = Some(Focus {
+ updated_at: now,
+ now,
+ is_window_focused: true,
+ });
}
fn unfocus(&mut self) {
- self.is_focused = false;
+ self.focus = None;
}
}
@@ -414,7 +451,7 @@ where
fn state(&self) -> widget::tree::State {
widget::tree::State::new(State {
- is_focused: false,
+ focus: None,
last_click: None,
drag_click: None,
partial_scroll: 0.0,
@@ -502,6 +539,41 @@ where
let state = tree.state.downcast_mut::<State<Highlighter>>();
+ match event {
+ Event::Window(window::Event::Unfocused) => {
+ if let Some(focus) = &mut state.focus {
+ focus.is_window_focused = false;
+ }
+ }
+ Event::Window(window::Event::Focused) => {
+ if let Some(focus) = &mut state.focus {
+ focus.is_window_focused = true;
+ focus.updated_at = Instant::now();
+
+ shell.request_redraw(window::RedrawRequest::NextFrame);
+ }
+ }
+ Event::Window(window::Event::RedrawRequested(now)) => {
+ if let Some(focus) = &mut state.focus {
+ if focus.is_window_focused {
+ focus.now = now;
+
+ let millis_until_redraw =
+ Focus::CURSOR_BLINK_INTERVAL_MILLIS
+ - (now - focus.updated_at).as_millis()
+ % Focus::CURSOR_BLINK_INTERVAL_MILLIS;
+
+ shell.request_redraw(window::RedrawRequest::At(
+ now + Duration::from_millis(
+ millis_until_redraw as u64,
+ ),
+ ));
+ }
+ }
+ }
+ _ => {}
+ }
+
let Some(update) = Update::from_event(
event,
state,
@@ -523,7 +595,7 @@ where
mouse::click::Kind::Triple => Action::SelectLine,
};
- state.is_focused = true;
+ state.focus = Some(Focus::now());
state.last_click = Some(click);
state.drag_click = Some(click.kind());
@@ -566,7 +638,7 @@ where
match binding {
Binding::Unfocus => {
- state.is_focused = false;
+ state.focus = None;
state.drag_click = None;
}
Binding::Copy => {
@@ -645,6 +717,10 @@ where
clipboard,
shell,
);
+
+ if let Some(focus) = &mut state.focus {
+ focus.updated_at = Instant::now();
+ }
}
}
@@ -679,7 +755,7 @@ where
let status = if is_disabled {
Status::Disabled
- } else if state.is_focused {
+ } else if state.focus.is_some() {
Status::Focused
} else if is_mouse_over {
Status::Hovered
@@ -740,9 +816,9 @@ where
bounds.y + self.padding.top,
);
- if state.is_focused {
+ if let Some(focus) = state.focus.as_ref() {
match internal.editor.cursor() {
- Cursor::Caret(position) => {
+ Cursor::Caret(position) if focus.is_cursor_visible() => {
let cursor =
Rectangle::new(
position + translation,
@@ -784,6 +860,7 @@ where
);
}
}
+ Cursor::Caret(_) => {}
}
}
}
@@ -990,7 +1067,7 @@ impl<Message> Update<Message> {
);
Some(Update::Click(click))
- } else if state.is_focused {
+ } else if state.focus.is_some() {
binding(Binding::Unfocus)
} else {
None
@@ -1030,7 +1107,7 @@ impl<Message> Update<Message> {
text,
..
}) => {
- let status = if state.is_focused {
+ let status = if state.focus.is_some() {
Status::Focused
} else {
Status::Active
diff --git a/widget/src/text_input.rs b/widget/src/text_input.rs
index a0fe14a0..20e80ba5 100644
--- a/widget/src/text_input.rs
+++ b/widget/src/text_input.rs
@@ -1210,21 +1210,6 @@ impl<P: text::Paragraph> State<P> {
Self::default()
}
- /// Creates a new [`State`], representing a focused [`TextInput`].
- pub fn focused() -> Self {
- Self {
- value: paragraph::Plain::default(),
- placeholder: paragraph::Plain::default(),
- icon: paragraph::Plain::default(),
- is_focused: None,
- is_dragging: false,
- is_pasting: None,
- last_click: None,
- cursor: Cursor::default(),
- keyboard_modifiers: keyboard::Modifiers::default(),
- }
- }
-
/// Returns whether the [`TextInput`] is currently focused or not.
pub fn is_focused(&self) -> bool {
self.is_focused.is_some()