flip cursor implementation to have an element and a state-entity
This commit is contained in:
@@ -11,7 +11,7 @@ mod storage;
|
||||
pub(self) mod unicode;
|
||||
|
||||
pub use colors::*;
|
||||
pub(self) use cursor::*;
|
||||
pub use cursor::*;
|
||||
pub use element::*;
|
||||
pub(self) use history::*;
|
||||
pub use layout::*;
|
||||
@@ -22,10 +22,5 @@ pub use storage::*;
|
||||
fn make_element(app: &mut gpui::App) -> impl gpui::IntoElement {
|
||||
use gpui::AppContext;
|
||||
let state = app.new(|cx| InputState::new(cx));
|
||||
let cursor = app.new(|cx| {
|
||||
let mut cursor = Cursor::new(None);
|
||||
cursor.subscribe_to(&state, cx);
|
||||
cursor
|
||||
});
|
||||
input(&state, app).cursor(cursor)
|
||||
input(&state, app).text_cursor(default_cursor(&state, app))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use gpui::{
|
||||
Bounds, Context, Element, Entity, EventEmitter, Hsla, IntoElement, Pixels, Point, Render,
|
||||
App, Bounds, Context, Element, Entity, EventEmitter, Hsla, IntoElement, Pixels, Point, Render,
|
||||
Subscription,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
@@ -13,35 +13,63 @@ pub const DEFAULT_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
/// The state of an input's cursor blinking. While active, the cursor's visibility changes at some interval.
|
||||
/// This blinking can be temporarily paused (e.g. during typing).
|
||||
pub struct Cursor {
|
||||
interval: Duration,
|
||||
generation: usize,
|
||||
visible: bool,
|
||||
active: bool,
|
||||
paused: bool,
|
||||
state: Entity<CursorState>,
|
||||
color: Hsla,
|
||||
/// Tracks whether we were focused on the last update.
|
||||
was_focused: bool,
|
||||
point: Point<Pixels>,
|
||||
height: Pixels,
|
||||
}
|
||||
|
||||
pub struct CursorState {
|
||||
interval: Duration,
|
||||
generation: usize,
|
||||
visible: bool,
|
||||
active: bool,
|
||||
paused: bool,
|
||||
#[allow(dead_code)]
|
||||
subscriptions: SmallVec<[Subscription; 2]>,
|
||||
}
|
||||
impl Default for CursorState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
interval: Duration::ZERO,
|
||||
generation: Default::default(),
|
||||
visible: true,
|
||||
active: Default::default(),
|
||||
paused: Default::default(),
|
||||
subscriptions: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn cursor(state: Entity<CursorState>) -> Cursor {
|
||||
Cursor::new(state)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn default_cursor<E>(emitter: &Entity<E>, cx: &mut App) -> Cursor
|
||||
where
|
||||
E: EventEmitter<CursorTrigger>,
|
||||
{
|
||||
use gpui::AppContext;
|
||||
cursor(cx.new(|cx| {
|
||||
let mut cursor = CursorState::default().blink_interval_default();
|
||||
cursor.subscribe_to(emitter, cx);
|
||||
cursor
|
||||
}))
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
/// Initializes the cursor blinking with the cursor already being visible.
|
||||
#[track_caller]
|
||||
pub fn new(interval: Option<Duration>) -> Self {
|
||||
fn new(state: Entity<CursorState>) -> Self {
|
||||
Self {
|
||||
interval: interval.unwrap_or_default(),
|
||||
generation: 0,
|
||||
visible: true,
|
||||
active: false,
|
||||
paused: false,
|
||||
state,
|
||||
color: Hsla::white(),
|
||||
was_focused: false,
|
||||
point: Point::default(),
|
||||
height: Pixels::ZERO,
|
||||
subscriptions: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,29 +77,38 @@ impl Cursor {
|
||||
self.color = color;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl CursorState {
|
||||
pub fn blink_interval_default(mut self) -> Self {
|
||||
self.interval = DEFAULT_BLINK_INTERVAL;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn blink_interval(mut self, interval: Duration) -> Self {
|
||||
self.interval = interval;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn subscribe_to<E>(&mut self, emitter: &Entity<E>, cx: &mut Context<Self>)
|
||||
where
|
||||
E: EventEmitter<CursorTrigger>,
|
||||
{
|
||||
let handle = cx.subscribe(emitter, |cursor, _emitter, event, cx| match event {
|
||||
let handle = cx.subscribe(emitter, |state, _emitter, event, cx| match event {
|
||||
CursorTrigger::PauseBlinkingForUserAction => {
|
||||
cursor.pause_blinking(cx);
|
||||
cx.notify();
|
||||
if !state.interval.is_zero() {
|
||||
state.pause_blinking(cx);
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
self.subscriptions.push(handle);
|
||||
}
|
||||
|
||||
/// Returns whether the cursor should currently be rendered.
|
||||
pub fn visible(&self) -> bool {
|
||||
self.visible
|
||||
}
|
||||
|
||||
/// Activates cursor blinking.
|
||||
///
|
||||
/// While active, the cursor will alternate between visible and hidden states at the configured interval. Has no effect if already active.
|
||||
pub fn enable(&mut self, cx: &mut Context<Self>) {
|
||||
fn enable(&mut self, cx: &mut Context<Self>) {
|
||||
if self.active {
|
||||
return;
|
||||
}
|
||||
@@ -86,7 +123,7 @@ impl Cursor {
|
||||
///
|
||||
/// Marks the cursor as invisible and pauses blinking indefinitely. `enable` must be called explicitly to resume visibility and blinking.
|
||||
/// Call `pause_blinking` instead if you want to temporarily stop blinking while keeping the cursor visible.
|
||||
pub fn disable(&mut self, cx: &mut Context<Self>) {
|
||||
fn disable(&mut self, cx: &mut Context<Self>) {
|
||||
self.active = false;
|
||||
self.visible = false;
|
||||
self.paused = false;
|
||||
@@ -94,7 +131,7 @@ impl Cursor {
|
||||
}
|
||||
|
||||
/// Temporarily pauses blinking and leaves the cursor visible. Blinking will resume after the pre-established interval elapses from the time this is called.
|
||||
pub fn pause_blinking(&mut self, cx: &mut Context<Self>) {
|
||||
fn pause_blinking(&mut self, cx: &mut Context<Self>) {
|
||||
if !self.visible {
|
||||
self.visible = true;
|
||||
cx.notify();
|
||||
@@ -142,13 +179,15 @@ impl Cursor {
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn update_input(
|
||||
&mut self,
|
||||
is_focused: bool,
|
||||
pos: Point<Pixels>,
|
||||
line_height: Pixels,
|
||||
cx: &mut Context<Self>,
|
||||
cx: &mut App,
|
||||
) -> bool {
|
||||
let was_focused = self.was_focused;
|
||||
self.was_focused = is_focused;
|
||||
@@ -156,17 +195,25 @@ impl Cursor {
|
||||
self.point = pos;
|
||||
self.height = line_height;
|
||||
|
||||
match (self.interval.is_zero(), is_focused, was_focused) {
|
||||
match (
|
||||
self.state.read(cx).interval.is_zero(),
|
||||
is_focused,
|
||||
was_focused,
|
||||
) {
|
||||
(true, _, _) => true,
|
||||
(false, true, false) => {
|
||||
self.enable(cx);
|
||||
self.state.update(cx, |state, cx| {
|
||||
state.enable(cx);
|
||||
});
|
||||
true
|
||||
}
|
||||
(false, false, true) => {
|
||||
self.disable(cx);
|
||||
self.state.update(cx, |state, cx| {
|
||||
state.disable(cx);
|
||||
});
|
||||
false
|
||||
}
|
||||
(false, _, _) => self.visible,
|
||||
(false, _, _) => self.state.read(cx).visible,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ pub struct Input {
|
||||
pub(super) interactivity: Interactivity,
|
||||
pub(super) placeholder: Option<SharedString>,
|
||||
pub(super) colors: InputColors,
|
||||
pub(super) cursor: Option<Entity<Cursor>>,
|
||||
pub(super) cursor: Option<Cursor>,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
@@ -159,8 +159,8 @@ impl Input {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cursor(mut self, entity: Entity<Cursor>) -> Self {
|
||||
self.cursor = Some(entity);
|
||||
pub fn text_cursor(mut self, cursor: Cursor) -> Self {
|
||||
self.cursor = Some(cursor);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,10 +67,9 @@ impl Element for Input {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(cursor) = &self.cursor {
|
||||
let (layout_id, layout) = cursor.update(cx, |cursor, cx| {
|
||||
cursor.request_layout(global_id, inspector_id, window, cx)
|
||||
});
|
||||
if let Some(cursor) = &mut self.cursor {
|
||||
let (layout_id, layout) =
|
||||
cursor.request_layout(global_id, inspector_id, window, cx);
|
||||
child_layout_ids.push(layout_id);
|
||||
cursor_layout = Some(layout);
|
||||
}
|
||||
@@ -133,16 +132,14 @@ impl Element for Input {
|
||||
window.with_element_offset(scroll_offset, |window| {
|
||||
match (&mut self.cursor, &mut layout_state.cursor_layout) {
|
||||
(Some(cursor), Some(layout)) => {
|
||||
let prepaint = cursor.update(cx, |cursor, cx| {
|
||||
cursor.prepaint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
layout,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let prepaint = cursor.prepaint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
layout,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
cursor_prepaint = Some(prepaint);
|
||||
}
|
||||
_ => {}
|
||||
@@ -211,26 +208,24 @@ impl Element for Input {
|
||||
&mut prepaint_state.cursor_prepaint,
|
||||
) {
|
||||
(Some(cursor), Some(layout), Some(prepaint)) => {
|
||||
cursor.update(cx, |cursor, cx| {
|
||||
let cursor_pos = context.find_cursor_position_in_layouts();
|
||||
let visible = cursor.update_input(
|
||||
is_focused,
|
||||
cursor_pos,
|
||||
context.snapshot.line_height,
|
||||
let cursor_pos = context.find_cursor_position_in_layouts();
|
||||
let visible = cursor.update_input(
|
||||
is_focused,
|
||||
cursor_pos,
|
||||
context.snapshot.line_height,
|
||||
cx,
|
||||
);
|
||||
if is_focused && visible && context.snapshot.selected_range.is_empty() {
|
||||
cursor.paint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
layout,
|
||||
prepaint,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
if is_focused && visible && context.snapshot.selected_range.is_empty() {
|
||||
cursor.paint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
layout,
|
||||
prepaint,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@ use unicode_segmentation::UnicodeSegmentation;
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum InputStateEvent {
|
||||
/// Emitted when the input gains focus.
|
||||
/// TODO: an emit was removed from element painting
|
||||
Focus,
|
||||
/// Emitted when the input loses focus.
|
||||
/// TODO: an emit was removed from element painting
|
||||
Blur,
|
||||
/// Emitted when the text content changes.
|
||||
TextChanged,
|
||||
@@ -30,7 +32,6 @@ impl EventEmitter<InputStateEvent> for InputState {}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CursorTrigger {
|
||||
// TODO: cursor needs to receive this
|
||||
PauseBlinkingForUserAction,
|
||||
}
|
||||
impl EventEmitter<CursorTrigger> for InputState {}
|
||||
|
||||
Reference in New Issue
Block a user