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:
parent
0d6d1c95d0
commit
41a83e6f6f
3 changed files with 65 additions and 26 deletions
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.),
|
||||
|
|
|
|||
Loading…
Reference in a new issue