merge element and state implementations back to a single type
This commit is contained in:
@@ -1,23 +1,17 @@
|
||||
pub mod actions;
|
||||
mod input_element;
|
||||
mod input_state;
|
||||
mod element;
|
||||
pub mod notify;
|
||||
mod shared_element;
|
||||
mod shared_state;
|
||||
mod state;
|
||||
mod storage;
|
||||
mod text_area_element;
|
||||
mod text_area_state;
|
||||
|
||||
pub use input_element::*;
|
||||
pub use input_state::*;
|
||||
pub use shared_state::*;
|
||||
pub use element::*;
|
||||
pub use state::*;
|
||||
pub use storage::*;
|
||||
pub use text_area_element::*;
|
||||
pub use text_area_state::*;
|
||||
|
||||
/* TODO list
|
||||
- disabled input/area
|
||||
- remove gpuikit based input
|
||||
- auto-scroll when cursor moves
|
||||
- cursor blinking
|
||||
- color styling configs
|
||||
- undo/redo
|
||||
@@ -27,6 +21,4 @@ pub use text_area_state::*;
|
||||
*/
|
||||
|
||||
/* Open questions:
|
||||
Is is practical and worthwhile to have separate element and state implementations?
|
||||
The only real difference is how some input-action handler functions are processed.
|
||||
*/
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
use gpui::{Action, Context, InteractiveElement, WeakEntity, Window};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
use gpui::{Action, AppContext, Context, InteractiveElement, WeakEntity, Window};
|
||||
|
||||
use crate::editable_text::StateBackedEditableText;
|
||||
|
||||
/// The key context used for input element keybindings.
|
||||
pub const DEFAULT_INPUT_CONTEXT: &str = "Input";
|
||||
|
||||
@@ -146,69 +143,59 @@ pub fn default_bindings() -> gpui::ActionBindingCollection {
|
||||
bindings
|
||||
}
|
||||
|
||||
pub trait EditableTextActionHandler<'app>: Sized {
|
||||
type Context: AppContext;
|
||||
pub trait EditableTextActionHandler<Context>: Sized {
|
||||
fn escape(&mut self, _: &Escape, _w: &mut Window, _cx: &mut Context) {}
|
||||
|
||||
fn escape(&mut self, _: &Escape, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn insert_tab(&mut self, _: &Tab, _w: &mut Window, _cx: &mut Context) {}
|
||||
|
||||
fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn insert_tab(&mut self, _: &Tab, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn backspace(&mut self, _: &Backspace, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn delete(&mut self, _: &Delete, _w: &mut Window, _cx: &mut Context) {}
|
||||
|
||||
fn backspace(&mut self, _: &Backspace, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn delete(&mut self, _: &Delete, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
|
||||
fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, _cx: &mut Self::Context) {
|
||||
}
|
||||
fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn delete_to_line_start(
|
||||
&mut self,
|
||||
_: &DeleteToBeginningOfLine,
|
||||
_w: &mut Window,
|
||||
_cx: &mut Self::Context,
|
||||
) {
|
||||
}
|
||||
fn delete_to_line_end(
|
||||
&mut self,
|
||||
_: &DeleteToEndOfLine,
|
||||
_w: &mut Window,
|
||||
_cx: &mut Self::Context,
|
||||
_cx: &mut Context,
|
||||
) {
|
||||
}
|
||||
fn delete_to_line_end(&mut self, _: &DeleteToEndOfLine, _w: &mut Window, _cx: &mut Context) {}
|
||||
|
||||
fn nav_left(&mut self, _: &Left, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_right(&mut self, _: &Right, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_up(&mut self, _: &Up, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_down(&mut self, _: &Down, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_line_start(&mut self, _: &Home, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_line_end(&mut self, _: &End, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn nav_left(&mut self, _: &Left, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn nav_right(&mut self, _: &Right, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn nav_up(&mut self, _: &Up, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn nav_down(&mut self, _: &Down, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn nav_line_start(&mut self, _: &Home, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn nav_line_end(&mut self, _: &End, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, _cx: &mut Context) {}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn select_right(&mut self, _: &SelectRight, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn select_up(&mut self, _: &SelectUp, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn select_down(&mut self, _: &SelectDown, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, _cx: &mut Self::Context) {
|
||||
}
|
||||
fn select_all(&mut self, _: &SelectAll, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn select_right(&mut self, _: &SelectRight, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn select_up(&mut self, _: &SelectUp, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn select_down(&mut self, _: &SelectDown, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, _cx: &mut Context) {}
|
||||
|
||||
fn cut(&mut self, _: &Cut, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn copy(&mut self, _: &Copy, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn paste(&mut self, _: &Paste, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn cut(&mut self, _: &Cut, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn copy(&mut self, _: &Copy, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn paste(&mut self, _: &Paste, _w: &mut Window, _cx: &mut Context) {}
|
||||
|
||||
fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Context) {}
|
||||
|
||||
fn show_character_palette(
|
||||
&mut self,
|
||||
_: &ShowCharacterPalette,
|
||||
window: &mut Window,
|
||||
_cx: &mut Self::Context,
|
||||
_cx: &mut Context,
|
||||
) {
|
||||
window.show_character_palette();
|
||||
}
|
||||
@@ -218,35 +205,30 @@ pub trait EditableTextActionHandler<'app>: Sized {
|
||||
_event: &gpui::MouseDownEvent,
|
||||
_text_position: gpui::Point<gpui::Pixels>,
|
||||
_w: &mut Window,
|
||||
_cx: &mut Self::Context,
|
||||
) {
|
||||
}
|
||||
fn on_mouse_up(
|
||||
&mut self,
|
||||
_event: &gpui::MouseUpEvent,
|
||||
_w: &mut Window,
|
||||
_cx: &mut Self::Context,
|
||||
_cx: &mut Context,
|
||||
) {
|
||||
}
|
||||
fn on_mouse_up(&mut self, _event: &gpui::MouseUpEvent, _w: &mut Window, _cx: &mut Context) {}
|
||||
fn on_mouse_move(
|
||||
&mut self,
|
||||
_event: &gpui::MouseMoveEvent,
|
||||
_text_position: gpui::Point<gpui::Pixels>,
|
||||
_w: &mut Window,
|
||||
_cx: &mut Self::Context,
|
||||
_cx: &mut Context,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) trait EditableTextActionElement: StateBackedEditableText {
|
||||
fn state_entity_rc(&self) -> &Rc<RefCell<WeakEntity<Self::State>>>;
|
||||
pub(super) trait EditableTextActionElement<State> {
|
||||
fn state_entity_rc(&self) -> &Rc<RefCell<WeakEntity<State>>>;
|
||||
|
||||
fn register_action<ActionType>(
|
||||
&mut self,
|
||||
listener: fn(&mut Self::State, &ActionType, &mut Window, &mut Context<Self::State>),
|
||||
listener: fn(&mut State, &ActionType, &mut Window, &mut Context<State>),
|
||||
) where
|
||||
Self: InteractiveElement,
|
||||
ActionType: Action + std::fmt::Debug,
|
||||
State: 'static,
|
||||
{
|
||||
let entity_rc = self.state_entity_rc().clone();
|
||||
self.interactivity()
|
||||
@@ -263,10 +245,9 @@ pub(super) trait EditableTextActionElement: StateBackedEditableText {
|
||||
fn register_actions(&mut self)
|
||||
where
|
||||
Self: InteractiveElement,
|
||||
Self::State:
|
||||
for<'app> EditableTextActionHandler<'app, Context = gpui::Context<'app, Self::State>>,
|
||||
State: for<'app> EditableTextActionHandler<gpui::Context<'app, State>>,
|
||||
State: 'static,
|
||||
{
|
||||
use super::actions::*;
|
||||
self.register_action(|state, action, window, cx| state.escape(action, window, cx));
|
||||
self.register_action(|state, action, window, cx| state.insert_enter(action, window, cx));
|
||||
self.register_action(|state, action, window, cx| state.insert_tab(action, window, cx));
|
||||
|
||||
+138
-33
@@ -1,15 +1,112 @@
|
||||
use crate::editable_text::{
|
||||
TextInputLayoutData, TextLineSegment,
|
||||
actions::{EditableTextActionElement, EditableTextActionHandler},
|
||||
EditableTextState, InitStorage, TextInputLayoutData, TextLineSegment,
|
||||
actions::{DEFAULT_INPUT_CONTEXT, EditableTextActionElement, EditableTextActionHandler},
|
||||
};
|
||||
use gpui::{
|
||||
App, Bounds, Context, CursorStyle, DispatchPhase, Display, ElementInputHandler, Entity,
|
||||
FocusHandle, Focusable, Hitbox, HitboxBehavior, Hsla, InteractiveElement, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, PaintQuad, Pixels, Point, SharedString, Size,
|
||||
Style, TextAlign, TextLayout, Window, WrappedLine, fill, point, px, size,
|
||||
App, Bounds, CursorStyle, DispatchPhase, Display, Element, ElementId, ElementInputHandler,
|
||||
Entity, FocusHandle, Focusable, Hitbox, HitboxBehavior, Hsla, InteractiveElement,
|
||||
Interactivity, IntoElement, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||
PaintQuad, Pixels, Point, SharedString, Size, StatefulInteractiveElement, Style,
|
||||
StyleRefinement, Styled, TextAlign, TextLayout, WeakEntity, Window, WrappedLine, fill, point,
|
||||
px, size,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use std::{cell::RefCell, ops::Range, rc::Rc, sync::Arc};
|
||||
|
||||
#[track_caller]
|
||||
pub fn editable_text(id: impl Into<ElementId>) -> EditableTextElement {
|
||||
let mut this = EditableTextElement {
|
||||
interactivity: Interactivity::new(),
|
||||
state_entity: Rc::new(RefCell::new(WeakEntity::new_invalid())),
|
||||
supports_multiline: true,
|
||||
init_storage: InitStorage::default(),
|
||||
placeholder: None,
|
||||
};
|
||||
this.interactivity.element_id = Some(id.into());
|
||||
|
||||
this = this.key_context(DEFAULT_INPUT_CONTEXT);
|
||||
this.register_actions();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn text_input(id: impl Into<ElementId>) -> EditableTextElement {
|
||||
editable_text(id).multiline(false)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub fn text_area(id: impl Into<ElementId>) -> EditableTextElement {
|
||||
editable_text(id).multiline(true)
|
||||
}
|
||||
|
||||
// TODO: Disabled flag/state?
|
||||
pub struct EditableTextElement {
|
||||
interactivity: Interactivity,
|
||||
// Populated on first render with an entity stored/attached to the view.
|
||||
// This reference is shared with the action handlers, which are processed between renders
|
||||
// and therefore cannot otherwise access state attached to the view.
|
||||
state_entity: Rc<RefCell<WeakEntity<EditableTextState>>>,
|
||||
init_storage: InitStorage,
|
||||
supports_multiline: bool,
|
||||
placeholder: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl EditableTextElement {
|
||||
pub fn multiline(mut self, enabled: bool) -> Self {
|
||||
self.supports_multiline = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn placeholder(mut self, text: impl Into<SharedString>) -> Self {
|
||||
self.placeholder = Some(text.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Swaps the default storage container (standard String) with a custom initializer of [`UnicodeTextStorage`].
|
||||
pub fn with_storage(mut self, fn_init: impl Into<InitStorage>) -> Self {
|
||||
self.init_storage = fn_init.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Swaps the default storage container. The new initializer is a standard String using the provided value.
|
||||
///
|
||||
/// Incompatible with [`with_storage`] (they establish the same internal value).
|
||||
/// If you initialize custom storage, you should be able to initialize its default value.
|
||||
pub fn default_value(mut self, value: impl Into<String>) -> Self {
|
||||
let storage = super::StringStorage::from(value.into());
|
||||
self.init_storage = InitStorage::new_typed(move |_cx| storage.clone());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for EditableTextElement {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
&mut self.interactivity
|
||||
}
|
||||
}
|
||||
|
||||
// forced implementation since the API for the element doesnt use Stateful<Element>
|
||||
impl StatefulInteractiveElement for EditableTextElement {}
|
||||
|
||||
impl Styled for EditableTextElement {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.interactivity.base_style
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for EditableTextElement {
|
||||
type Element = Self;
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextActionElement<EditableTextState> for EditableTextElement {
|
||||
fn state_entity_rc(&self) -> &Rc<RefCell<WeakEntity<EditableTextState>>> {
|
||||
&self.state_entity
|
||||
}
|
||||
}
|
||||
|
||||
pub enum PrepaintElement {
|
||||
Line {
|
||||
@@ -48,23 +145,32 @@ pub struct PrepaintState {
|
||||
pub caret_visible: bool,
|
||||
}
|
||||
|
||||
pub trait EditableTextElement: InteractiveElement + EditableTextActionElement {
|
||||
fn init_state(&self, cx: &mut Context<Self::State>) -> Self::State;
|
||||
fn placeholder(&self) -> &Option<SharedString>;
|
||||
impl Element for EditableTextElement {
|
||||
type RequestLayoutState = LayoutState<EditableTextState>;
|
||||
type PrepaintState = PrepaintState;
|
||||
|
||||
fn shared_request_layout(
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
self.interactivity.element_id.clone()
|
||||
}
|
||||
|
||||
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
|
||||
self.interactivity.source_location()
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::App,
|
||||
) -> (gpui::LayoutId, LayoutState<Self::State>) {
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
// Fetches or initializes the internal state of the field
|
||||
let state = match &self.interactivity().element_id {
|
||||
None => unimplemented!("all input elements must be assigned an id"),
|
||||
Some(element_id) => {
|
||||
let state = window
|
||||
.use_keyed_state(element_id.clone(), cx, |_window, cx| self.init_state(cx));
|
||||
let state = window.use_keyed_state(element_id.clone(), cx, |_window, cx| {
|
||||
EditableTextState::new(self.init_storage.exec(cx), cx)
|
||||
});
|
||||
// store a reference to the entity owned by the element for access in action handlers
|
||||
*self.state_entity_rc().borrow_mut() = state.downgrade();
|
||||
state
|
||||
@@ -84,8 +190,9 @@ pub trait EditableTextElement: InteractiveElement + EditableTextActionElement {
|
||||
// TODO: This required a gpui api change in order to sync the focus handle between Interactivity and TextInputStateBase
|
||||
self.interactivity().track_focus(focus_handle);
|
||||
|
||||
let placeholder = self.placeholder().clone();
|
||||
let placeholder = self.placeholder.clone();
|
||||
let placeholder_color = Hsla::white().opacity(0.5); // TODO: as an element param
|
||||
let supports_multiline = self.supports_multiline;
|
||||
let layout_id = self.interactivity().request_layout(
|
||||
global_id,
|
||||
inspector_id,
|
||||
@@ -209,6 +316,7 @@ pub trait EditableTextElement: InteractiveElement + EditableTextActionElement {
|
||||
}
|
||||
|
||||
let layout_data = TextInputLayoutData {
|
||||
supports_multiline,
|
||||
wrap_width,
|
||||
size: Some(size),
|
||||
last_seen_storage_version,
|
||||
@@ -235,15 +343,15 @@ pub trait EditableTextElement: InteractiveElement + EditableTextActionElement {
|
||||
(layout_id, layout_state)
|
||||
}
|
||||
|
||||
fn shared_prepaint(
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
bounds: gpui::Bounds<gpui::Pixels>,
|
||||
request_layout: &mut LayoutState<Self::State>,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::App,
|
||||
) -> PrepaintState {
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
// should reflect the text content layout size of the stored text,
|
||||
// so that scrolling can take it into account during prepaint.
|
||||
let content_size = {
|
||||
@@ -430,19 +538,16 @@ pub trait EditableTextElement: InteractiveElement + EditableTextActionElement {
|
||||
}
|
||||
}
|
||||
|
||||
fn shared_paint(
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
bounds: gpui::Bounds<gpui::Pixels>,
|
||||
request_layout: &mut LayoutState<Self::State>,
|
||||
prepaint: &mut PrepaintState,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::App,
|
||||
) where
|
||||
Self::State:
|
||||
for<'app> EditableTextActionHandler<'app, Context = Context<'app, Self::State>>,
|
||||
{
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
if let Some(hitbox) = &prepaint.hitbox {
|
||||
window.set_cursor_style(CursorStyle::IBeam, hitbox);
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
use crate::editable_text::{
|
||||
EditableTextInputState, InitStorage, StateBackedEditableText,
|
||||
actions::{DEFAULT_INPUT_CONTEXT, EditableTextActionElement},
|
||||
shared_element::{self, EditableTextElement},
|
||||
};
|
||||
use gpui::{
|
||||
App, Bounds, Element, ElementId, InteractiveElement, Interactivity, IntoElement, Pixels,
|
||||
SharedString, StatefulInteractiveElement, StyleRefinement, Styled, WeakEntity, Window,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
#[track_caller]
|
||||
pub fn input(id: impl Into<ElementId>) -> TextInputElement {
|
||||
let mut this = TextInputElement {
|
||||
interactivity: Interactivity::new(),
|
||||
state_entity: Rc::new(RefCell::new(WeakEntity::new_invalid())),
|
||||
init_storage: InitStorage::default(),
|
||||
placeholder: None,
|
||||
};
|
||||
this.interactivity.element_id = Some(id.into());
|
||||
|
||||
this = this.key_context(DEFAULT_INPUT_CONTEXT);
|
||||
this.register_actions();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
// TODO: Disabled flag/state?
|
||||
pub struct TextInputElement {
|
||||
interactivity: Interactivity,
|
||||
// Populated on first render with an entity stored/attached to the view.
|
||||
// This reference is shared with the action handlers, which are processed between renders
|
||||
// and therefore cannot otherwise access state attached to the view.
|
||||
state_entity: Rc<RefCell<WeakEntity<EditableTextInputState>>>,
|
||||
init_storage: InitStorage,
|
||||
placeholder: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl TextInputElement {
|
||||
pub fn placeholder(mut self, text: impl Into<SharedString>) -> Self {
|
||||
self.placeholder = Some(text.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Swaps the default storage container (standard String) with a custom initializer of [`UnicodeTextStorage`].
|
||||
pub fn with_storage(mut self, fn_init: impl Into<InitStorage>) -> Self {
|
||||
self.init_storage = fn_init.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Swaps the default storage container. The new initializer is a standard String using the provided value.
|
||||
///
|
||||
/// Incompatible with [`with_storage`] (they establish the same internal value).
|
||||
/// If you initialize custom storage, you should be able to initialize its default value.
|
||||
pub fn default_value(mut self, value: impl Into<String>) -> Self {
|
||||
let storage = super::StringStorage::from(value.into());
|
||||
self.init_storage = InitStorage::new_typed(move |_cx| storage.clone());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for TextInputElement {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
&mut self.interactivity
|
||||
}
|
||||
}
|
||||
|
||||
// forced implementation since the API for the element doesnt use Stateful<Element>
|
||||
impl StatefulInteractiveElement for TextInputElement {}
|
||||
|
||||
impl Styled for TextInputElement {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.interactivity.base_style
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for TextInputElement {
|
||||
type Element = Self;
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl StateBackedEditableText for TextInputElement {
|
||||
type State = EditableTextInputState;
|
||||
}
|
||||
|
||||
impl EditableTextActionElement for TextInputElement {
|
||||
fn state_entity_rc(&self) -> &Rc<RefCell<WeakEntity<Self::State>>> {
|
||||
&self.state_entity
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextElement for TextInputElement {
|
||||
fn init_state(&self, cx: &mut gpui::prelude::Context<Self::State>) -> Self::State {
|
||||
Self::State::new(self.init_storage.exec(cx), cx)
|
||||
}
|
||||
|
||||
fn placeholder(&self) -> &Option<SharedString> {
|
||||
&self.placeholder
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for TextInputElement {
|
||||
type RequestLayoutState = shared_element::LayoutState<EditableTextInputState>;
|
||||
type PrepaintState = shared_element::PrepaintState;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
self.interactivity.element_id.clone()
|
||||
}
|
||||
|
||||
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
|
||||
self.interactivity.source_location()
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
self.shared_request_layout(global_id, inspector_id, window, cx)
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
self.shared_prepaint(global_id, inspector_id, bounds, request_layout, window, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.shared_paint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
request_layout,
|
||||
prepaint,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,365 +0,0 @@
|
||||
use super::notify::TextHistoryPushed;
|
||||
use crate::editable_text::{
|
||||
EditableTextStateBase, EditableTextStateNotifier, TextBoundary, UnicodeTextStorage,
|
||||
notify::TextChanged,
|
||||
};
|
||||
use gpui::{
|
||||
Bounds, Context, EntityInputHandler, EventEmitter, NavigationDirection, Pixels, Point,
|
||||
UTF16Selection, Window,
|
||||
};
|
||||
use std::ops::Range;
|
||||
|
||||
pub struct EditableTextInputState {
|
||||
internal: EditableTextStateBase,
|
||||
}
|
||||
|
||||
impl EventEmitter<TextChanged> for EditableTextInputState {}
|
||||
impl EventEmitter<TextHistoryPushed> for EditableTextInputState {}
|
||||
|
||||
impl std::ops::Deref for EditableTextInputState {
|
||||
type Target = EditableTextStateBase;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.internal
|
||||
}
|
||||
}
|
||||
impl std::ops::DerefMut for EditableTextInputState {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.internal
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextInputState {
|
||||
pub fn new(storage: impl Into<Box<dyn UnicodeTextStorage>>, cx: &mut Context<Self>) -> Self {
|
||||
let internal = EditableTextStateBase::new(storage, cx);
|
||||
Self { internal }
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextStateNotifier for Context<'_, EditableTextInputState> {
|
||||
fn notify_changed(&mut self) {
|
||||
self.notify();
|
||||
}
|
||||
|
||||
fn emit_text_changed(&mut self, event: TextChanged) {
|
||||
self.emit(event);
|
||||
}
|
||||
|
||||
fn emit_history(&mut self, event: TextHistoryPushed) {
|
||||
self.emit(event);
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityInputHandler for EditableTextInputState {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<String> {
|
||||
self.internal
|
||||
.ime_text_for_range(range_utf16, adjusted_range)
|
||||
}
|
||||
|
||||
fn selected_text_range(
|
||||
&mut self,
|
||||
ignore_disabled_input: bool,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<UTF16Selection> {
|
||||
self.internal.ime_selected_text_range(ignore_disabled_input)
|
||||
}
|
||||
|
||||
fn marked_text_range(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<Range<usize>> {
|
||||
self.internal.ime_marked_text_range()
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||
self.internal.ime_unmark_text();
|
||||
}
|
||||
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
text_to_insert: &str,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let range_utf8 = self.internal.ime_resolve_range(range_utf16);
|
||||
self.internal
|
||||
.replace_text_in_range_bytes(range_utf8, text_to_insert, cx);
|
||||
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
||||
cx.emit_text_changed(TextChanged);
|
||||
cx.notify_changed();
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
text_to_insert: &str,
|
||||
new_selected_range_utf16: Option<Range<usize>>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let range = self.internal.ime_resolve_range(range_utf16);
|
||||
self.internal
|
||||
.replace_text_in_range_bytes(range.clone(), text_to_insert, cx);
|
||||
self.internal
|
||||
.ime_mark_text_in_range(&range, text_to_insert.len());
|
||||
self.internal.ime_mark_selected_range(
|
||||
&range,
|
||||
&new_selected_range_utf16,
|
||||
text_to_insert.len(),
|
||||
);
|
||||
cx.emit_text_changed(TextChanged);
|
||||
cx.notify_changed();
|
||||
}
|
||||
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<Bounds<Pixels>> {
|
||||
self.internal
|
||||
.ime_bounds_for_range(range_utf16, bounds, window)
|
||||
}
|
||||
|
||||
fn character_index_for_point(
|
||||
&mut self,
|
||||
point: Point<Pixels>,
|
||||
window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<usize> {
|
||||
let index = self
|
||||
.internal
|
||||
.index_for_pixel_point(point, window.line_height());
|
||||
Some(self.internal.storage().utf_offset_8to16(index))
|
||||
}
|
||||
}
|
||||
|
||||
use super::actions::*;
|
||||
impl<'app> EditableTextActionHandler<'app> for EditableTextInputState {
|
||||
type Context = gpui::Context<'app, Self>;
|
||||
|
||||
fn escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.set_selected_range(0..0);
|
||||
cx.notify();
|
||||
|
||||
window.blur();
|
||||
}
|
||||
|
||||
fn insert_enter(&mut self, _: &Enter, _w: &mut Window, _cx: &mut Self::Context) {}
|
||||
|
||||
fn insert_tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Self::Context) {
|
||||
self.replace_text_in_range(None, "\t", window, cx);
|
||||
}
|
||||
|
||||
fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn delete(&mut self, _: &Delete, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn delete_to_line_start(
|
||||
&mut self,
|
||||
_: &DeleteToBeginningOfLine,
|
||||
_w: &mut Window,
|
||||
cx: &mut Self::Context,
|
||||
) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn delete_to_line_end(
|
||||
&mut self,
|
||||
_: &DeleteToEndOfLine,
|
||||
_w: &mut Window,
|
||||
cx: &mut Self::Context,
|
||||
) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_left(&mut self, _: &Left, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn nav_right(&mut self, _: &Right, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn nav_up(&mut self, _: &Up, _w: &mut Window, cx: &mut Self::Context) {
|
||||
// semantically equivalent to line
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_down(&mut self, _: &Down, _w: &mut Window, cx: &mut Self::Context) {
|
||||
// semantically equivalent to line
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_line_start(&mut self, _: &Home, _w: &mut Window, cx: &mut Self::Context) {
|
||||
// semantically equivalent to document
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_line_end(&mut self, _: &End, _w: &mut Window, cx: &mut Self::Context) {
|
||||
// semantically equivalent to document
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.select_all(cx);
|
||||
}
|
||||
|
||||
fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn select_right(&mut self, _: &SelectRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn select_up(&mut self, _: &SelectUp, _w: &mut Window, cx: &mut Self::Context) {
|
||||
// semantically equivalent to select document
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn select_down(&mut self, _: &SelectDown, _w: &mut Window, cx: &mut Self::Context) {
|
||||
// semantically equivalent to select document
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn cut(&mut self, _: &Cut, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.cut(cx);
|
||||
}
|
||||
|
||||
fn copy(&mut self, _: &Copy, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.copy(cx);
|
||||
}
|
||||
|
||||
fn paste(&mut self, _: &Paste, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.paste(cx);
|
||||
}
|
||||
|
||||
fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Self::Context) {
|
||||
// TODO: STUB
|
||||
}
|
||||
|
||||
fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Self::Context) {
|
||||
// TODO: STUB
|
||||
}
|
||||
|
||||
fn on_mouse_down(
|
||||
&mut self,
|
||||
event: &gpui::MouseDownEvent,
|
||||
text_position: gpui::Point<gpui::Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut Self::Context,
|
||||
) {
|
||||
let character_pos = self
|
||||
.internal
|
||||
.index_for_pixel_point(text_position, window.line_height());
|
||||
self.internal.on_mouse_down(
|
||||
text_position,
|
||||
character_pos,
|
||||
event.click_count,
|
||||
event.modifiers.shift,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn on_mouse_up(
|
||||
&mut self,
|
||||
_event: &gpui::MouseUpEvent,
|
||||
_w: &mut Window,
|
||||
_cx: &mut Self::Context,
|
||||
) {
|
||||
self.internal.on_mouse_up();
|
||||
}
|
||||
|
||||
fn on_mouse_move(
|
||||
&mut self,
|
||||
_event: &gpui::MouseMoveEvent,
|
||||
text_position: Point<Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut Self::Context,
|
||||
) {
|
||||
let character_pos = self
|
||||
.internal
|
||||
.index_for_pixel_point(text_position, window.line_height());
|
||||
self.internal.on_mouse_move(character_pos, cx);
|
||||
}
|
||||
}
|
||||
+411
-163
@@ -4,26 +4,12 @@ use crate::editable_text::{
|
||||
notify::{TextChanged, TextHistoryPushed},
|
||||
};
|
||||
use gpui::{
|
||||
App, Bounds, ClipboardItem, EntityInputHandler, FocusHandle, Focusable, NavigationDirection,
|
||||
Pixels, Point, Size, UTF16Selection, Window, WrappedLine, point,
|
||||
App, Bounds, ClipboardItem, Context, EntityInputHandler, EventEmitter, FocusHandle, Focusable,
|
||||
NavigationDirection, Pixels, Point, Size, UTF16Selection, Window, WrappedLine, point,
|
||||
};
|
||||
use std::{ops::Range, sync::Arc};
|
||||
|
||||
pub trait EditableTextStateNotifier {
|
||||
fn notify_changed(&mut self);
|
||||
fn emit_text_changed(&mut self, event: TextChanged);
|
||||
fn emit_history(&mut self, event: TextHistoryPushed);
|
||||
}
|
||||
|
||||
pub trait StateBackedEditableText {
|
||||
type State: 'static
|
||||
+ std::ops::Deref<Target = EditableTextStateBase>
|
||||
+ std::ops::DerefMut
|
||||
+ EntityInputHandler
|
||||
+ for<'app> EditableTextActionHandler<'app>;
|
||||
}
|
||||
|
||||
pub struct EditableTextStateBase {
|
||||
pub struct EditableTextState {
|
||||
storage: Box<dyn UnicodeTextStorage>,
|
||||
|
||||
/// The utf-8 character range that is currently selected by the user.
|
||||
@@ -51,6 +37,7 @@ pub struct EditableTextStateBase {
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct TextInputLayoutData {
|
||||
pub supports_multiline: bool,
|
||||
/// The last known width at which the lines were wrapped.
|
||||
pub wrap_width: Option<Pixels>,
|
||||
/// The last known size of the text, as generated during layout.
|
||||
@@ -85,14 +72,17 @@ impl TextLineSegment {
|
||||
}
|
||||
}
|
||||
|
||||
impl Focusable for EditableTextStateBase {
|
||||
impl EventEmitter<TextChanged> for EditableTextState {}
|
||||
impl EventEmitter<TextHistoryPushed> for EditableTextState {}
|
||||
|
||||
impl Focusable for EditableTextState {
|
||||
fn focus_handle(&self, _: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextStateBase {
|
||||
pub fn new(storage: impl Into<Box<dyn UnicodeTextStorage>>, cx: &mut App) -> Self {
|
||||
impl EditableTextState {
|
||||
pub fn new(storage: impl Into<Box<dyn UnicodeTextStorage>>, cx: &mut Context<Self>) -> Self {
|
||||
Self {
|
||||
storage: storage.into(),
|
||||
|
||||
@@ -141,7 +131,7 @@ impl EditableTextStateBase {
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextStateBase {
|
||||
impl EditableTextState {
|
||||
/// Returns the utf-8 character position of the start of the line that contains the provided pixel-point.
|
||||
pub fn index_for_pixel_point(&self, point: Point<Pixels>, line_height: Pixels) -> usize {
|
||||
let storage_len_utf8 = self.storage.content_utf8().len();
|
||||
@@ -177,39 +167,8 @@ impl EditableTextStateBase {
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextStateBase {
|
||||
pub fn ime_text_for_range(
|
||||
&self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
) -> Option<String> {
|
||||
let range = self.storage.utf_range_16to8(&range_utf16);
|
||||
let storage_len_utf8 = self.storage.content_utf8().len();
|
||||
let clamped_range = range.start.min(storage_len_utf8)..range.end.min(storage_len_utf8);
|
||||
adjusted_range.replace(self.storage.utf_range_8to16(&clamped_range));
|
||||
Some(self.storage.content_utf8()[clamped_range].to_string())
|
||||
}
|
||||
|
||||
pub fn ime_selected_text_range(&self, _ignore_disabled_input: bool) -> Option<UTF16Selection> {
|
||||
let selection_range = self.selected_range();
|
||||
let direction = self.selection_direction();
|
||||
Some(UTF16Selection {
|
||||
range: self.storage.utf_range_8to16(&selection_range),
|
||||
reversed: direction == Some(NavigationDirection::Back),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ime_marked_text_range(&self) -> Option<Range<usize>> {
|
||||
self.marked_range
|
||||
.as_ref()
|
||||
.map(|range| self.storage.utf_range_8to16(range))
|
||||
}
|
||||
|
||||
pub fn ime_unmark_text(&mut self) {
|
||||
self.marked_range = None;
|
||||
}
|
||||
|
||||
pub fn ime_resolve_range(&self, range_utf16: Option<Range<usize>>) -> Range<usize> {
|
||||
impl EditableTextState {
|
||||
fn ime_resolve_range(&self, range_utf16: Option<Range<usize>>) -> Range<usize> {
|
||||
// Use a series of fallbacks to pick the range to operate on.
|
||||
// Fallback order: IME provided range, active IME marked range, selection
|
||||
let range = range_utf16.map(|range_utf16| self.storage.utf_range_16to8(&range_utf16));
|
||||
@@ -230,13 +189,8 @@ impl EditableTextStateBase {
|
||||
self.selected_range = new_caret..new_caret;
|
||||
}
|
||||
|
||||
fn emit_change_for_undo(
|
||||
&self,
|
||||
cx: &mut impl EditableTextStateNotifier,
|
||||
range: Range<usize>,
|
||||
length: usize,
|
||||
) {
|
||||
cx.emit_history(TextHistoryPushed::new(
|
||||
fn emit_change_for_undo(&self, cx: &mut Context<Self>, range: Range<usize>, length: usize) {
|
||||
cx.emit(TextHistoryPushed::new(
|
||||
range.clone(),
|
||||
length,
|
||||
&*self.storage,
|
||||
@@ -248,7 +202,7 @@ impl EditableTextStateBase {
|
||||
&mut self,
|
||||
range: Range<usize>,
|
||||
mut text_to_insert: &str,
|
||||
cx: &mut impl EditableTextStateNotifier,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
// TODO: Apply text sanitization
|
||||
// single-line fields should prune \n and \r
|
||||
@@ -272,14 +226,14 @@ impl EditableTextStateBase {
|
||||
self.marked_range = None;
|
||||
}
|
||||
|
||||
pub fn ime_mark_text_in_range(&mut self, range: &Range<usize>, text_len: usize) {
|
||||
fn ime_mark_text_in_range(&mut self, range: &Range<usize>, text_len: usize) {
|
||||
self.marked_range = match text_len {
|
||||
0 => None,
|
||||
_ => Some(range.start..range.start + text_len),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ime_mark_selected_range(
|
||||
fn ime_mark_selected_range(
|
||||
&mut self,
|
||||
range_overwritten: &Range<usize>,
|
||||
new_selected_range_utf16: &Option<Range<usize>>,
|
||||
@@ -298,84 +252,30 @@ impl EditableTextStateBase {
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ime_bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
window: &mut Window,
|
||||
) -> Option<Bounds<Pixels>> {
|
||||
let range = self.storage.utf_range_16to8(&range_utf16);
|
||||
let line_height = window.line_height();
|
||||
|
||||
for line in &self.layout_data.lines {
|
||||
let y_offset = line.pos_y * line_height;
|
||||
if line.text_range.is_empty() {
|
||||
if range.start == line.text_range.start {
|
||||
return Some(Bounds::from_corners(
|
||||
bounds.origin + point(Pixels::ZERO, y_offset),
|
||||
bounds.origin + point(gpui::px(4.), y_offset + line_height),
|
||||
));
|
||||
}
|
||||
} else if line.text_range.contains(&range.start) {
|
||||
if let Some(wrapped) = &line.wrapped_line {
|
||||
let local_start = range.start - line.text_range.start;
|
||||
let local_end = (range.end - line.text_range.start).min(wrapped.text.len());
|
||||
|
||||
let start_pos = wrapped
|
||||
.position_for_index(local_start, line_height)
|
||||
.unwrap_or(point(Pixels::ZERO, Pixels::ZERO));
|
||||
let end_pos = wrapped
|
||||
.position_for_index(local_end, line_height)
|
||||
.unwrap_or_else(|| {
|
||||
let last_line_y = line_height * (line.num_visual_lines - 1) as f32;
|
||||
point(wrapped.width(), last_line_y)
|
||||
});
|
||||
|
||||
let start_visual_line = (start_pos.y / line_height).floor() as usize;
|
||||
let end_visual_line = (end_pos.y / line_height).floor() as usize;
|
||||
|
||||
if start_visual_line == end_visual_line {
|
||||
return Some(Bounds::from_corners(
|
||||
bounds.origin + start_pos + point(Pixels::ZERO, y_offset),
|
||||
bounds.origin + point(end_pos.x, y_offset + start_pos.y + line_height),
|
||||
));
|
||||
} else {
|
||||
return Some(Bounds::from_corners(
|
||||
bounds.origin + start_pos + point(Pixels::ZERO, y_offset),
|
||||
bounds.origin
|
||||
+ point(wrapped.width(), y_offset + start_pos.y + line_height),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextStateBase {
|
||||
pub fn move_to(&mut self, caret_pos: usize, cx: &mut impl EditableTextStateNotifier) {
|
||||
impl EditableTextState {
|
||||
pub fn move_to(&mut self, caret_pos: usize, cx: &mut Context<Self>) {
|
||||
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
||||
let caret_pos = caret_pos.min(self.storage.content_utf8().len());
|
||||
self.selected_range = caret_pos..caret_pos;
|
||||
//self.scroll_to_cursor();
|
||||
cx.notify_changed();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn select_to(&mut self, caret_pos: usize, cx: &mut impl EditableTextStateNotifier) {
|
||||
pub fn select_to(&mut self, caret_pos: usize, cx: &mut Context<Self>) {
|
||||
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
||||
let caret_pos = caret_pos.min(self.storage().content_utf8().len());
|
||||
self.selected_range.start = caret_pos;
|
||||
//self.scroll_to_cursor();
|
||||
cx.notify_changed();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn delete(
|
||||
pub fn delete_linear(
|
||||
&mut self,
|
||||
direction: NavigationDirection,
|
||||
boundary: TextBoundary,
|
||||
cx: &mut impl EditableTextStateNotifier,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let range = self.selected_range();
|
||||
let range = match range.is_empty() {
|
||||
@@ -390,15 +290,15 @@ impl EditableTextStateBase {
|
||||
self.replace_text(&range, "");
|
||||
self.marked_range = None;
|
||||
|
||||
cx.emit_text_changed(TextChanged);
|
||||
cx.notify_changed();
|
||||
cx.emit(TextChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn nav_linear(
|
||||
&mut self,
|
||||
direction: NavigationDirection,
|
||||
boundary: TextBoundary,
|
||||
cx: &mut impl EditableTextStateNotifier,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let caret_pos = match self.selected_range.is_empty() {
|
||||
false => match direction {
|
||||
@@ -488,27 +388,359 @@ impl EditableTextStateBase {
|
||||
(direction > 0).then(|| self.storage.content_utf8().len())
|
||||
}
|
||||
|
||||
pub fn select_all(&mut self, cx: &mut impl EditableTextStateNotifier) {
|
||||
pub fn select_document(&mut self, cx: &mut Context<Self>) {
|
||||
self.selected_range = 0..self.storage.content_utf8().len();
|
||||
cx.notify_changed();
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn select_linear(
|
||||
&mut self,
|
||||
direction: NavigationDirection,
|
||||
boundary: TextBoundary,
|
||||
cx: &mut impl EditableTextStateNotifier,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let caret_pos = self
|
||||
.storage
|
||||
.offset_from_caret(self.caret_pos(), direction, boundary);
|
||||
self.select_to(caret_pos, cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cut<T>(&mut self, cx: &mut T)
|
||||
where
|
||||
T: EditableTextStateNotifier + std::ops::Deref<Target = App>,
|
||||
{
|
||||
impl EntityInputHandler for EditableTextState {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<String> {
|
||||
let range = self.storage.utf_range_16to8(&range_utf16);
|
||||
let storage_len_utf8 = self.storage.content_utf8().len();
|
||||
let clamped_range = range.start.min(storage_len_utf8)..range.end.min(storage_len_utf8);
|
||||
adjusted_range.replace(self.storage.utf_range_8to16(&clamped_range));
|
||||
Some(self.storage.content_utf8()[clamped_range].to_string())
|
||||
}
|
||||
|
||||
fn selected_text_range(
|
||||
&mut self,
|
||||
_ignore_disabled_input: bool,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<UTF16Selection> {
|
||||
let selection_range = self.selected_range();
|
||||
let direction = self.selection_direction();
|
||||
Some(UTF16Selection {
|
||||
range: self.storage.utf_range_8to16(&selection_range),
|
||||
reversed: direction == Some(NavigationDirection::Back),
|
||||
})
|
||||
}
|
||||
|
||||
fn marked_text_range(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<Range<usize>> {
|
||||
self.marked_range
|
||||
.as_ref()
|
||||
.map(|range| self.storage.utf_range_8to16(range))
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||
self.marked_range = None;
|
||||
}
|
||||
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
text_to_insert: &str,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let range_utf8 = self.ime_resolve_range(range_utf16);
|
||||
self.replace_text_in_range_bytes(range_utf8, text_to_insert, cx);
|
||||
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
||||
cx.emit(TextChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
text_to_insert: &str,
|
||||
new_selected_range_utf16: Option<Range<usize>>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let range = self.ime_resolve_range(range_utf16);
|
||||
self.replace_text_in_range_bytes(range.clone(), text_to_insert, cx);
|
||||
self.ime_mark_text_in_range(&range, text_to_insert.len());
|
||||
self.ime_mark_selected_range(&range, &new_selected_range_utf16, text_to_insert.len());
|
||||
cx.emit(TextChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<Bounds<Pixels>> {
|
||||
let range = self.storage.utf_range_16to8(&range_utf16);
|
||||
let line_height = window.line_height();
|
||||
|
||||
for line in &self.layout_data.lines {
|
||||
let y_offset = line.pos_y * line_height;
|
||||
if line.text_range.is_empty() {
|
||||
if range.start == line.text_range.start {
|
||||
return Some(Bounds::from_corners(
|
||||
bounds.origin + point(Pixels::ZERO, y_offset),
|
||||
bounds.origin + point(gpui::px(4.), y_offset + line_height),
|
||||
));
|
||||
}
|
||||
} else if line.text_range.contains(&range.start) {
|
||||
if let Some(wrapped) = &line.wrapped_line {
|
||||
let local_start = range.start - line.text_range.start;
|
||||
let local_end = (range.end - line.text_range.start).min(wrapped.text.len());
|
||||
|
||||
let start_pos = wrapped
|
||||
.position_for_index(local_start, line_height)
|
||||
.unwrap_or(point(Pixels::ZERO, Pixels::ZERO));
|
||||
let end_pos = wrapped
|
||||
.position_for_index(local_end, line_height)
|
||||
.unwrap_or_else(|| {
|
||||
let last_line_y = line_height * (line.num_visual_lines - 1) as f32;
|
||||
point(wrapped.width(), last_line_y)
|
||||
});
|
||||
|
||||
let start_visual_line = (start_pos.y / line_height).floor() as usize;
|
||||
let end_visual_line = (end_pos.y / line_height).floor() as usize;
|
||||
|
||||
if start_visual_line == end_visual_line {
|
||||
return Some(Bounds::from_corners(
|
||||
bounds.origin + start_pos + point(Pixels::ZERO, y_offset),
|
||||
bounds.origin + point(end_pos.x, y_offset + start_pos.y + line_height),
|
||||
));
|
||||
} else {
|
||||
return Some(Bounds::from_corners(
|
||||
bounds.origin + start_pos + point(Pixels::ZERO, y_offset),
|
||||
bounds.origin
|
||||
+ point(wrapped.width(), y_offset + start_pos.y + line_height),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn character_index_for_point(
|
||||
&mut self,
|
||||
point: Point<Pixels>,
|
||||
window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<usize> {
|
||||
let index = self.index_for_pixel_point(point, window.line_height());
|
||||
Some(self.storage().utf_offset_8to16(index))
|
||||
}
|
||||
}
|
||||
|
||||
use super::actions::*;
|
||||
impl<'app> EditableTextActionHandler<Context<'app, Self>> for EditableTextState {
|
||||
fn escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.set_selected_range(0..0);
|
||||
cx.notify();
|
||||
|
||||
window.blur();
|
||||
}
|
||||
|
||||
fn insert_enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
if self.layout_data.supports_multiline {
|
||||
// TODO: Why is the cursor appearing at the start of the entire field instead of on the new line?
|
||||
self.replace_text_in_range(None, "\n", window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.replace_text_in_range(None, "\t", window, cx);
|
||||
}
|
||||
|
||||
fn backspace(&mut self, _: &Backspace, _: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.delete_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn delete(&mut self, _: &Delete, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.delete_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn delete_word_left(
|
||||
&mut self,
|
||||
_: &DeleteWordLeft,
|
||||
_w: &mut Window,
|
||||
cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
self.delete_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn delete_word_right(
|
||||
&mut self,
|
||||
_: &DeleteWordRight,
|
||||
_w: &mut Window,
|
||||
cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
self.delete_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn delete_to_line_start(
|
||||
&mut self,
|
||||
_: &DeleteToBeginningOfLine,
|
||||
_w: &mut Window,
|
||||
cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
self.delete_linear(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn delete_to_line_end(
|
||||
&mut self,
|
||||
_: &DeleteToEndOfLine,
|
||||
_w: &mut Window,
|
||||
cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
self.delete_linear(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_left(&mut self, _: &Left, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.nav_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn nav_right(&mut self, _: &Right, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.nav_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn nav_up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
if !self.layout_data.supports_multiline {
|
||||
// semantically equivalent to line
|
||||
self.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(caret_pos) = self.find_position_in_vertical_direction(-1, window.line_height())
|
||||
{
|
||||
self.move_to(caret_pos, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn nav_down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
if !self.layout_data.supports_multiline {
|
||||
// semantically equivalent to line
|
||||
self.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(caret_pos) = self.find_position_in_vertical_direction(1, window.line_height()) {
|
||||
self.move_to(caret_pos, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn nav_line_start(&mut self, _: &Home, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
// [when not multiline] semantically equivalent to document
|
||||
self.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_line_end(&mut self, _: &End, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
// [when not multiline] semantically equivalent to document
|
||||
self.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.nav_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.nav_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.nav_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.nav_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.select_document(cx);
|
||||
}
|
||||
|
||||
fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.select_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn select_right(&mut self, _: &SelectRight, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.select_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
if !self.layout_data.supports_multiline {
|
||||
// semantically equivalent to select document
|
||||
self.select_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(caret_pos) = self.find_position_in_vertical_direction(-1, window.line_height())
|
||||
{
|
||||
self.select_to(caret_pos, cx);
|
||||
//self.scroll_to_cursor();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
if !self.layout_data.supports_multiline {
|
||||
// semantically equivalent to select document
|
||||
self.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(caret_pos) = self.find_position_in_vertical_direction(1, window.line_height()) {
|
||||
self.select_to(caret_pos, cx);
|
||||
//self.scroll_to_cursor();
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
fn select_start(
|
||||
&mut self,
|
||||
_: &SelectToBeginning,
|
||||
_w: &mut Window,
|
||||
cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
self.select_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
self.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn select_left_word(
|
||||
&mut self,
|
||||
_: &SelectWordLeft,
|
||||
_w: &mut Window,
|
||||
cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
self.select_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn select_right_word(
|
||||
&mut self,
|
||||
_: &SelectWordRight,
|
||||
_w: &mut Window,
|
||||
cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
self.select_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn cut(&mut self, _: &Cut, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
if !self.selected_range.is_empty() {
|
||||
// Cut selected text
|
||||
let slice = &self.storage.content_utf8()[self.selected_range.clone()];
|
||||
@@ -545,40 +777,43 @@ impl EditableTextStateBase {
|
||||
|
||||
self.replace_text_in_range_bytes(self.selected_range.clone(), "", cx);
|
||||
}
|
||||
cx.emit_text_changed(TextChanged);
|
||||
cx.notify_changed();
|
||||
cx.emit(TextChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn copy(&mut self, app: &mut App) {
|
||||
fn copy(&mut self, _: &Copy, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
if !self.selected_range.is_empty() {
|
||||
let slice = &self.storage.content_utf8()[self.selected_range.clone()];
|
||||
app.write_to_clipboard(ClipboardItem::new_string(slice.to_string()));
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(slice.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paste<T>(&mut self, cx: &mut T)
|
||||
where
|
||||
T: EditableTextStateNotifier + std::ops::Deref<Target = App>,
|
||||
{
|
||||
fn paste(&mut self, _: &Paste, _w: &mut Window, cx: &mut Context<'app, Self>) {
|
||||
let Some(text) = cx.read_from_clipboard().and_then(|item| item.text()) else {
|
||||
return;
|
||||
};
|
||||
self.replace_text_in_range_bytes(self.ime_resolve_range(None), &text, cx);
|
||||
cx.emit_text_changed(TextChanged);
|
||||
cx.notify_changed();
|
||||
cx.emit(TextChanged);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn on_mouse_down<Context>(
|
||||
fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Context<'app, Self>) {
|
||||
// TODO: STUB
|
||||
}
|
||||
|
||||
fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Context<'app, Self>) {
|
||||
// TODO: STUB
|
||||
}
|
||||
|
||||
fn on_mouse_down(
|
||||
&mut self,
|
||||
position: Point<Pixels>,
|
||||
character_pos: usize,
|
||||
click_count: usize,
|
||||
shift: bool,
|
||||
event: &gpui::MouseDownEvent,
|
||||
text_position: gpui::Point<gpui::Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context,
|
||||
) where
|
||||
Context: EditableTextStateNotifier + std::ops::DerefMut<Target = App>,
|
||||
{
|
||||
cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
let character_pos = self.index_for_pixel_point(text_position, window.line_height());
|
||||
|
||||
window.focus(&self.focus_handle, cx);
|
||||
self.is_selecting = true;
|
||||
|
||||
@@ -586,22 +821,23 @@ impl EditableTextStateBase {
|
||||
.last_click_position
|
||||
.map(|last| {
|
||||
let threshold = gpui::px(4.);
|
||||
(position.x - last.x).abs() < threshold && (position.y - last.y).abs() < threshold
|
||||
(text_position.x - last.x).abs() < threshold
|
||||
&& (text_position.y - last.y).abs() < threshold
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if is_same_position && click_count > 1 {
|
||||
self.click_count = click_count;
|
||||
if is_same_position && event.click_count > 1 {
|
||||
self.click_count = event.click_count;
|
||||
} else {
|
||||
self.click_count = 1;
|
||||
}
|
||||
self.last_click_position = Some(position);
|
||||
self.last_click_position = Some(text_position);
|
||||
|
||||
match self.click_count {
|
||||
2 => {
|
||||
let (word_start, word_end) = self.storage.word_range_at(character_pos);
|
||||
self.selected_range = word_start..word_end;
|
||||
cx.notify_changed();
|
||||
cx.notify();
|
||||
}
|
||||
3 => {
|
||||
let line_start = self.storage.find_line_start(character_pos);
|
||||
@@ -612,10 +848,10 @@ impl EditableTextStateBase {
|
||||
line_end
|
||||
};
|
||||
self.selected_range = line_start..line_end_with_newline;
|
||||
cx.notify_changed();
|
||||
cx.notify();
|
||||
}
|
||||
_ => {
|
||||
if shift {
|
||||
if event.modifiers.shift {
|
||||
self.select_to(character_pos, cx);
|
||||
} else {
|
||||
self.move_to(character_pos, cx);
|
||||
@@ -624,11 +860,23 @@ impl EditableTextStateBase {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_mouse_up(&mut self) {
|
||||
fn on_mouse_up(
|
||||
&mut self,
|
||||
_event: &gpui::MouseUpEvent,
|
||||
_w: &mut Window,
|
||||
_cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
self.is_selecting = false;
|
||||
}
|
||||
|
||||
pub fn on_mouse_move(&mut self, character_pos: usize, cx: &mut impl EditableTextStateNotifier) {
|
||||
fn on_mouse_move(
|
||||
&mut self,
|
||||
_event: &gpui::MouseMoveEvent,
|
||||
text_position: Point<Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<'app, Self>,
|
||||
) {
|
||||
let character_pos = self.index_for_pixel_point(text_position, window.line_height());
|
||||
if self.is_selecting && self.click_count == 1 {
|
||||
self.select_to(character_pos, cx);
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
use crate::editable_text::{
|
||||
EditableTextAreaState, InitStorage, StateBackedEditableText,
|
||||
actions::{DEFAULT_INPUT_CONTEXT, EditableTextActionElement},
|
||||
shared_element::{self, EditableTextElement},
|
||||
};
|
||||
use gpui::{
|
||||
App, Bounds, Element, ElementId, InteractiveElement, Interactivity, IntoElement, Pixels,
|
||||
SharedString, StatefulInteractiveElement, StyleRefinement, Styled, WeakEntity, Window,
|
||||
};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
|
||||
pub fn text_area(id: impl Into<ElementId>) -> EditableTextAreaElement {
|
||||
let mut this = EditableTextAreaElement {
|
||||
interactivity: Interactivity::new(),
|
||||
state_entity: Rc::new(RefCell::new(WeakEntity::new_invalid())),
|
||||
init_storage: InitStorage::default(),
|
||||
placeholder: None,
|
||||
};
|
||||
this.interactivity.element_id = Some(id.into());
|
||||
|
||||
this = this.key_context(DEFAULT_INPUT_CONTEXT);
|
||||
this.register_actions();
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
// TODO: Disabled flag/state?
|
||||
pub struct EditableTextAreaElement {
|
||||
interactivity: Interactivity,
|
||||
// Populated on first render with an entity stored/attached to the view.
|
||||
// This reference is shared with the action handlers, which are processed between renders
|
||||
// and therefore cannot otherwise access state attached to the view.
|
||||
state_entity: Rc<RefCell<WeakEntity<EditableTextAreaState>>>,
|
||||
init_storage: InitStorage,
|
||||
placeholder: Option<SharedString>,
|
||||
}
|
||||
|
||||
impl EditableTextAreaElement {
|
||||
pub fn placeholder(mut self, text: impl Into<SharedString>) -> Self {
|
||||
self.placeholder = Some(text.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Swaps the default storage container (standard String) with a custom initializer of [`UnicodeTextStorage`].
|
||||
pub fn with_storage(mut self, fn_init: impl Into<InitStorage>) -> Self {
|
||||
self.init_storage = fn_init.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Swaps the default storage container. The new initializer is a standard String using the provided value.
|
||||
///
|
||||
/// Incompatible with [`with_storage`] (they establish the same internal value).
|
||||
/// If you initialize custom storage, you should be able to initialize its default value.
|
||||
pub fn default_value(mut self, value: impl Into<String>) -> Self {
|
||||
let storage = super::StringStorage::from(value.into());
|
||||
self.init_storage = InitStorage::new_typed(move |_cx| storage.clone());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl InteractiveElement for EditableTextAreaElement {
|
||||
fn interactivity(&mut self) -> &mut Interactivity {
|
||||
&mut self.interactivity
|
||||
}
|
||||
}
|
||||
|
||||
// forced implementation since the API for the element doesnt use Stateful<Element>
|
||||
impl StatefulInteractiveElement for EditableTextAreaElement {}
|
||||
|
||||
impl Styled for EditableTextAreaElement {
|
||||
fn style(&mut self) -> &mut StyleRefinement {
|
||||
&mut self.interactivity.base_style
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoElement for EditableTextAreaElement {
|
||||
type Element = Self;
|
||||
fn into_element(self) -> Self::Element {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl StateBackedEditableText for EditableTextAreaElement {
|
||||
type State = EditableTextAreaState;
|
||||
}
|
||||
|
||||
impl EditableTextActionElement for EditableTextAreaElement {
|
||||
fn state_entity_rc(&self) -> &Rc<RefCell<WeakEntity<Self::State>>> {
|
||||
&self.state_entity
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextElement for EditableTextAreaElement {
|
||||
fn init_state(&self, cx: &mut gpui::prelude::Context<Self::State>) -> Self::State {
|
||||
Self::State::new(self.init_storage.exec(cx), cx)
|
||||
}
|
||||
|
||||
fn placeholder(&self) -> &Option<SharedString> {
|
||||
&self.placeholder
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for EditableTextAreaElement {
|
||||
type RequestLayoutState = shared_element::LayoutState<EditableTextAreaState>;
|
||||
type PrepaintState = shared_element::PrepaintState;
|
||||
|
||||
fn id(&self) -> Option<ElementId> {
|
||||
self.interactivity.element_id.clone()
|
||||
}
|
||||
|
||||
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
|
||||
self.interactivity.source_location()
|
||||
}
|
||||
|
||||
fn request_layout(
|
||||
&mut self,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
||||
self.shared_request_layout(global_id, inspector_id, window, cx)
|
||||
}
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Self::PrepaintState {
|
||||
self.shared_prepaint(global_id, inspector_id, bounds, request_layout, window, cx)
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
bounds: Bounds<Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
prepaint: &mut Self::PrepaintState,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
self.shared_paint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
request_layout,
|
||||
prepaint,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
use crate::editable_text::{
|
||||
EditableTextStateBase, EditableTextStateNotifier, TextBoundary, UnicodeTextStorage,
|
||||
notify::{TextChanged, TextHistoryPushed},
|
||||
};
|
||||
use gpui::{
|
||||
Bounds, Context, EntityInputHandler, EventEmitter, NavigationDirection, Pixels, Point,
|
||||
UTF16Selection, Window,
|
||||
};
|
||||
use std::ops::Range;
|
||||
|
||||
pub struct EditableTextAreaState {
|
||||
internal: EditableTextStateBase,
|
||||
}
|
||||
|
||||
impl EventEmitter<TextChanged> for EditableTextAreaState {}
|
||||
impl EventEmitter<TextHistoryPushed> for EditableTextAreaState {}
|
||||
|
||||
impl std::ops::Deref for EditableTextAreaState {
|
||||
type Target = EditableTextStateBase;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.internal
|
||||
}
|
||||
}
|
||||
impl std::ops::DerefMut for EditableTextAreaState {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.internal
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextAreaState {
|
||||
pub fn new(storage: impl Into<Box<dyn UnicodeTextStorage>>, cx: &mut Context<Self>) -> Self {
|
||||
let internal = EditableTextStateBase::new(storage, cx);
|
||||
Self { internal }
|
||||
}
|
||||
}
|
||||
|
||||
impl EditableTextStateNotifier for Context<'_, EditableTextAreaState> {
|
||||
fn notify_changed(&mut self) {
|
||||
self.notify();
|
||||
}
|
||||
|
||||
fn emit_text_changed(&mut self, event: TextChanged) {
|
||||
self.emit(event);
|
||||
}
|
||||
|
||||
fn emit_history(&mut self, event: TextHistoryPushed) {
|
||||
self.emit(event);
|
||||
}
|
||||
}
|
||||
|
||||
impl EntityInputHandler for EditableTextAreaState {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<String> {
|
||||
self.internal
|
||||
.ime_text_for_range(range_utf16, adjusted_range)
|
||||
}
|
||||
|
||||
fn selected_text_range(
|
||||
&mut self,
|
||||
ignore_disabled_input: bool,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<UTF16Selection> {
|
||||
self.internal.ime_selected_text_range(ignore_disabled_input)
|
||||
}
|
||||
|
||||
fn marked_text_range(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<Range<usize>> {
|
||||
self.internal.ime_marked_text_range()
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||
self.internal.ime_unmark_text();
|
||||
}
|
||||
|
||||
fn replace_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
text_to_insert: &str,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let range_utf8 = self.internal.ime_resolve_range(range_utf16);
|
||||
self.internal
|
||||
.replace_text_in_range_bytes(range_utf8, text_to_insert, cx);
|
||||
//cx.emit(CursorTrigger::PauseBlinkingForUserAction);
|
||||
cx.emit_text_changed(TextChanged);
|
||||
cx.notify_changed();
|
||||
}
|
||||
|
||||
fn replace_and_mark_text_in_range(
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
text_to_insert: &str,
|
||||
new_selected_range_utf16: Option<Range<usize>>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let range = self.internal.ime_resolve_range(range_utf16);
|
||||
self.internal
|
||||
.replace_text_in_range_bytes(range.clone(), text_to_insert, cx);
|
||||
self.internal
|
||||
.ime_mark_text_in_range(&range, text_to_insert.len());
|
||||
self.internal.ime_mark_selected_range(
|
||||
&range,
|
||||
&new_selected_range_utf16,
|
||||
text_to_insert.len(),
|
||||
);
|
||||
cx.emit_text_changed(TextChanged);
|
||||
cx.notify_changed();
|
||||
}
|
||||
|
||||
fn bounds_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<Bounds<Pixels>> {
|
||||
self.internal
|
||||
.ime_bounds_for_range(range_utf16, bounds, window)
|
||||
}
|
||||
|
||||
fn character_index_for_point(
|
||||
&mut self,
|
||||
point: Point<Pixels>,
|
||||
window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<usize> {
|
||||
let index = self
|
||||
.internal
|
||||
.index_for_pixel_point(point, window.line_height());
|
||||
Some(self.internal.storage().utf_offset_8to16(index))
|
||||
}
|
||||
}
|
||||
|
||||
use super::actions::*;
|
||||
impl<'app> EditableTextActionHandler<'app> for EditableTextAreaState {
|
||||
type Context = gpui::Context<'app, Self>;
|
||||
|
||||
fn escape(&mut self, _: &Escape, window: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.set_selected_range(0..0);
|
||||
cx.notify();
|
||||
|
||||
window.blur();
|
||||
}
|
||||
|
||||
fn insert_enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Self::Context) {
|
||||
// TODO: Why is the cursor appearing at the start of the entire field instead of on the new line?
|
||||
self.replace_text_in_range(None, "\n", window, cx);
|
||||
}
|
||||
|
||||
fn insert_tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Self::Context) {
|
||||
self.replace_text_in_range(None, "\t", window, cx);
|
||||
}
|
||||
|
||||
fn backspace(&mut self, _: &Backspace, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn delete(&mut self, _: &Delete, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn delete_word_left(&mut self, _: &DeleteWordLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn delete_word_right(&mut self, _: &DeleteWordRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn delete_to_line_start(
|
||||
&mut self,
|
||||
_: &DeleteToBeginningOfLine,
|
||||
_w: &mut Window,
|
||||
cx: &mut Self::Context,
|
||||
) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn delete_to_line_end(
|
||||
&mut self,
|
||||
_: &DeleteToEndOfLine,
|
||||
_w: &mut Window,
|
||||
cx: &mut Self::Context,
|
||||
) {
|
||||
self.internal
|
||||
.delete(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_left(&mut self, _: &Left, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn nav_right(&mut self, _: &Right, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn nav_up(&mut self, _: &Up, window: &mut Window, cx: &mut Self::Context) {
|
||||
if let Some(caret_pos) = self
|
||||
.internal
|
||||
.find_position_in_vertical_direction(-1, window.line_height())
|
||||
{
|
||||
self.internal.move_to(caret_pos, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn nav_down(&mut self, _: &Down, window: &mut Window, cx: &mut Self::Context) {
|
||||
if let Some(caret_pos) = self
|
||||
.internal
|
||||
.find_position_in_vertical_direction(1, window.line_height())
|
||||
{
|
||||
self.internal.move_to(caret_pos, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn nav_line_start(&mut self, _: &Home, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Back, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_line_end(&mut self, _: &End, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Forward, TextBoundary::Line, cx);
|
||||
}
|
||||
|
||||
fn nav_start(&mut self, _: &MoveToBeginning, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn nav_end(&mut self, _: &MoveToEnd, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn nav_left_word(&mut self, _: &WordLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn nav_right_word(&mut self, _: &WordRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.nav_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.select_all(cx);
|
||||
}
|
||||
|
||||
fn select_left(&mut self, _: &SelectLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Back, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn select_right(&mut self, _: &SelectRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Forward, TextBoundary::Graphmeme, cx);
|
||||
}
|
||||
|
||||
fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Self::Context) {
|
||||
if let Some(caret_pos) = self
|
||||
.internal
|
||||
.find_position_in_vertical_direction(-1, window.line_height())
|
||||
{
|
||||
self.internal.select_to(caret_pos, cx);
|
||||
//self.scroll_to_cursor();
|
||||
cx.notify_changed();
|
||||
}
|
||||
}
|
||||
|
||||
fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Self::Context) {
|
||||
if let Some(caret_pos) = self
|
||||
.internal
|
||||
.find_position_in_vertical_direction(1, window.line_height())
|
||||
{
|
||||
self.internal.select_to(caret_pos, cx);
|
||||
//self.scroll_to_cursor();
|
||||
cx.notify_changed();
|
||||
}
|
||||
}
|
||||
|
||||
fn select_start(&mut self, _: &SelectToBeginning, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Back, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn select_end(&mut self, _: &SelectToEnd, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Forward, TextBoundary::Document, cx);
|
||||
}
|
||||
|
||||
fn select_left_word(&mut self, _: &SelectWordLeft, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Back, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn select_right_word(&mut self, _: &SelectWordRight, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal
|
||||
.select_linear(NavigationDirection::Forward, TextBoundary::Word, cx);
|
||||
}
|
||||
|
||||
fn cut(&mut self, _: &Cut, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.cut(cx);
|
||||
}
|
||||
|
||||
fn copy(&mut self, _: &Copy, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.copy(cx);
|
||||
}
|
||||
|
||||
fn paste(&mut self, _: &Paste, _w: &mut Window, cx: &mut Self::Context) {
|
||||
self.internal.paste(cx);
|
||||
}
|
||||
|
||||
fn undo(&mut self, _: &Undo, _w: &mut Window, _cx: &mut Self::Context) {
|
||||
// TODO: STUB
|
||||
}
|
||||
|
||||
fn redo(&mut self, _: &Redo, _w: &mut Window, _cx: &mut Self::Context) {
|
||||
// TODO: STUB
|
||||
}
|
||||
|
||||
fn on_mouse_down(
|
||||
&mut self,
|
||||
event: &gpui::MouseDownEvent,
|
||||
text_position: gpui::Point<gpui::Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut Self::Context,
|
||||
) {
|
||||
let character_pos = self
|
||||
.internal
|
||||
.index_for_pixel_point(text_position, window.line_height());
|
||||
self.internal.on_mouse_down(
|
||||
text_position,
|
||||
character_pos,
|
||||
event.click_count,
|
||||
event.modifiers.shift,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
fn on_mouse_up(
|
||||
&mut self,
|
||||
_event: &gpui::MouseUpEvent,
|
||||
_w: &mut Window,
|
||||
_cx: &mut Self::Context,
|
||||
) {
|
||||
self.internal.on_mouse_up();
|
||||
}
|
||||
|
||||
fn on_mouse_move(
|
||||
&mut self,
|
||||
_event: &gpui::MouseMoveEvent,
|
||||
text_position: Point<Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut Self::Context,
|
||||
) {
|
||||
let character_pos = self
|
||||
.internal
|
||||
.index_for_pixel_point(text_position, window.line_height());
|
||||
self.internal.on_mouse_move(character_pos, cx);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user