Move cursor entity to input element and set it up as an Element so it can render independently
This commit is contained in:
Generated
+1
@@ -2427,6 +2427,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-io",
|
||||
"gpui",
|
||||
"smallvec",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ ignored = ["gpui"]
|
||||
gpui.workspace = true
|
||||
async-io.workspace = true
|
||||
unicode-segmentation.workspace = true
|
||||
smallvec.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
||||
|
||||
@@ -4,6 +4,8 @@ use std::time::Duration;
|
||||
/// Default interval for cursor blinking.
|
||||
pub const DEFAULT_BLINK_INTERVAL: Duration = Duration::from_millis(500);
|
||||
|
||||
pub trait Cursor {}
|
||||
|
||||
/// Configuration for cursor blinking, to be provided to InputState.
|
||||
pub enum CursorBlinkType<'app> {
|
||||
/// The cursor will not blink.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::input::{InputColors, InputState};
|
||||
use crate::input::{Cursor, InputColors, InputState};
|
||||
use gpui::{
|
||||
Action, App, Context, Entity, FocusHandle, Focusable, Hsla, InteractiveElement, Interactivity,
|
||||
IntoElement, SharedString, StyleRefinement, Styled, Window,
|
||||
Action, AnyElement, App, Context, Entity, FocusHandle, Focusable, Hsla, InteractiveElement,
|
||||
Interactivity, IntoElement, SharedString, StyleRefinement, Styled, Window,
|
||||
};
|
||||
|
||||
#[track_caller]
|
||||
@@ -15,6 +15,7 @@ pub struct Input {
|
||||
pub(super) interactivity: Interactivity,
|
||||
pub(super) placeholder: Option<SharedString>,
|
||||
pub(super) colors: InputColors,
|
||||
pub(super) cursor: Option<AnyElement>,
|
||||
}
|
||||
|
||||
impl Input {
|
||||
@@ -26,6 +27,7 @@ impl Input {
|
||||
interactivity: Interactivity::new(),
|
||||
placeholder: None,
|
||||
colors: InputColors::default(),
|
||||
cursor: None,
|
||||
};
|
||||
input.register_actions();
|
||||
input
|
||||
@@ -163,6 +165,15 @@ impl Input {
|
||||
self.colors.marked = color;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn cursor<T>(mut self, entity: Entity<T>) -> Self
|
||||
where
|
||||
T: Cursor,
|
||||
Entity<T>: Into<AnyElement>,
|
||||
{
|
||||
self.cursor = Some(entity.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
fn register_action<A: Action>(
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use crate::input::{Input, InputColors, InputLayoutData, InputLogicalLine, InputState};
|
||||
use gpui::{
|
||||
Along, App, Axis, Bounds, ContentMask, CursorStyle, DispatchPhase, Element, ElementId,
|
||||
Along, App, Axis, Bounds, ContentMask, CursorStyle, DispatchPhase, Display, Element, ElementId,
|
||||
ElementInputHandler, Entity, Focusable, GlobalElementId, Hitbox, HitboxBehavior, Hsla,
|
||||
InspectorElementId, LayoutId, Length, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, Pixels, Point, ScrollWheelEvent, SharedString, Style, TextAlign, TextRun,
|
||||
TextStyle, Window, fill, point, px, relative, size,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::ops::Range;
|
||||
|
||||
const CURSOR_WIDTH: f32 = 2.0;
|
||||
@@ -13,6 +14,8 @@ const MARKED_TEXT_UNDERLINE_THICKNESS: f32 = 2.0;
|
||||
|
||||
pub struct InputLayoutState {
|
||||
text_style: TextStyle,
|
||||
#[allow(dead_code)]
|
||||
child_layout_ids: SmallVec<[LayoutId; 2]>,
|
||||
}
|
||||
|
||||
pub struct InputPrepaintState {
|
||||
@@ -39,6 +42,7 @@ impl Element for Input {
|
||||
cx: &mut App,
|
||||
) -> (LayoutId, Self::RequestLayoutState) {
|
||||
let mut resolved_text_style = None;
|
||||
let mut child_layout_ids = SmallVec::new();
|
||||
|
||||
let layout_id = self.interactivity.request_layout(
|
||||
global_id,
|
||||
@@ -46,12 +50,13 @@ impl Element for Input {
|
||||
window,
|
||||
cx,
|
||||
|element_style, window, cx| {
|
||||
let layout = self.input.read(cx).layout_style();
|
||||
window.with_text_style(element_style.text_style().cloned(), |window| {
|
||||
let state = self.input.read(cx);
|
||||
|
||||
resolved_text_style = Some(window.text_style());
|
||||
|
||||
let mut layout_style = element_style.clone();
|
||||
if matches!(layout, super::InputLayoutStyle::MultiLine) {
|
||||
if matches!(state.layout_style(), super::InputLayoutStyle::MultiLine) {
|
||||
if let Length::Auto = layout_style.size.width {
|
||||
layout_style.size.width = relative(1.).into();
|
||||
}
|
||||
@@ -59,13 +64,21 @@ impl Element for Input {
|
||||
layout_style.size.height = relative(1.).into();
|
||||
}
|
||||
}
|
||||
window.request_layout(layout_style, None, cx)
|
||||
|
||||
child_layout_ids = self
|
||||
.cursor
|
||||
.iter_mut()
|
||||
.map(|cursor| cursor.request_layout(window, cx))
|
||||
.collect::<SmallVec<_>>();
|
||||
|
||||
window.request_layout(layout_style, child_layout_ids.iter().copied(), cx)
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
let layout_state = InputLayoutState {
|
||||
text_style: resolved_text_style.unwrap_or_else(|| window.text_style()),
|
||||
child_layout_ids,
|
||||
};
|
||||
(layout_id, layout_state)
|
||||
}
|
||||
@@ -106,8 +119,19 @@ impl Element for Input {
|
||||
bounds.size,
|
||||
window,
|
||||
cx,
|
||||
|_style, _point, hitbox, window, _cx| {
|
||||
hitbox.or_else(|| Some(window.insert_hitbox(bounds, HitboxBehavior::Normal)))
|
||||
|style, scroll_offset, hitbox, window, cx| {
|
||||
let hitbox =
|
||||
hitbox.or_else(|| Some(window.insert_hitbox(bounds, HitboxBehavior::Normal)));
|
||||
|
||||
if style.display != Display::None {
|
||||
window.with_element_offset(scroll_offset, |window| {
|
||||
if let Some(cursor) = &mut self.cursor {
|
||||
cursor.prepaint(window, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
hitbox
|
||||
},
|
||||
);
|
||||
|
||||
@@ -146,7 +170,11 @@ impl Element for Input {
|
||||
input.toggle_cursor_on_focus_change(is_focused, cx)
|
||||
});
|
||||
|
||||
let perform_paint = |_style: &Style, window: &mut Window, cx: &mut App| {
|
||||
let perform_paint = |style: &Style, window: &mut Window, cx: &mut App| {
|
||||
if style.display == Display::None {
|
||||
return;
|
||||
}
|
||||
|
||||
let context = PaintContext {
|
||||
snapshot,
|
||||
is_focused,
|
||||
@@ -159,6 +187,10 @@ impl Element for Input {
|
||||
context.process_mouse_events(&self.input, window, cx);
|
||||
window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
context.paint(window, cx);
|
||||
|
||||
if let Some(cursor) = &mut self.cursor {
|
||||
cursor.paint(window, cx);
|
||||
}
|
||||
});
|
||||
};
|
||||
self.interactivity.paint(
|
||||
|
||||
@@ -2,8 +2,8 @@ use super::actions::*;
|
||||
use crate::input::{CursorBlinkType, InputLayoutStyle, InputStorage};
|
||||
use gpui::{
|
||||
App, AppContext, ClipboardItem, Context, Entity, EntityId, EntityInputHandler, EventEmitter,
|
||||
FocusHandle, Focusable, NavigationDirection, Pixels, Point, SharedString, Size, Subscription,
|
||||
TextRun, TextStyle, Window, WrappedLine, point, px,
|
||||
FocusHandle, Focusable, NavigationDirection, Pixels, Point, Render, SharedString, Size,
|
||||
Subscription, TextRun, TextStyle, Window, WrappedLine, point, px,
|
||||
};
|
||||
use std::{
|
||||
ops::Range,
|
||||
@@ -74,9 +74,6 @@ pub struct InputState {
|
||||
history_undo_stack: Vec<super::HistoryEntry>,
|
||||
/// Stack of undone states for redo.
|
||||
history_redo_stack: Vec<super::HistoryEntry>,
|
||||
|
||||
/// Optional entity and subscription tracking the blinking of the text cursor.
|
||||
cursor_blink: Option<(Entity<super::CursorBlink>, Subscription)>,
|
||||
}
|
||||
|
||||
/// Data built during element prepaint that is stored in InputState for conveinence
|
||||
@@ -126,7 +123,7 @@ impl InputState {
|
||||
/// Creates a new `Input` with the specified multiline setting.
|
||||
/// Cursor blinking is enabled by default.
|
||||
pub fn new(cx: &mut Context<Self>) -> Self {
|
||||
let mut this = Self {
|
||||
Self {
|
||||
entity_id: cx.entity_id(),
|
||||
focus_handle: cx.focus_handle(),
|
||||
content: Box::new(super::Standard::default()),
|
||||
@@ -148,30 +145,7 @@ impl InputState {
|
||||
history_grouping_interval: super::DEFAULT_GROUP_INTERVAL,
|
||||
history_undo_stack: Vec::new(),
|
||||
history_redo_stack: Vec::new(),
|
||||
|
||||
cursor_blink: None,
|
||||
};
|
||||
// TODO: This is unoptimal for non-blinking cases, since the entity is generated and then discarded.
|
||||
this = this.cursor_blink(CursorBlinkType::Enabled {
|
||||
app: cx,
|
||||
interval: None,
|
||||
});
|
||||
this
|
||||
}
|
||||
|
||||
/// Configure how often the cursor should blink when the input element has focus.
|
||||
pub fn cursor_blink<'app>(mut self, args: CursorBlinkType<'app>) -> Self {
|
||||
self.cursor_blink = match args {
|
||||
CursorBlinkType::Disabled => None,
|
||||
CursorBlinkType::Enabled { app: cx, interval } => {
|
||||
let interval = interval.unwrap_or(super::DEFAULT_BLINK_INTERVAL);
|
||||
let cursor_blink = cx.new(|_cx| super::CursorBlink::new(interval));
|
||||
let entity_id = self.entity_id;
|
||||
let subscription = cx.observe(&cursor_blink, move |_, cx| cx.notify(entity_id));
|
||||
Some((cursor_blink, subscription))
|
||||
}
|
||||
};
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current text content.
|
||||
|
||||
Reference in New Issue
Block a user