Add to blinking currsor support for Input.

This commit is contained in:
Jason Lee 2024-06-29 15:38:02 +08:00
parent ddadcda140
commit 2785f38eeb
2 changed files with 126 additions and 4 deletions

View file

@ -2,10 +2,13 @@ use std::ops::Range;
use crate::event::InterativeElementExt as _;
use crate::theme::ActiveTheme;
use blink_cursor::BlinkCursor;
use gpui::*;
use prelude::FluentBuilder as _;
use unicode_segmentation::*;
mod blink_cursor;
actions!(
input,
[
@ -71,6 +74,7 @@ pub struct TextInput {
focus_handle: FocusHandle,
text: SharedString,
placeholder: SharedString,
blink_cursor: Model<BlinkCursor>,
selected_range: Range<usize>,
selection_reversed: bool,
marked_range: Option<Range<usize>>,
@ -84,10 +88,13 @@ impl EventEmitter<TextEvent> for TextInput {}
impl TextInput {
pub fn new(cx: &mut ViewContext<Self>) -> Self {
Self {
focus_handle: cx.focus_handle(),
let focus_handle = cx.focus_handle();
let blink_cursor = cx.new_model(|cx| BlinkCursor::new(cx));
let input = Self {
focus_handle: focus_handle.clone(),
text: "".into(),
placeholder: "".into(),
blink_cursor,
selected_range: 0..0,
selection_reversed: false,
marked_range: None,
@ -95,7 +102,25 @@ impl TextInput {
disabled: false,
masked: false,
appearance: true,
}
};
// Observe the blink cursor to repaint the view when it changes.
cx.observe(&input.blink_cursor, |_, _, cx| cx.notify())
.detach();
// Blink the cursor when the window is active, pause when it's not.
cx.observe_window_activation(|input, cx| {
if cx.is_window_active() {
input.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.start(cx);
});
}
})
.detach();
cx.on_focus(&focus_handle, Self::on_focus).detach();
cx.on_blur(&focus_handle, Self::on_blur).detach();
input
}
/// Set the text of the input field.
@ -288,6 +313,23 @@ impl TextInput {
.find_map(|(idx, _)| (idx > offset).then_some(idx))
.unwrap_or(self.text.len())
}
/// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
pub(crate) fn show_cursor(&self, cx: &WindowContext) -> bool {
self.focus_handle.is_focused(cx) && self.blink_cursor.read(cx).visible()
}
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
self.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.start(cx);
});
}
fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
self.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.pause(cx);
});
}
}
impl ViewInputHandler for TextInput {
@ -496,7 +538,7 @@ impl Element for TextElement {
.unwrap();
let cursor_pos = line.x_for_index(cursor);
let (selection, cursor) = if selected_range.is_empty() {
let (selection, cursor) = if selected_range.is_empty() && input.show_cursor(cx) {
(
None,
Some(fill(

View file

@ -0,0 +1,80 @@
use std::time::Duration;
use gpui::{ModelContext, Timer};
/// To manage the Input cursor blinking.
///
/// It will start blinking with a interval of 500ms.
/// Every loop will notify the view to update the `visable`, and Input will observe this update to touch repaint.
///
/// The input painter will check if this in visible state, then it will draw the cursor.
pub struct BlinkCursor {
interval: Duration,
blink_epoch: usize,
visible: bool,
paused: bool,
started: bool,
}
impl BlinkCursor {
pub fn new(_cx: &mut ModelContext<Self>) -> Self {
Self {
interval: Duration::from_millis(500),
visible: false,
paused: false,
started: false,
blink_epoch: 0,
}
}
/// Start the blinking
pub fn start(&mut self, cx: &mut ModelContext<Self>) {
if self.started {
return;
}
self.started = true;
self.paused = false;
self.blink(self.blink_epoch, cx);
}
fn next_epoch(&mut self) -> usize {
self.blink_epoch += 1;
self.blink_epoch
}
fn blink(&mut self, epoch: usize, cx: &mut ModelContext<Self>) {
if self.paused {
return;
}
if epoch != self.blink_epoch {
return;
}
self.visible = !self.visible;
cx.notify();
let epoch = self.next_epoch();
// Schedule the next blink
let interval = self.interval;
cx.spawn(|this, mut cx| async move {
Timer::after(interval).await;
if let Some(this) = this.upgrade() {
this.update(&mut cx, |this, cx| this.blink(epoch, cx)).ok();
}
})
.detach();
}
pub fn visible(&self) -> bool {
self.visible
}
pub fn pause(&mut self, cx: &mut ModelContext<Self>) {
self.paused = true;
self.started = false;
cx.notify();
}
}