stub out <textarea> element

This commit is contained in:
temportalflux
2026-07-11 09:31:07 -04:00
parent bad745a761
commit 3f6faa20a5
4 changed files with 196 additions and 28 deletions
@@ -1,11 +1,10 @@
use crate::editable_text::{
EditableInputActionElement, StateBackedElement, TextInputState, UnicodeTextStorage,
EditableInputActionElement, InitStorage, StateBackedElement, TextInputState,
};
use gpui::{
App, Element, ElementId, Entity, Hitbox, InteractiveElement, Interactivity, IntoElement,
SharedString, StyleRefinement, Styled, TextStyle, Window,
};
use std::rc::Rc;
#[track_caller]
pub fn input(id: impl Into<ElementId>) -> TextInputElement {
@@ -47,17 +46,6 @@ impl IntoElement for TextInputElement {
}
}
#[derive(Clone, Default)]
pub(super) struct InitStorage(Option<Rc<dyn Fn(&mut App) -> Box<dyn UnicodeTextStorage>>>);
impl InitStorage {
fn exec(&self, cx: &mut App) -> Box<dyn UnicodeTextStorage> {
match &self.0 {
None => Box::new(String::new()),
Some(init) => (*init)(cx),
}
}
}
impl EditableInputActionElement for TextInputElement {}
impl super::StateBackedElement for TextInputElement {
type State = TextInputState;
@@ -80,20 +68,24 @@ impl super::StateBackedElement for TextInputElement {
}
}
#[doc(hidden)]
pub struct LayoutState {
state: Entity<TextInputState>,
text_style: TextStyle,
}
pub mod element {
use super::*;
#[doc(hidden)]
pub struct PrepaintState {
hitbox: Option<Hitbox>,
#[doc(hidden)]
pub struct LayoutState {
pub state: Entity<TextInputState>,
pub text_style: TextStyle,
}
#[doc(hidden)]
pub struct PrepaintState {
pub hitbox: Option<Hitbox>,
}
}
impl Element for TextInputElement {
type RequestLayoutState = LayoutState;
type PrepaintState = PrepaintState;
type RequestLayoutState = element::LayoutState;
type PrepaintState = element::PrepaintState;
fn id(&self) -> Option<ElementId> {
self.interactivity.element_id.clone()
@@ -130,7 +122,7 @@ impl Element for TextInputElement {
},
);
let layout_state = LayoutState {
let layout_state = element::LayoutState {
state,
text_style: resolved_text_style.unwrap_or_else(|| window.text_style()),
};
@@ -1,5 +1,5 @@
use gpui::NavigationDirection;
use std::ops::Range;
use gpui::{App, NavigationDirection};
use std::{ops::Range, rc::Rc};
use unicode_segmentation::UnicodeSegmentation;
pub enum TextBoundary {
@@ -13,6 +13,17 @@ pub enum TextBoundary {
Document,
}
#[derive(Clone, Default)]
pub(super) struct InitStorage(Option<Rc<dyn Fn(&mut App) -> Box<dyn UnicodeTextStorage>>>);
impl InitStorage {
pub fn exec(&self, cx: &mut App) -> Box<dyn UnicodeTextStorage> {
match &self.0 {
None => Box::new(String::new()),
Some(init) => (*init)(cx),
}
}
}
pub trait UnicodeTextStorage {
/// Returns a reference to the utf8 string.
fn content_utf8(&self) -> &str;
@@ -1,3 +1,160 @@
use gpui::ElementId;
use crate::editable_text::{
EditableInputActionElement, InitStorage, StateBackedElement, TextAreaState,
};
use gpui::{
App, Element, ElementId, Entity, Hitbox, InteractiveElement, Interactivity, IntoElement,
SharedString, StyleRefinement, Styled, TextStyle, Window,
};
pub fn text_area(id: impl Into<ElementId>) {}
pub fn text_area(id: impl Into<ElementId>) -> TextAreaElement {
let mut this = TextAreaElement {
id: id.into(),
placeholder: None,
interactivity: Interactivity::new(),
init_storage: InitStorage::default(),
};
this = this.key_context(super::DEFAULT_INPUT_CONTEXT);
this.register_actions();
this
}
// TODO: Disabled flag/state?
pub struct TextAreaElement {
id: ElementId,
placeholder: Option<SharedString>,
interactivity: Interactivity,
init_storage: InitStorage,
}
impl InteractiveElement for TextAreaElement {
fn interactivity(&mut self) -> &mut Interactivity {
&mut self.interactivity
}
}
impl Styled for TextAreaElement {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.interactivity.base_style
}
}
impl IntoElement for TextAreaElement {
type Element = Self;
fn into_element(self) -> Self::Element {
self
}
}
impl EditableInputActionElement for TextAreaElement {}
impl super::StateBackedElement for TextAreaElement {
type State = TextAreaState;
type InitProps = (ElementId, InitStorage);
fn init_props(&self) -> Self::InitProps {
(self.id.clone(), self.init_storage.clone())
}
fn get_or_init_state(
init_props: &Self::InitProps,
window: &mut Window,
cx: &mut App,
) -> Entity<Self::State> {
// Get the state from the app using the element's id as the key.
// If it doesnt exist, initialize a new state with the user's desired storage medium.
window.use_keyed_state(init_props.0.clone(), cx, |_window, cx| {
TextAreaState::new(init_props.1.exec(cx), cx)
})
}
}
pub mod element {
use super::*;
#[doc(hidden)]
pub struct LayoutState {
pub state: Entity<TextAreaState>,
pub text_style: TextStyle,
}
#[doc(hidden)]
pub struct PrepaintState {
pub hitbox: Option<Hitbox>,
}
}
impl Element for TextAreaElement {
type RequestLayoutState = element::LayoutState;
type PrepaintState = 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 gpui::Window,
cx: &mut gpui::App,
) -> (gpui::LayoutId, Self::RequestLayoutState) {
let mut resolved_text_style = None;
let state = self.get_state(window, cx);
let layout_id = self.interactivity.request_layout(
global_id,
inspector_id,
window,
cx,
|element_style, window, cx| {
window.with_text_style(element_style.text_style().cloned(), |window| {
resolved_text_style = Some(window.text_style());
let mut style = element_style.clone();
if let gpui::Length::Auto = style.size.width {
style.size.width = gpui::relative(1.).into();
}
if let gpui::Length::Auto = style.size.height {
style.size.height = gpui::relative(1.).into();
}
window.request_layout(style, None, cx)
})
},
);
let layout_state = element::LayoutState {
state,
text_style: resolved_text_style.unwrap_or_else(|| window.text_style()),
};
(layout_id, layout_state)
}
fn prepaint(
&mut self,
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!()
}
fn paint(
&mut self,
id: Option<&gpui::GlobalElementId>,
inspector_id: Option<&gpui::InspectorElementId>,
bounds: gpui::Bounds<gpui::Pixels>,
request_layout: &mut Self::RequestLayoutState,
prepaint: &mut Self::PrepaintState,
window: &mut gpui::Window,
cx: &mut gpui::App,
) {
todo!()
}
}
@@ -1,5 +1,6 @@
use crate::editable_text::{
EditableTextActionHandler, TextBoundary, TextInputStateBase, TextStateNotifier,
UnicodeTextStorage,
notify::{TextChanged, TextHistoryPushed},
};
use gpui::{
@@ -15,6 +16,13 @@ pub struct TextAreaState {
impl EventEmitter<TextChanged> for TextAreaState {}
impl EventEmitter<TextHistoryPushed> for TextAreaState {}
impl TextAreaState {
pub fn new(storage: impl Into<Box<dyn UnicodeTextStorage>>, cx: &mut Context<Self>) -> Self {
let internal = TextInputStateBase::new(storage, cx);
Self { internal }
}
}
impl TextStateNotifier for Context<'_, TextAreaState> {
fn notify_changed(&mut self) {
self.notify();