chore: Upgrade to GPUI3 (#6)
* wip: gpui3 * wip: gpui3 * chore: fix clippy
This commit is contained in:
@@ -3,28 +3,7 @@
|
||||
//! Based on the `Input` example from the `gpui` crate.
|
||||
//! https://github.com/zed-industries/zed/blob/main/crates/gpui/examples/input.rs
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use std::cell::Cell;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use unicode_segmentation::*;
|
||||
|
||||
use gpui::prelude::FluentBuilder as _;
|
||||
use gpui::{
|
||||
actions, div, point, px, AnyElement, AppContext, Bounds, ClickEvent, ClipboardItem,
|
||||
Context as _, Entity, EventEmitter, FocusHandle, FocusableView, Half, InteractiveElement as _,
|
||||
IntoElement, KeyBinding, KeyDownEvent, Model, MouseButton, MouseDownEvent, MouseMoveEvent,
|
||||
MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render, ScrollHandle, ScrollWheelEvent,
|
||||
SharedString, Styled as _, UTF16Selection, ViewContext, ViewInputHandler, WindowContext,
|
||||
WrappedLine,
|
||||
};
|
||||
|
||||
// TODO:
|
||||
// - Press Up,Down to move cursor up, down line if multi-line
|
||||
// - Move cursor to skip line eof empty chars.
|
||||
|
||||
use super::{blink_cursor::BlinkCursor, change::Change, element::TextElement, ClearButton};
|
||||
|
||||
use super::{blink_cursor::BlinkCursor, change::Change, element::TextElement};
|
||||
use crate::{
|
||||
history::History,
|
||||
indicator::Indicator,
|
||||
@@ -32,6 +11,16 @@ use crate::{
|
||||
theme::{scale::ColorScaleStep, ActiveTheme},
|
||||
Sizable, Size, StyleSized, StyledExt,
|
||||
};
|
||||
use gpui::{
|
||||
actions, div, point, prelude::FluentBuilder as _, px, AnyElement, App, AppContext, Bounds,
|
||||
ClickEvent, ClipboardItem, Context, Entity, EntityInputHandler, EventEmitter, FocusHandle,
|
||||
Focusable, Half, InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton,
|
||||
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render,
|
||||
ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, UTF16Selection, Window, WrappedLine,
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{cell::Cell, ops::Range, rc::Rc};
|
||||
use unicode_segmentation::*;
|
||||
|
||||
actions!(
|
||||
input,
|
||||
@@ -80,7 +69,7 @@ pub enum InputEvent {
|
||||
|
||||
const CONTEXT: &str = "Input";
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
pub fn init(cx: &mut App) {
|
||||
cx.bind_keys([
|
||||
KeyBinding::new("backspace", Backspace, Some(CONTEXT)),
|
||||
KeyBinding::new("delete", Delete, Some(CONTEXT)),
|
||||
@@ -154,8 +143,8 @@ pub fn init(cx: &mut AppContext) {
|
||||
]);
|
||||
}
|
||||
|
||||
type TextInputPrefix<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>;
|
||||
type TextInputSuffix<T> = Option<Box<dyn Fn(&mut ViewContext<T>) -> AnyElement + 'static>>;
|
||||
type TextInputPrefix<T> = Option<Box<dyn Fn(&mut Window, &mut Context<T>) -> AnyElement + 'static>>;
|
||||
type TextInputSuffix<T> = Option<Box<dyn Fn(&mut Window, &mut Context<T>) -> AnyElement + 'static>>;
|
||||
type Validate = Option<Box<dyn Fn(&str) -> bool + 'static>>;
|
||||
|
||||
pub struct TextInput {
|
||||
@@ -163,7 +152,7 @@ pub struct TextInput {
|
||||
pub(super) text: SharedString,
|
||||
multi_line: bool,
|
||||
pub(super) history: History<Change>,
|
||||
pub(super) blink_cursor: Model<BlinkCursor>,
|
||||
pub(super) blink_cursor: Entity<BlinkCursor>,
|
||||
pub(super) prefix: TextInputPrefix<Self>,
|
||||
pub(super) suffix: TextInputSuffix<Self>,
|
||||
pub(super) loading: bool,
|
||||
@@ -203,9 +192,9 @@ pub struct TextInput {
|
||||
impl EventEmitter<InputEvent> for TextInput {}
|
||||
|
||||
impl TextInput {
|
||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
|
||||
let focus_handle = cx.focus_handle();
|
||||
let blink_cursor = cx.new_model(|_| BlinkCursor::new());
|
||||
let blink_cursor = cx.new(|_| BlinkCursor::new());
|
||||
let history = History::new().group_interval(std::time::Duration::from_secs(1));
|
||||
let input = Self {
|
||||
focus_handle: focus_handle.clone(),
|
||||
@@ -246,11 +235,12 @@ impl TextInput {
|
||||
// Observe the blink cursor to repaint the view when it changes.
|
||||
cx.observe(&input.blink_cursor, |_, _, cx| cx.notify())
|
||||
.detach();
|
||||
|
||||
// Blink the cursor when the window is active, pause when it's not.
|
||||
cx.observe_window_activation(|input, cx| {
|
||||
if cx.is_window_active() {
|
||||
cx.observe_window_activation(window, |input, window, cx| {
|
||||
if window.is_window_active() {
|
||||
let focus_handle = input.focus_handle.clone();
|
||||
if focus_handle.is_focused(cx) {
|
||||
if focus_handle.is_focused(window) {
|
||||
input.blink_cursor.update(cx, |blink_cursor, cx| {
|
||||
blink_cursor.start(cx);
|
||||
});
|
||||
@@ -259,8 +249,8 @@ impl TextInput {
|
||||
})
|
||||
.detach();
|
||||
|
||||
cx.on_focus(&focus_handle, Self::on_focus).detach();
|
||||
cx.on_blur(&focus_handle, Self::on_blur).detach();
|
||||
cx.on_focus(&focus_handle, window, Self::on_focus).detach();
|
||||
cx.on_blur(&focus_handle, window, Self::on_blur).detach();
|
||||
|
||||
input
|
||||
}
|
||||
@@ -272,7 +262,7 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Called after moving the cursor. Updates preferred_x_offset if we know where the cursor now is.
|
||||
fn update_preferred_x_offset(&mut self, _cx: &mut ViewContext<Self>) {
|
||||
fn update_preferred_x_offset(&mut self, _cx: &mut Context<Self>) {
|
||||
if let (Some(lines), Some(bounds)) = (&self.last_layout, &self.last_bounds) {
|
||||
let offset = self.cursor_offset();
|
||||
let line_height = self.last_line_height;
|
||||
@@ -314,7 +304,7 @@ impl TextInput {
|
||||
|
||||
/// Move the cursor vertically by one line (up or down) while preserving the column if possible.
|
||||
/// direction: -1 for up, +1 for down
|
||||
fn move_vertical(&mut self, direction: i32, cx: &mut ViewContext<Self>) {
|
||||
fn move_vertical(&mut self, direction: i32, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
@@ -344,7 +334,7 @@ impl TextInput {
|
||||
// Handle moving above the first line
|
||||
if direction == -1 && new_line_index == 0 && new_sub_line < 0 {
|
||||
// Move cursor to the beginning of the text
|
||||
self.move_to(0, cx);
|
||||
self.move_to(0, window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -421,9 +411,14 @@ impl TextInput {
|
||||
/// Set the text of the input field.
|
||||
///
|
||||
/// And the selection_range will be reset to 0..0.
|
||||
pub fn set_text(&mut self, text: impl Into<SharedString>, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_text(
|
||||
&mut self,
|
||||
text: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.history.ignore = true;
|
||||
self.replace_text(text, cx);
|
||||
self.replace_text(text, window, cx);
|
||||
self.history.ignore = false;
|
||||
// Ensure cursor to start when set text
|
||||
self.selected_range = 0..0;
|
||||
@@ -431,46 +426,55 @@ impl TextInput {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn replace_text(&mut self, text: impl Into<SharedString>, cx: &mut ViewContext<Self>) {
|
||||
fn replace_text(
|
||||
&mut self,
|
||||
text: impl Into<SharedString>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let text: SharedString = text.into();
|
||||
let range = 0..self.text.chars().map(|c| c.len_utf16()).sum();
|
||||
self.replace_text_in_range(Some(range), &text, cx);
|
||||
self.replace_text_in_range(Some(range), &text, window, cx);
|
||||
}
|
||||
|
||||
/// Set the disabled state of the input field.
|
||||
pub fn set_disabled(&mut self, disabled: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_disabled(&mut self, disabled: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.disabled = disabled;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the masked state of the input field.
|
||||
pub fn set_masked(&mut self, masked: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_masked(&mut self, masked: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.masked = masked;
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the prefix element of the input field.
|
||||
pub fn set_prefix<F, E>(&mut self, builder: F, cx: &mut ViewContext<Self>)
|
||||
pub fn set_prefix<F, E>(&mut self, builder: F, _: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
F: Fn(&ViewContext<Self>) -> E + 'static,
|
||||
F: Fn(&Window, &Context<Self>) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.prefix = Some(Box::new(move |cx| builder(cx).into_any_element()));
|
||||
self.prefix = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the suffix element of the input field.
|
||||
pub fn set_suffix<F, E>(&mut self, builder: F, cx: &mut ViewContext<Self>)
|
||||
pub fn set_suffix<F, E>(&mut self, builder: F, _: &mut Window, cx: &mut Context<Self>)
|
||||
where
|
||||
F: Fn(&ViewContext<Self>) -> E + 'static,
|
||||
F: Fn(&Window, &Context<Self>) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element()));
|
||||
self.suffix = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
/// Set the Input size
|
||||
pub fn set_size(&mut self, size: Size, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_size(&mut self, size: Size, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.size = size;
|
||||
cx.notify();
|
||||
}
|
||||
@@ -490,20 +494,24 @@ impl TextInput {
|
||||
/// Set the prefix element of the input field, for example a search Icon.
|
||||
pub fn prefix<F, E>(mut self, builder: F) -> Self
|
||||
where
|
||||
F: Fn(&mut ViewContext<Self>) -> E + 'static,
|
||||
F: Fn(&mut Window, &mut Context<Self>) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.prefix = Some(Box::new(move |cx| builder(cx).into_any_element()));
|
||||
self.prefix = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the suffix element of the input field, for example a clear button.
|
||||
pub fn suffix<F, E>(mut self, builder: F) -> Self
|
||||
where
|
||||
F: Fn(&mut ViewContext<Self>) -> E + 'static,
|
||||
F: Fn(&mut Window, &mut Context<Self>) -> E + 'static,
|
||||
E: IntoElement,
|
||||
{
|
||||
self.suffix = Some(Box::new(move |cx| builder(cx).into_any_element()));
|
||||
self.suffix = Some(Box::new(move |window, cx| {
|
||||
builder(window, cx).into_any_element()
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -542,7 +550,7 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Set true to show indicator at the input right.
|
||||
pub fn set_loading(&mut self, loading: bool, cx: &mut ViewContext<Self>) {
|
||||
pub fn set_loading(&mut self, loading: bool, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.loading = loading;
|
||||
cx.notify();
|
||||
}
|
||||
@@ -557,130 +565,141 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Focus the input field.
|
||||
pub fn focus(&self, cx: &mut ViewContext<Self>) {
|
||||
self.focus_handle.focus(cx);
|
||||
pub fn focus(&self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.focus_handle.focus(window);
|
||||
}
|
||||
|
||||
fn left(&mut self, _: &Left, cx: &mut ViewContext<Self>) {
|
||||
fn left(&mut self, _: &Left, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.pause_blink_cursor(cx);
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||
self.move_to(self.previous_boundary(self.cursor_offset()), window, cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.start, cx)
|
||||
self.move_to(self.selected_range.start, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn right(&mut self, _: &Right, cx: &mut ViewContext<Self>) {
|
||||
fn right(&mut self, _: &Right, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.pause_blink_cursor(cx);
|
||||
if self.selected_range.is_empty() {
|
||||
self.move_to(self.next_boundary(self.selected_range.end), cx);
|
||||
self.move_to(self.next_boundary(self.selected_range.end), window, cx);
|
||||
} else {
|
||||
self.move_to(self.selected_range.end, cx)
|
||||
self.move_to(self.selected_range.end, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn up(&mut self, _: &Up, cx: &mut ViewContext<Self>) {
|
||||
fn up(&mut self, _: &Up, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
self.pause_blink_cursor(cx);
|
||||
self.move_vertical(-1, cx);
|
||||
self.move_vertical(-1, window, cx);
|
||||
}
|
||||
|
||||
fn down(&mut self, _: &Down, cx: &mut ViewContext<Self>) {
|
||||
fn down(&mut self, _: &Down, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
self.pause_blink_cursor(cx);
|
||||
self.move_vertical(1, cx);
|
||||
self.move_vertical(1, window, cx);
|
||||
}
|
||||
|
||||
fn select_left(&mut self, _: &SelectLeft, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), cx);
|
||||
fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), window, cx);
|
||||
}
|
||||
|
||||
fn select_right(&mut self, _: &SelectRight, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), cx);
|
||||
fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), window, cx);
|
||||
}
|
||||
|
||||
fn select_up(&mut self, _: &SelectUp, cx: &mut ViewContext<Self>) {
|
||||
fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
let offset = self.start_of_line(cx).saturating_sub(1);
|
||||
self.select_to(offset, cx);
|
||||
let offset = self.start_of_line(window, cx).saturating_sub(1);
|
||||
self.select_to(offset, window, cx);
|
||||
}
|
||||
|
||||
fn select_down(&mut self, _: &SelectDown, cx: &mut ViewContext<Self>) {
|
||||
fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_single_line() {
|
||||
return;
|
||||
}
|
||||
let offset = (self.end_of_line(cx) + 1).min(self.text.len());
|
||||
self.select_to(offset, cx);
|
||||
let offset = (self.end_of_line(window, cx) + 1).min(self.text.len());
|
||||
self.select_to(self.next_boundary(offset), window, cx);
|
||||
}
|
||||
|
||||
fn select_all(&mut self, _: &SelectAll, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
self.select_to(self.text.len(), cx)
|
||||
fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_to(0, window, cx);
|
||||
self.select_to(self.text.len(), window, cx)
|
||||
}
|
||||
|
||||
fn home(&mut self, _: &Home, cx: &mut ViewContext<Self>) {
|
||||
fn home(&mut self, _: &Home, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.pause_blink_cursor(cx);
|
||||
let offset = self.start_of_line(cx);
|
||||
self.move_to(offset, cx);
|
||||
let offset = self.start_of_line(window, cx);
|
||||
self.move_to(offset, window, cx);
|
||||
}
|
||||
|
||||
fn end(&mut self, _: &End, cx: &mut ViewContext<Self>) {
|
||||
fn end(&mut self, _: &End, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.pause_blink_cursor(cx);
|
||||
let offset = self.end_of_line(cx);
|
||||
self.move_to(offset, cx);
|
||||
let offset = self.end_of_line(window, cx);
|
||||
self.move_to(offset, window, cx);
|
||||
}
|
||||
|
||||
fn move_to_start(&mut self, _: &MoveToStart, cx: &mut ViewContext<Self>) {
|
||||
self.move_to(0, cx);
|
||||
fn move_to_start(&mut self, _: &MoveToStart, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.move_to(0, window, cx);
|
||||
}
|
||||
|
||||
fn move_to_end(&mut self, _: &MoveToEnd, cx: &mut ViewContext<Self>) {
|
||||
fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let end = self.text.len();
|
||||
self.move_to(end, cx);
|
||||
self.move_to(end, window, cx);
|
||||
}
|
||||
|
||||
fn select_to_start(&mut self, _: &SelectToStart, cx: &mut ViewContext<Self>) {
|
||||
self.select_to(0, cx);
|
||||
fn select_to_start(&mut self, _: &SelectToStart, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.select_to(0, window, cx);
|
||||
}
|
||||
|
||||
fn select_to_end(&mut self, _: &SelectToEnd, cx: &mut ViewContext<Self>) {
|
||||
fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let end = self.text.len();
|
||||
self.select_to(end, cx);
|
||||
self.select_to(end, window, cx);
|
||||
}
|
||||
|
||||
fn select_to_start_of_line(&mut self, _: &SelectToStartOfLine, cx: &mut ViewContext<Self>) {
|
||||
let offset = self.start_of_line(cx);
|
||||
self.select_to(offset, cx);
|
||||
fn select_to_start_of_line(
|
||||
&mut self,
|
||||
_: &SelectToStartOfLine,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let offset = self.start_of_line(window, cx);
|
||||
self.select_to(self.previous_boundary(offset), window, cx);
|
||||
}
|
||||
|
||||
fn select_to_end_of_line(&mut self, _: &SelectToEndOfLine, cx: &mut ViewContext<Self>) {
|
||||
let offset = self.end_of_line(cx);
|
||||
self.select_to(offset, cx);
|
||||
fn select_to_end_of_line(
|
||||
&mut self,
|
||||
_: &SelectToEndOfLine,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let offset = self.end_of_line(window, cx);
|
||||
self.select_to(self.next_boundary(offset), window, cx);
|
||||
}
|
||||
|
||||
/// Get start of line
|
||||
fn start_of_line(&mut self, cx: &mut ViewContext<Self>) -> usize {
|
||||
fn start_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize {
|
||||
if self.is_single_line() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let offset = self.previous_boundary(self.cursor_offset());
|
||||
|
||||
self.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, cx)
|
||||
let line = self
|
||||
.text_for_range(self.range_to_utf16(&(0..offset + 1)), &mut None, window, cx)
|
||||
.unwrap_or_default()
|
||||
.rfind('\n')
|
||||
.map(|i| i + 1)
|
||||
.unwrap_or(0)
|
||||
.unwrap_or(0);
|
||||
line
|
||||
}
|
||||
|
||||
/// Get end of line
|
||||
fn end_of_line(&mut self, cx: &mut ViewContext<Self>) -> usize {
|
||||
fn end_of_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> usize {
|
||||
if self.is_single_line() {
|
||||
return self.text.len();
|
||||
}
|
||||
@@ -688,7 +707,12 @@ impl TextInput {
|
||||
let offset = self.next_boundary(self.cursor_offset());
|
||||
// ignore if offset is "\n"
|
||||
if self
|
||||
.text_for_range(self.range_to_utf16(&(offset - 1..offset)), &mut None, cx)
|
||||
.text_for_range(
|
||||
self.range_to_utf16(&(offset - 1..offset)),
|
||||
&mut None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
.eq("\n")
|
||||
{
|
||||
@@ -698,6 +722,7 @@ impl TextInput {
|
||||
self.text_for_range(
|
||||
self.range_to_utf16(&(offset..self.text.len())),
|
||||
&mut None,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.unwrap_or_default()
|
||||
@@ -706,88 +731,106 @@ impl TextInput {
|
||||
.unwrap_or(self.text.len())
|
||||
}
|
||||
|
||||
fn backspace(&mut self, _: &Backspace, cx: &mut ViewContext<Self>) {
|
||||
fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), cx)
|
||||
self.select_to(self.previous_boundary(self.cursor_offset()), window, cx)
|
||||
}
|
||||
self.replace_text_in_range(None, "", cx);
|
||||
self.replace_text_in_range(None, "", window, cx);
|
||||
self.pause_blink_cursor(cx);
|
||||
}
|
||||
|
||||
fn delete(&mut self, _: &Delete, cx: &mut ViewContext<Self>) {
|
||||
fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), cx)
|
||||
self.select_to(self.next_boundary(self.cursor_offset()), window, cx)
|
||||
}
|
||||
self.replace_text_in_range(None, "", cx);
|
||||
self.replace_text_in_range(None, "", window, cx);
|
||||
self.pause_blink_cursor(cx);
|
||||
}
|
||||
|
||||
fn delete_to_beginning_of_line(
|
||||
&mut self,
|
||||
_: &DeleteToBeginningOfLine,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let offset = self.start_of_line(cx);
|
||||
let offset = self.start_of_line(window, cx);
|
||||
self.replace_text_in_range(
|
||||
Some(self.range_to_utf16(&(offset..self.cursor_offset()))),
|
||||
"",
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.pause_blink_cursor(cx);
|
||||
}
|
||||
|
||||
fn delete_to_end_of_line(&mut self, _: &DeleteToEndOfLine, cx: &mut ViewContext<Self>) {
|
||||
let offset = self.end_of_line(cx);
|
||||
fn delete_to_end_of_line(
|
||||
&mut self,
|
||||
_: &DeleteToEndOfLine,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let offset = self.end_of_line(window, cx);
|
||||
self.replace_text_in_range(
|
||||
Some(self.range_to_utf16(&(self.cursor_offset()..offset))),
|
||||
"",
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
self.pause_blink_cursor(cx);
|
||||
}
|
||||
|
||||
fn enter(&mut self, _: &Enter, cx: &mut ViewContext<Self>) {
|
||||
fn enter(&mut self, _: &Enter, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.is_multi_line() {
|
||||
let is_eof = self.selected_range.end == self.text.len();
|
||||
self.replace_text_in_range(None, "\n", cx);
|
||||
self.replace_text_in_range(None, "\n", window, cx);
|
||||
|
||||
// Move cursor to the start of the next line
|
||||
let mut new_offset = self.next_boundary(self.cursor_offset()) - 1;
|
||||
if is_eof {
|
||||
new_offset += 1;
|
||||
}
|
||||
self.move_to(new_offset, cx);
|
||||
self.move_to(new_offset, window, cx);
|
||||
}
|
||||
|
||||
cx.emit(InputEvent::PressEnter);
|
||||
}
|
||||
|
||||
fn clean(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||
self.replace_text("", cx);
|
||||
fn clean(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.replace_text("", window, cx);
|
||||
}
|
||||
|
||||
fn on_mouse_down(&mut self, event: &MouseDownEvent, cx: &mut ViewContext<Self>) {
|
||||
fn on_mouse_down(
|
||||
&mut self,
|
||||
event: &MouseDownEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.is_selecting = true;
|
||||
let offset = self.index_for_mouse_position(event.position, cx);
|
||||
let offset = self.index_for_mouse_position(event.position, window, cx);
|
||||
// Double click to select word
|
||||
if event.button == MouseButton::Left && event.click_count == 2 {
|
||||
self.select_word(offset, cx);
|
||||
self.select_word(offset, window, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
if event.modifiers.shift {
|
||||
self.select_to(offset, cx);
|
||||
self.select_to(offset, window, cx);
|
||||
} else {
|
||||
self.move_to(offset, cx)
|
||||
self.move_to(offset, window, cx)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_up(&mut self, _: &MouseUpEvent, _: &mut ViewContext<Self>) {
|
||||
fn on_mouse_up(&mut self, _: &MouseUpEvent, _window: &mut Window, _cx: &mut Context<Self>) {
|
||||
self.is_selecting = false;
|
||||
self.selected_word_range = None;
|
||||
}
|
||||
|
||||
fn on_scroll_wheel(&mut self, event: &ScrollWheelEvent, _: &mut ViewContext<Self>) {
|
||||
fn on_scroll_wheel(
|
||||
&mut self,
|
||||
event: &ScrollWheelEvent,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) {
|
||||
let delta = event.delta.pixel_delta(self.last_line_height);
|
||||
let safe_y_range =
|
||||
(-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
|
||||
@@ -801,11 +844,16 @@ impl TextInput {
|
||||
self.scroll_handle.set_offset(offset);
|
||||
}
|
||||
|
||||
fn show_character_palette(&mut self, _: &ShowCharacterPalette, cx: &mut ViewContext<Self>) {
|
||||
cx.show_character_palette();
|
||||
fn show_character_palette(
|
||||
&mut self,
|
||||
_: &ShowCharacterPalette,
|
||||
window: &mut Window,
|
||||
_: &mut Context<Self>,
|
||||
) {
|
||||
window.show_character_palette();
|
||||
}
|
||||
|
||||
fn copy(&mut self, _: &Copy, cx: &mut ViewContext<Self>) {
|
||||
fn copy(&mut self, _: &Copy, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -814,7 +862,7 @@ impl TextInput {
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
|
||||
}
|
||||
|
||||
fn cut(&mut self, _: &Cut, cx: &mut ViewContext<Self>) {
|
||||
fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selected_range.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -822,27 +870,33 @@ impl TextInput {
|
||||
let range = self.range_from_utf16(&self.selected_range);
|
||||
let selected_text = self.text[range].to_string();
|
||||
cx.write_to_clipboard(ClipboardItem::new_string(selected_text));
|
||||
self.replace_text_in_range(None, "", cx);
|
||||
self.replace_text_in_range(None, "", window, cx);
|
||||
}
|
||||
|
||||
fn paste(&mut self, _: &Paste, cx: &mut ViewContext<Self>) {
|
||||
fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
|
||||
if let Some(clipboard) = cx.read_from_clipboard() {
|
||||
let mut new_text = clipboard.text().unwrap_or_default();
|
||||
if !self.multi_line {
|
||||
new_text = new_text.replace('\n', "");
|
||||
}
|
||||
|
||||
self.replace_text_in_range(None, &new_text, cx);
|
||||
self.replace_text_in_range(None, &new_text, window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_history(&mut self, range: &Range<usize>, new_text: &str, cx: &mut ViewContext<Self>) {
|
||||
fn push_history(
|
||||
&mut self,
|
||||
range: &Range<usize>,
|
||||
new_text: &str,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.history.ignore {
|
||||
return;
|
||||
}
|
||||
|
||||
let old_text = self
|
||||
.text_for_range(self.range_to_utf16(range), &mut None, cx)
|
||||
.text_for_range(self.range_to_utf16(range), &mut None, window, cx)
|
||||
.unwrap_or("".to_string());
|
||||
|
||||
let new_range = range.start..range.start + new_text.len();
|
||||
@@ -855,29 +909,34 @@ impl TextInput {
|
||||
));
|
||||
}
|
||||
|
||||
fn undo(&mut self, _: &Undo, cx: &mut ViewContext<Self>) {
|
||||
fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.history.ignore = true;
|
||||
if let Some(changes) = self.history.undo() {
|
||||
for change in changes {
|
||||
let range_utf16 = self.range_to_utf16(&change.new_range);
|
||||
self.replace_text_in_range(Some(range_utf16), &change.old_text, cx);
|
||||
self.replace_text_in_range(Some(range_utf16), &change.old_text, window, cx);
|
||||
}
|
||||
}
|
||||
self.history.ignore = false;
|
||||
}
|
||||
|
||||
fn redo(&mut self, _: &Redo, cx: &mut ViewContext<Self>) {
|
||||
fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.history.ignore = true;
|
||||
if let Some(changes) = self.history.redo() {
|
||||
for change in changes {
|
||||
let range_utf16 = self.range_to_utf16(&change.old_range);
|
||||
self.replace_text_in_range(Some(range_utf16), &change.new_text, cx);
|
||||
self.replace_text_in_range(Some(range_utf16), &change.new_text, window, cx);
|
||||
}
|
||||
}
|
||||
self.history.ignore = false;
|
||||
}
|
||||
|
||||
fn move_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
/// Move the cursor to the given offset.
|
||||
///
|
||||
/// The offset is the UTF-8 offset.
|
||||
///
|
||||
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
|
||||
fn move_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
|
||||
self.selected_range = offset..offset;
|
||||
self.pause_blink_cursor(cx);
|
||||
self.update_preferred_x_offset(cx);
|
||||
@@ -892,7 +951,12 @@ impl TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
fn index_for_mouse_position(&self, position: Point<Pixels>, _: &WindowContext) -> usize {
|
||||
fn index_for_mouse_position(
|
||||
&self,
|
||||
position: Point<Pixels>,
|
||||
_window: &Window,
|
||||
_cx: &App,
|
||||
) -> usize {
|
||||
// If the text is empty, always return 0
|
||||
if self.text.is_empty() {
|
||||
return 0;
|
||||
@@ -987,7 +1051,12 @@ impl TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
fn select_to(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
/// Select the text from the current cursor position to the given offset.
|
||||
///
|
||||
/// The offset is the UTF-8 offset.
|
||||
///
|
||||
/// Ensure the offset use self.next_boundary or self.previous_boundary to get the correct offset.
|
||||
fn select_to(&mut self, offset: usize, _: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.selection_reversed {
|
||||
self.selected_range.start = offset
|
||||
} else {
|
||||
@@ -1015,7 +1084,7 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Select the word at the given offset.
|
||||
fn select_word(&mut self, offset: usize, cx: &mut ViewContext<Self>) {
|
||||
fn select_word(&mut self, offset: usize, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn is_word(c: char) -> bool {
|
||||
c.is_alphanumeric() || matches!(c, '_')
|
||||
}
|
||||
@@ -1023,10 +1092,10 @@ impl TextInput {
|
||||
let mut start = self.offset_to_utf16(offset);
|
||||
let mut end = start;
|
||||
let prev_text = self
|
||||
.text_for_range(0..start, &mut None, cx)
|
||||
.text_for_range(0..start, &mut None, window, cx)
|
||||
.unwrap_or_default();
|
||||
let next_text = self
|
||||
.text_for_range(end..self.text.len(), &mut None, cx)
|
||||
.text_for_range(end..self.text.len(), &mut None, window, cx)
|
||||
.unwrap_or_default();
|
||||
|
||||
let prev_chars = prev_text.chars().rev().peekable();
|
||||
@@ -1053,7 +1122,7 @@ impl TextInput {
|
||||
cx.notify()
|
||||
}
|
||||
|
||||
fn unselect(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn unselect(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.selected_range = self.cursor_offset()..self.cursor_offset();
|
||||
cx.notify()
|
||||
}
|
||||
@@ -1112,36 +1181,46 @@ impl TextInput {
|
||||
}
|
||||
|
||||
/// Returns the true to let InputElement to render cursor, when Input is focused and current BlinkCursor is visible.
|
||||
pub(crate) fn show_cursor(&self, cx: &WindowContext) -> bool {
|
||||
self.focus_handle.is_focused(cx) && self.blink_cursor.read(cx).visible()
|
||||
pub(crate) fn show_cursor(&self, window: &Window, cx: &App) -> bool {
|
||||
self.focus_handle.is_focused(window) && self.blink_cursor.read(cx).visible()
|
||||
}
|
||||
|
||||
fn on_focus(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn on_focus(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.blink_cursor.update(cx, |cursor, cx| {
|
||||
cursor.start(cx);
|
||||
});
|
||||
cx.emit(InputEvent::Focus);
|
||||
}
|
||||
|
||||
fn on_blur(&mut self, cx: &mut ViewContext<Self>) {
|
||||
self.unselect(cx);
|
||||
fn on_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.unselect(window, cx);
|
||||
self.blink_cursor.update(cx, |cursor, cx| {
|
||||
cursor.stop(cx);
|
||||
});
|
||||
cx.emit(InputEvent::Blur);
|
||||
}
|
||||
|
||||
fn pause_blink_cursor(&mut self, cx: &mut ViewContext<Self>) {
|
||||
fn pause_blink_cursor(&mut self, cx: &mut Context<Self>) {
|
||||
self.blink_cursor.update(cx, |cursor, cx| {
|
||||
cursor.pause(cx);
|
||||
});
|
||||
}
|
||||
|
||||
fn on_key_down_for_blink_cursor(&mut self, _: &KeyDownEvent, cx: &mut ViewContext<Self>) {
|
||||
fn on_key_down_for_blink_cursor(
|
||||
&mut self,
|
||||
_: &KeyDownEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.pause_blink_cursor(cx)
|
||||
}
|
||||
|
||||
pub(super) fn on_drag_move(&mut self, event: &MouseMoveEvent, cx: &mut ViewContext<Self>) {
|
||||
pub(super) fn on_drag_move(
|
||||
&mut self,
|
||||
event: &MouseMoveEvent,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.text.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -1150,7 +1229,7 @@ impl TextInput {
|
||||
return;
|
||||
}
|
||||
|
||||
if !self.focus_handle.is_focused(cx) {
|
||||
if !self.focus_handle.is_focused(window) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1158,8 +1237,8 @@ impl TextInput {
|
||||
return;
|
||||
}
|
||||
|
||||
let offset = self.index_for_mouse_position(event.position, cx);
|
||||
self.select_to(offset, cx);
|
||||
let offset = self.index_for_mouse_position(event.position, window, cx);
|
||||
self.select_to(offset, window, cx);
|
||||
}
|
||||
|
||||
fn is_valid_input(&self, new_text: &str) -> bool {
|
||||
@@ -1187,12 +1266,13 @@ impl Sizable for TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
impl ViewInputHandler for TextInput {
|
||||
impl EntityInputHandler for TextInput {
|
||||
fn text_for_range(
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
adjusted_range: &mut Option<Range<usize>>,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<String> {
|
||||
let range = self.range_from_utf16(&range_utf16);
|
||||
adjusted_range.replace(self.range_to_utf16(&range));
|
||||
@@ -1202,7 +1282,8 @@ impl ViewInputHandler for TextInput {
|
||||
fn selected_text_range(
|
||||
&mut self,
|
||||
_ignore_disabled_input: bool,
|
||||
_cx: &mut ViewContext<Self>,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<UTF16Selection> {
|
||||
Some(UTF16Selection {
|
||||
range: self.range_to_utf16(&self.selected_range),
|
||||
@@ -1210,13 +1291,17 @@ impl ViewInputHandler for TextInput {
|
||||
})
|
||||
}
|
||||
|
||||
fn marked_text_range(&self, _cx: &mut ViewContext<Self>) -> Option<Range<usize>> {
|
||||
fn marked_text_range(
|
||||
&self,
|
||||
_window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Option<Range<usize>> {
|
||||
self.marked_range
|
||||
.as_ref()
|
||||
.map(|range| self.range_to_utf16(range))
|
||||
}
|
||||
|
||||
fn unmark_text(&mut self, _cx: &mut ViewContext<Self>) {
|
||||
fn unmark_text(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.marked_range = None;
|
||||
}
|
||||
|
||||
@@ -1224,7 +1309,8 @@ impl ViewInputHandler for TextInput {
|
||||
&mut self,
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.disabled {
|
||||
return;
|
||||
@@ -1242,7 +1328,7 @@ impl ViewInputHandler for TextInput {
|
||||
return;
|
||||
}
|
||||
|
||||
self.push_history(&range, new_text, cx);
|
||||
self.push_history(&range, new_text, window, cx);
|
||||
self.text = pending_text;
|
||||
self.selected_range = range.start + new_text.len()..range.start + new_text.len();
|
||||
self.marked_range.take();
|
||||
@@ -1256,7 +1342,8 @@ impl ViewInputHandler for TextInput {
|
||||
range_utf16: Option<Range<usize>>,
|
||||
new_text: &str,
|
||||
new_selected_range_utf16: Option<Range<usize>>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
if self.disabled {
|
||||
return;
|
||||
@@ -1273,7 +1360,7 @@ impl ViewInputHandler for TextInput {
|
||||
return;
|
||||
}
|
||||
|
||||
self.push_history(&range, new_text, cx);
|
||||
self.push_history(&range, new_text, window, cx);
|
||||
self.text = pending_text;
|
||||
self.marked_range = Some(range.start..range.start + new_text.len());
|
||||
self.selected_range = new_selected_range_utf16
|
||||
@@ -1291,7 +1378,8 @@ impl ViewInputHandler for TextInput {
|
||||
&mut self,
|
||||
range_utf16: Range<usize>,
|
||||
bounds: Bounds<Pixels>,
|
||||
_: &mut ViewContext<Self>,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
) -> Option<Bounds<Pixels>> {
|
||||
let line_height = self.last_line_height;
|
||||
let lines = self.last_layout.as_ref()?;
|
||||
@@ -1325,19 +1413,19 @@ impl ViewInputHandler for TextInput {
|
||||
}
|
||||
}
|
||||
|
||||
impl FocusableView for TextInput {
|
||||
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
|
||||
impl Focusable for TextInput {
|
||||
fn focus_handle(&self, _cx: &App) -> FocusHandle {
|
||||
self.focus_handle.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for TextInput {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
const LINE_HEIGHT: Rems = Rems(1.25);
|
||||
let focused = self.focus_handle.is_focused(cx);
|
||||
let focused = self.focus_handle.is_focused(window);
|
||||
|
||||
let prefix = self.prefix.as_ref().map(|build| build(cx));
|
||||
let suffix = self.suffix.as_ref().map(|build| build(cx));
|
||||
let prefix = self.prefix.as_ref().map(|build| build(window, cx));
|
||||
let suffix = self.suffix.as_ref().map(|build| build(window, cx));
|
||||
|
||||
div()
|
||||
.flex()
|
||||
@@ -1392,7 +1480,7 @@ impl Render for TextInput {
|
||||
this.bg(cx.theme().base.step(cx, ColorScaleStep::THREE))
|
||||
.rounded(px(cx.theme().radius))
|
||||
.when(cx.theme().shadow, |this| this.shadow_sm())
|
||||
.when(focused, |this| this.outline(cx))
|
||||
.when(focused, |this| this.outline(window, cx))
|
||||
.when(prefix.is_none(), |this| this.input_pl(self.size))
|
||||
.when(suffix.is_none(), |this| this.input_pr(self.size))
|
||||
})
|
||||
@@ -1404,18 +1492,14 @@ impl Render for TextInput {
|
||||
.id("TextElement")
|
||||
.flex_grow()
|
||||
.overflow_x_hidden()
|
||||
.child(TextElement::new(cx.view().clone())),
|
||||
.child(TextElement::new(cx.model().clone())),
|
||||
)
|
||||
.when(self.loading, |this| {
|
||||
this.child(Indicator::new().color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN)))
|
||||
})
|
||||
.when(
|
||||
self.cleanable && !self.loading && !self.text.is_empty() && self.is_single_line(),
|
||||
|this| this.child(ClearButton::new(cx).on_click(cx.listener(Self::clean))),
|
||||
)
|
||||
.children(suffix)
|
||||
.when(self.is_multi_line(), |this| {
|
||||
let entity_id = cx.view().entity_id();
|
||||
let entity_id = cx.model().entity_id();
|
||||
if self.last_layout.is_some() {
|
||||
let scroll_size = self.scroll_size;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user