Improve Input (#29)

Continue #27 

- Fix selection color in dark mode.
- Allow to continue selection, even mouse is out of the input.
- Pause cursor blinking when keep typing.

https://github.com/user-attachments/assets/ce9e722c-5c47-4daa-b2ba-cbe1680c4932
This commit is contained in:
Jason Lee 2024-07-13 00:34:55 +08:00 committed by GitHub
parent 0d6d1c95d0
commit 41a83e6f6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 65 additions and 26 deletions

View file

@ -118,9 +118,12 @@ impl TextInput {
// 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);
});
let focus_handle = input.focus_handle.clone();
if focus_handle.is_focused(cx) {
input.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.start(cx);
});
}
}
})
.detach();
@ -230,7 +233,8 @@ impl TextInput {
if self.selected_range.is_empty() {
self.select_to(self.previous_boundary(self.cursor_offset()), cx)
}
self.replace_text_in_range(None, "", cx)
self.replace_text_in_range(None, "", cx);
self.pause_blink_cursor(cx);
}
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
@ -267,7 +271,7 @@ impl TextInput {
self.replace_text_in_range(Some(self.selected_range.clone()), "", cx);
}
pub fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
if let Some(clipboard) = cx.read_from_clipboard() {
let new_text = clipboard.text().replace('\n', "");
self.replace_text_in_range(Some(self.selected_range.clone()), &new_text, cx);
@ -364,22 +368,32 @@ impl TextInput {
}
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
self.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.start(cx);
self.blink_cursor.update(cx, |cursor, cx| {
cursor.start(cx);
});
}
fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
self.unselect(cx);
self.blink_cursor.update(cx, |blink_cursor, cx| {
blink_cursor.pause(cx);
self.blink_cursor.update(cx, |cursor, cx| {
cursor.stop(cx);
});
}
fn pause_blink_cursor(&mut self, cx: &mut ViewContext<Self>) {
self.blink_cursor.update(cx, |cursor, cx| {
cursor.pause(cx);
});
}
fn on_key_down_for_blink_cursor(&mut self, _: &KeyDownEvent, cx: &mut ViewContext<Self>) {
self.pause_blink_cursor(cx)
}
fn on_mouse_left_down(
&mut self,
event: &MouseDownEvent,
text_hitbox: Hitbox,
hitbox: Hitbox,
cx: &mut ViewContext<TextInput>,
) {
// Ignore if text is empty
@ -387,11 +401,11 @@ impl TextInput {
return;
}
if !text_hitbox.contains(&event.position) {
if !hitbox.contains(&event.position) {
return;
}
let offset = self.offset_of_position(event.position, &text_hitbox);
let offset = self.offset_of_position(event.position, &hitbox);
if event.modifiers.shift {
self.select_to(offset, cx);
} else {
@ -399,12 +413,7 @@ impl TextInput {
}
}
fn on_drag_move(
&mut self,
event: &MouseMoveEvent,
text_hitbox: Hitbox,
cx: &mut ViewContext<Self>,
) {
fn on_drag_move(&mut self, event: &MouseMoveEvent, hitbox: Hitbox, cx: &mut ViewContext<Self>) {
// Ignore if text is empty
if self.text.is_empty() {
return;
@ -414,11 +423,11 @@ impl TextInput {
return;
}
if !text_hitbox.contains(&event.position) {
if !self.focus_handle.is_focused(cx) {
return;
}
let offset = self.offset_of_position(event.position, &text_hitbox);
let offset = self.offset_of_position(event.position, &hitbox);
if offset == self.cursor_offset() {
return;
}
@ -696,7 +705,7 @@ impl Element for TextElement {
point(bounds.left() + cursor_pos, bounds.top()),
size(px(1.5), bounds.bottom() - bounds.top()),
),
gpui::blue(),
crate::blue_500(),
)),
)
} else {
@ -789,6 +798,7 @@ impl Render for TextInput {
.on_double_click(cx.listener(|view, _, cx| {
view.select_all(&SelectAll, cx);
}))
.on_key_down(cx.listener(Self::on_key_down_for_blink_cursor))
.size_full()
.line_height(rems(1.25))
.text_size(rems(0.875))

View file

@ -1,6 +1,8 @@
use std::time::Duration;
use gpui::{ModelContext, Timer};
use gpui::{ModelContext, Timer, WeakView};
use super::TextInput;
/// To manage the Input cursor blinking.
///
@ -34,17 +36,21 @@ impl BlinkCursor {
}
self.started = true;
self.paused = false;
self.blink(self.blink_epoch, cx);
}
pub fn stop(&mut self, cx: &mut ModelContext<Self>) {
self.started = false;
cx.notify();
}
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 {
if self.paused || !self.started {
return;
}
@ -69,12 +75,35 @@ impl BlinkCursor {
}
pub fn visible(&self) -> bool {
// Keep showing the cursor if paused
if self.paused {
return true;
}
self.visible
}
/// Pause the blinking, and delay 500ms to resume the blinking.
pub fn pause(&mut self, cx: &mut ModelContext<Self>) {
self.paused = true;
self.started = false;
self.next_epoch();
cx.notify();
let epoch = self.next_epoch();
// delay 500ms to start the blinking
cx.spawn(|this, mut cx| async move {
Timer::after(Duration::from_secs_f64(0.5)).await;
if let Some(this) = this.upgrade() {
this.update(&mut cx, |this, cx| {
if epoch != this.blink_epoch {
return;
}
this.paused = false;
this.blink(epoch, cx);
})
.ok();
}
})
.detach();
}
}

View file

@ -250,7 +250,7 @@ impl Colors {
border: hsl(240.0, 3.7, 15.9),
input: hsl(240.0, 3.7, 15.9),
ring: hsl(240.0, 4.9, 83.9),
selection: hsl(211.0, 97.0, 85.0),
selection: hsl(211.0, 97.0, 22.0),
scrollbar: Hsla::transparent_black(),
scrollbar_thumb: hsl(240.0, 3.7, 15.9).opacity(0.7),
panel: hsl(299.0, 2., 9.),