Add to blinking currsor support for Input.
This commit is contained in:
parent
ddadcda140
commit
2785f38eeb
2 changed files with 126 additions and 4 deletions
|
|
@ -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(
|
||||
|
|
|
|||
80
crates/ui/src/input/blink_cursor.rs
Normal file
80
crates/ui/src/input/blink_cursor.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue