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::event::InterativeElementExt as _;
|
||||||
use crate::theme::ActiveTheme;
|
use crate::theme::ActiveTheme;
|
||||||
|
use blink_cursor::BlinkCursor;
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use prelude::FluentBuilder as _;
|
use prelude::FluentBuilder as _;
|
||||||
use unicode_segmentation::*;
|
use unicode_segmentation::*;
|
||||||
|
|
||||||
|
mod blink_cursor;
|
||||||
|
|
||||||
actions!(
|
actions!(
|
||||||
input,
|
input,
|
||||||
[
|
[
|
||||||
|
|
@ -71,6 +74,7 @@ pub struct TextInput {
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
text: SharedString,
|
text: SharedString,
|
||||||
placeholder: SharedString,
|
placeholder: SharedString,
|
||||||
|
blink_cursor: Model<BlinkCursor>,
|
||||||
selected_range: Range<usize>,
|
selected_range: Range<usize>,
|
||||||
selection_reversed: bool,
|
selection_reversed: bool,
|
||||||
marked_range: Option<Range<usize>>,
|
marked_range: Option<Range<usize>>,
|
||||||
|
|
@ -84,10 +88,13 @@ impl EventEmitter<TextEvent> for TextInput {}
|
||||||
|
|
||||||
impl TextInput {
|
impl TextInput {
|
||||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
Self {
|
let focus_handle = cx.focus_handle();
|
||||||
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(),
|
text: "".into(),
|
||||||
placeholder: "".into(),
|
placeholder: "".into(),
|
||||||
|
blink_cursor,
|
||||||
selected_range: 0..0,
|
selected_range: 0..0,
|
||||||
selection_reversed: false,
|
selection_reversed: false,
|
||||||
marked_range: None,
|
marked_range: None,
|
||||||
|
|
@ -95,7 +102,25 @@ impl TextInput {
|
||||||
disabled: false,
|
disabled: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
appearance: true,
|
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.
|
/// Set the text of the input field.
|
||||||
|
|
@ -288,6 +313,23 @@ impl TextInput {
|
||||||
.find_map(|(idx, _)| (idx > offset).then_some(idx))
|
.find_map(|(idx, _)| (idx > offset).then_some(idx))
|
||||||
.unwrap_or(self.text.len())
|
.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 {
|
impl ViewInputHandler for TextInput {
|
||||||
|
|
@ -496,7 +538,7 @@ impl Element for TextElement {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let cursor_pos = line.x_for_index(cursor);
|
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,
|
None,
|
||||||
Some(fill(
|
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