stub out prepaint and paint for <input>
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
use crate::editable_text::{
|
||||
EditableInputActionElement, InitStorage, StateBackedElement, TextInputState,
|
||||
EditableInputActionElement, InitStorage, StateBackedElement, TextInputLayoutData,
|
||||
TextInputState,
|
||||
};
|
||||
use gpui::{
|
||||
App, Element, ElementId, Entity, Hitbox, InteractiveElement, Interactivity, IntoElement,
|
||||
SharedString, StyleRefinement, Styled, TextStyle, Window,
|
||||
App, Bounds, ContentMask, CursorStyle, Display, Element, ElementId, ElementInputHandler,
|
||||
Entity, FocusHandle, Focusable, Hitbox, HitboxBehavior, Hsla, InteractiveElement,
|
||||
Interactivity, IntoElement, PaintQuad, Pixels, ShapedLine, SharedString, Style,
|
||||
StyleRefinement, Styled, TextRun, TextStyle, Window, fill, point, size,
|
||||
};
|
||||
|
||||
#[track_caller]
|
||||
@@ -80,6 +83,13 @@ pub mod element {
|
||||
#[doc(hidden)]
|
||||
pub struct PrepaintState {
|
||||
pub hitbox: Option<Hitbox>,
|
||||
pub line: Option<ShapedLine>,
|
||||
pub focus_handle: FocusHandle,
|
||||
pub selection: Option<PaintQuad>,
|
||||
pub caret_quad: Option<PaintQuad>,
|
||||
pub scroll_x: Pixels,
|
||||
pub display_text: SharedString,
|
||||
pub caret_visible: bool,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,7 +132,7 @@ impl Element for TextInputElement {
|
||||
},
|
||||
);
|
||||
|
||||
let layout_state = element::LayoutState {
|
||||
let layout_state = Self::RequestLayoutState {
|
||||
state,
|
||||
text_style: resolved_text_style.unwrap_or_else(|| window.text_style()),
|
||||
};
|
||||
@@ -131,19 +141,119 @@ impl Element for TextInputElement {
|
||||
|
||||
fn prepaint(
|
||||
&mut self,
|
||||
id: Option<&gpui::GlobalElementId>,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
bounds: gpui::Bounds<gpui::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::App,
|
||||
) -> Self::PrepaintState {
|
||||
todo!()
|
||||
let input = request_layout.state.read(cx);
|
||||
|
||||
let focus_handle = input.focus_handle(cx);
|
||||
let caret_pos = input.caret_pos();
|
||||
let selection = input.selected_range();
|
||||
// TODO: horizontal scroll
|
||||
let scroll_x_input = Pixels::ZERO; //input.scroll_x();
|
||||
// TODO: Cursor blinking
|
||||
let cursor_visible = true; // input.cursor_visible();
|
||||
|
||||
let text_color = Hsla::white(); // TODO: as an element param
|
||||
let placeholder_color = Hsla::black().opacity(0.5); // TODO: as an element param
|
||||
let selection_color = Hsla::blue().opacity(0.5); // TODO: as an element param
|
||||
let caret_color = Hsla::white(); // TODO: as an element param
|
||||
|
||||
let text_value = input.storage().content_utf8();
|
||||
let is_empty = text_value.is_empty();
|
||||
|
||||
let (display_text, run_color) = match is_empty {
|
||||
// TODO: Can the SharedString allocation be avoided?
|
||||
false => (SharedString::new(text_value), text_color),
|
||||
true => {
|
||||
let value = self.placeholder.as_ref().cloned().unwrap_or_default();
|
||||
(value, placeholder_color)
|
||||
}
|
||||
};
|
||||
|
||||
let style = window.text_style();
|
||||
let font_size = style.font_size.to_pixels(window.rem_size());
|
||||
let run = TextRun {
|
||||
len: display_text.len(),
|
||||
font: style.font(),
|
||||
color: run_color,
|
||||
background_color: None,
|
||||
underline: None,
|
||||
strikethrough: None,
|
||||
};
|
||||
let line = window
|
||||
.text_system()
|
||||
.shape_line(display_text.clone(), font_size, &[run], None);
|
||||
|
||||
let caret_x_line = line.x_for_index(caret_pos);
|
||||
let cursor_thickness = gpui::px(2.0);
|
||||
let max_cursor_x = (bounds.size.width - cursor_thickness).max(Pixels::ZERO);
|
||||
let max_scroll_x = (line.width - max_cursor_x).max(Pixels::ZERO);
|
||||
let mut scroll_x = scroll_x_input.clamp(Pixels::ZERO, max_scroll_x);
|
||||
if caret_x_line < scroll_x {
|
||||
scroll_x = caret_x_line;
|
||||
} else if caret_x_line > scroll_x + max_cursor_x {
|
||||
scroll_x = caret_x_line - max_cursor_x;
|
||||
}
|
||||
scroll_x = scroll_x.clamp(Pixels::ZERO, max_scroll_x);
|
||||
|
||||
let (selection_quad, cursor_quad) = if !selection.is_empty() && !is_empty {
|
||||
let start_x = line.x_for_index(selection.start);
|
||||
let end_x = line.x_for_index(selection.end);
|
||||
let quad = fill(
|
||||
Bounds::from_corners(
|
||||
point(bounds.left() + start_x.min(end_x) - scroll_x, bounds.top()),
|
||||
point(
|
||||
bounds.left() + start_x.max(end_x) - scroll_x,
|
||||
bounds.bottom(),
|
||||
),
|
||||
),
|
||||
selection_color,
|
||||
);
|
||||
(Some(quad), None)
|
||||
} else {
|
||||
let cursor_paint_x = bounds.left() + caret_x_line - scroll_x;
|
||||
let quad = fill(
|
||||
Bounds::new(
|
||||
point(cursor_paint_x, bounds.top()),
|
||||
size(cursor_thickness, bounds.bottom() - bounds.top()),
|
||||
),
|
||||
caret_color,
|
||||
);
|
||||
(None, Some(quad))
|
||||
};
|
||||
|
||||
let hitbox = self.interactivity.prepaint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
bounds.size,
|
||||
window,
|
||||
cx,
|
||||
|style, scroll_offset, hitbox, window, cx| {
|
||||
hitbox.or_else(|| Some(window.insert_hitbox(bounds, HitboxBehavior::Normal)))
|
||||
},
|
||||
);
|
||||
|
||||
Self::PrepaintState {
|
||||
hitbox,
|
||||
line: Some(line),
|
||||
focus_handle,
|
||||
selection: selection_quad,
|
||||
caret_quad: cursor_quad,
|
||||
scroll_x,
|
||||
display_text,
|
||||
caret_visible: cursor_visible,
|
||||
}
|
||||
}
|
||||
|
||||
fn paint(
|
||||
&mut self,
|
||||
id: Option<&gpui::GlobalElementId>,
|
||||
global_id: Option<&gpui::GlobalElementId>,
|
||||
inspector_id: Option<&gpui::InspectorElementId>,
|
||||
bounds: gpui::Bounds<gpui::Pixels>,
|
||||
request_layout: &mut Self::RequestLayoutState,
|
||||
@@ -151,6 +261,66 @@ impl Element for TextInputElement {
|
||||
window: &mut gpui::Window,
|
||||
cx: &mut gpui::App,
|
||||
) {
|
||||
todo!()
|
||||
if let Some(hitbox) = &prepaint.hitbox {
|
||||
window.set_cursor_style(CursorStyle::IBeam, hitbox);
|
||||
}
|
||||
|
||||
// NOTE: Skip when disabled
|
||||
let ime_handler = ElementInputHandler::new(bounds, request_layout.state.clone());
|
||||
window.handle_input(&prepaint.focus_handle, ime_handler, cx);
|
||||
|
||||
let mut layout_data = TextInputLayoutData::default();
|
||||
let perform_paint = |style: &Style, window: &mut Window, cx: &mut App| {
|
||||
if style.display == Display::None {
|
||||
return;
|
||||
}
|
||||
layout_data = window.with_content_mask(Some(ContentMask { bounds }), |window| {
|
||||
if let Some(sel) = prepaint.selection.take() {
|
||||
window.paint_quad(sel);
|
||||
}
|
||||
|
||||
let line = prepaint
|
||||
.line
|
||||
.take()
|
||||
.expect("prepaint always produces a line");
|
||||
let origin_x = bounds.left() - prepaint.scroll_x;
|
||||
let _ = line.paint(
|
||||
point(origin_x, bounds.top()),
|
||||
window.line_height(),
|
||||
gpui::TextAlign::Left,
|
||||
None,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
|
||||
// TODO: Render marked IME underlines
|
||||
|
||||
let is_focused = prepaint.focus_handle.is_focused(window);
|
||||
if is_focused
|
||||
&& prepaint.caret_visible
|
||||
&& let Some(cur) = prepaint.caret_quad.take()
|
||||
{
|
||||
window.paint_quad(cur);
|
||||
}
|
||||
|
||||
TextInputLayoutData {
|
||||
lines: vec![line],
|
||||
bounds,
|
||||
}
|
||||
});
|
||||
};
|
||||
self.interactivity.paint(
|
||||
global_id,
|
||||
inspector_id,
|
||||
bounds,
|
||||
prepaint.hitbox.as_ref(),
|
||||
window,
|
||||
cx,
|
||||
perform_paint,
|
||||
);
|
||||
|
||||
request_layout.state.update(cx, |state, _cx| {
|
||||
*state.layout_data_mut() = layout_data;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,19 @@ pub struct TextInputState {
|
||||
impl EventEmitter<TextChanged> for TextInputState {}
|
||||
impl EventEmitter<TextHistoryPushed> for TextInputState {}
|
||||
|
||||
impl std::ops::Deref for TextInputState {
|
||||
type Target = TextInputStateBase;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.internal
|
||||
}
|
||||
}
|
||||
impl std::ops::DerefMut for TextInputState {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.internal
|
||||
}
|
||||
}
|
||||
|
||||
impl TextInputState {
|
||||
pub fn new(storage: impl Into<Box<dyn UnicodeTextStorage>>, cx: &mut Context<Self>) -> Self {
|
||||
let internal = TextInputStateBase::new(storage, cx);
|
||||
|
||||
@@ -3,8 +3,8 @@ use crate::editable_text::{
|
||||
notify::{TextChanged, TextHistoryPushed},
|
||||
};
|
||||
use gpui::{
|
||||
App, ClipboardItem, Entity, FocusHandle, Focusable, NavigationDirection, Pixels, Point,
|
||||
UTF16Selection, Window,
|
||||
App, Bounds, ClipboardItem, Entity, FocusHandle, Focusable, NavigationDirection, Pixels, Point,
|
||||
ShapedLine, UTF16Selection, Window,
|
||||
};
|
||||
use std::ops::Range;
|
||||
|
||||
@@ -53,6 +53,18 @@ pub struct TextInputStateBase {
|
||||
click_count: usize,
|
||||
|
||||
focus_handle: FocusHandle,
|
||||
|
||||
layout_data: TextInputLayoutData,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct TextInputLayoutData {
|
||||
/// The `ShapedLine` produced by the painter's `prepaint`.
|
||||
/// Cached so IME `bounds_for_range` / `character_index_for_point` can evaluate without re-shaping.
|
||||
pub lines: Vec<ShapedLine>,
|
||||
/// The bounds of the text area, in window coordinates.
|
||||
/// Cached for IME operations.
|
||||
pub bounds: Bounds<Pixels>,
|
||||
}
|
||||
|
||||
impl Focusable for TextInputStateBase {
|
||||
@@ -74,6 +86,8 @@ impl TextInputStateBase {
|
||||
click_count: 0,
|
||||
|
||||
focus_handle: cx.focus_handle(),
|
||||
|
||||
layout_data: TextInputLayoutData::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +117,14 @@ impl TextInputStateBase {
|
||||
pub fn set_selected_range(&mut self, range: Range<usize>) {
|
||||
self.selected_range = range;
|
||||
}
|
||||
|
||||
pub(super) fn layout_data(&self) -> &TextInputLayoutData {
|
||||
&self.layout_data
|
||||
}
|
||||
|
||||
pub(super) fn layout_data_mut(&mut self) -> &mut TextInputLayoutData {
|
||||
&mut self.layout_data
|
||||
}
|
||||
}
|
||||
|
||||
impl TextInputStateBase {
|
||||
|
||||
@@ -16,6 +16,19 @@ pub struct TextAreaState {
|
||||
impl EventEmitter<TextChanged> for TextAreaState {}
|
||||
impl EventEmitter<TextHistoryPushed> for TextAreaState {}
|
||||
|
||||
impl std::ops::Deref for TextAreaState {
|
||||
type Target = TextInputStateBase;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.internal
|
||||
}
|
||||
}
|
||||
impl std::ops::DerefMut for TextAreaState {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.internal
|
||||
}
|
||||
}
|
||||
|
||||
impl TextAreaState {
|
||||
pub fn new(storage: impl Into<Box<dyn UnicodeTextStorage>>, cx: &mut Context<Self>) -> Self {
|
||||
let internal = TextInputStateBase::new(storage, cx);
|
||||
|
||||
Reference in New Issue
Block a user