GPUI Box GitHub

Components / controls

TextArea view

Wrapped, multi-line editable text.

A view survives a frame. Hold it in an Entity with cx.new(..) and reach it with .update(..).

use gpui_kit::controls::textarea::TextArea;

Reviewed in touch-inputs textarea editor-options.

Construct
new(ident: impl Into<Ident>, window: &mut Window, cx: &mut Context<Self>) -> Self
detached(ident: impl Into<Ident>, cx: &mut Context<Self>) -> Self
bind(area: &Entity<Self>, signal: &Signal<String>, cx: &mut App) -> Vec<Subscription>
Options

Chain onto the value.

placeholder(placeholder: impl Into<SharedString>) -> Self
input_options(options: gpui::TextInputOptions) -> Self
frame(frame: Frame) -> Self
wrap(wrap: TextAreaWrap) -> Self
text(text: impl Into<SharedString>) -> Self
invalid(invalid: bool) -> Self
required(required: bool) -> Self
read_only(read_only: bool) -> Self
rows(rows: usize) -> Self
max_rows(max_rows: usize) -> Self
autosize(min_rows: usize, max_rows: usize) -> Self
enter(enter: Enter) -> Self
max_length(max_length: usize) -> Self
Commands

Need a Context, so they need a view.

set_input_options(options: gpui::TextInputOptions, cx: &mut Context<Self>)
set_placeholder(placeholder: impl Into<SharedString>, cx: &mut Context<Self>)
set_frame(frame: Frame, cx: &mut Context<Self>)
set_wrap(wrap: TextAreaWrap, cx: &mut Context<Self>)
set_required(required: bool, cx: &mut Context<Self>)
set_rows(rows: usize, cx: &mut Context<Self>)
set_max_rows(max_rows: Option<usize>, cx: &mut Context<Self>)
set_autosize(rows: Option<(usize, usize)>, cx: &mut Context<Self>)
set_enter(enter: Enter, cx: &mut Context<Self>)
set_max_length(max_length: Option<usize>, cx: &mut Context<Self>)
set_control_size(size: ControlSize, cx: &mut Context<Self>)
set_arrows_claimed(claimed: bool)
set_completion_claimed(claimed: bool)
set_value(value: impl Into<SharedString>, cx: &mut Context<Self>)
insert(text: &str, cx: &mut Context<Self>)
replace_range(range: Range<usize>, text: &str, cx: &mut Context<Self>) -> Option<Range<usize>>
set_disabled(disabled: bool, cx: &mut Context<Self>)
set_read_only(read_only: bool, cx: &mut Context<Self>)
set_invalid(invalid: bool, cx: &mut Context<Self>)
set_selected_range(range: Range<usize>, cx: &mut Context<Self>)
replace_ranges(edits: impl IntoIterator<Item = (Range<usize>, SharedString)>, cx: &mut Context<Self>) -> bool
set_selections(selections: impl IntoIterator<Item = (Range<usize>, bool)>, cx: &mut Context<Self>) -> bool
select_rectangle(anchor: Point<Pixels>, focus: Point<Pixels>, cx: &mut Context<Self>) -> bool
Queries

Only answer; they change nothing.

arrows_claimed() -> bool
completion_claimed() -> bool
value() -> &SharedString
document() -> gpui::EditSnapshot
snapshot() -> TextAreaSnapshot
revision() -> u64
wrap_mode() -> TextAreaWrap
is_empty() -> bool
is_disabled() -> bool
is_read_only() -> bool
selected_range() -> Range<usize>
cursor_offset() -> usize
selections() -> Vec<(Range<usize>, bool)>
bounds_for_range(range: Range<usize>) -> Option<Vec<Bounds<Pixels>>>
caret_bounds() -> Option<Bounds<Pixels>>
bounds_for_position(offset: usize) -> Option<Bounds<Pixels>>
cursor_row() -> usize
shaping_work() -> Option<gpui::EditableTextWork>
accessibility_work() -> gpui::AccessibleTextWork
row_index_work() -> usize
wrapped_index_work() -> usize
measured() -> Option<Measured>
horizontal_scroll_offset() -> Pixels
Reports

The variants of the event it emits. It never applies the change itself.

  • Edited
  • Change
  • Submit
  • Cancel
  • Pasted
  • MoveUp
  • MoveDown
  • AcceptCompletion
  • DismissCompletion
  • IndentRequested
  • OutdentRequested
  • SelectionChanged
  • GeometryChanged
  • Focus
  • Blur
Example · touch-inputs
fn touch_inputs(window: &mut Window, cx: &mut App) -> AnyElement {
    if !cx.has_global::<TouchInputs>() {
        let email = cx.new(|cx| {
            TextInput::new("scene.touch.email", window, cx)
                .name("Email")
                .placeholder("[email protected]")
                .control_size(ControlSize::Touch)
                .input_options(gpui::TextInputOptions {
                    purpose: gpui::KeyboardPurpose::Email,
                    action: gpui::TextInputAction::Next,
                    autofill: Some(gpui::AutofillPurpose::Email),
                    ..Default::default()
                })
        });
        let password = cx.new(|cx| {
            PasswordInput::new("scene.touch.password", window, cx)
                .name("Password")
                .placeholder("Password")
                .control_size(ControlSize::Touch)
                .input_options(gpui::TextInputOptions {
                    action: gpui::TextInputAction::Done,
                    autofill: Some(gpui::AutofillPurpose::CurrentPassword),
                    ..Default::default()
                })
        });
        let password_focus = password.clone();
        let host_window = window.window_handle();
        let next = cx.subscribe(&email, move |_, action: &gpui::TextInputAction, cx| {
            if *action == gpui::TextInputAction::Next {
                let focus = password_focus.read(cx).focus_handle(cx);
                cx.update_window(host_window, |_, window, cx| focus.focus(window, cx))
                    .ok();
            }
        });
        let code = cx.new(|cx| {
            OneTimeCodeInput::new("scene.touch.code", window, cx)
                .name("Code")
                .slots(6)
                .control_size(ControlSize::Touch)
                .input_options(gpui::TextInputOptions {
                    purpose: gpui::KeyboardPurpose::Number,
                    autofill: Some(gpui::AutofillPurpose::OneTimeCode),
                    ..Default::default()
                })
        });
        let notes = cx.new(|cx| {
            TextArea::new("scene.touch.notes", window, cx)
                .placeholder("Notes · wraps at this width")
                .rows(2)
                .control_size(ControlSize::Touch)
        });
        let disabled = cx.new(|cx| {
            TextInput::new("scene.touch.disabled", window, cx)
                .name("Disabled fixture")
                .text("Managed by the host")
                .disabled(true)
                .control_size(ControlSize::Touch)
        });
        let invalid = cx.new(|cx| {
            TextInput::new("scene.touch.invalid", window, cx)
                .name("Invalid email fixture")
                .text("not an address")
                .invalid(true)
                .control_size(ControlSize::Touch)
        });
        cx.set_global(TouchInputs {
            fields: vec![
                ("scene.touch.email", "Email", email.into()),
                ("scene.touch.password", "Password", password.into()),
                (
                    "scene.touch.code",
                    "Verification code · 6 characters",
                    code.into(),
                ),
                ("scene.touch.notes", "Notes", notes.into()),
                ("scene.touch.disabled", "Managed field", disabled.into()),
                ("scene.touch.invalid", "Invalid email", invalid.into()),
            ],
            _next: next,
        });
    }
    let theme = cx.theme().clone();
    stack(&theme)
        .w(px(390.0))
        .max_w_full()
        .child(caption(
            &theme,
            "Touch editing fixtures · keyboard hints are platform-dependent",
        ))
        .children(
            cx.global::<TouchInputs>()
                .fields
                .iter()
                .map(|(id, label, view)| {
                    FormField::new(format!("{id}.field"), *label)
                        .control(*id)
                        .child(view.clone())
                }),
        )
        .child(caption(
            &theme,
            "Code paste is not SMS autofill. Error and disabled values remain visible.",
        ))
        .into_any_element()
}

Source: crates/gpui-kit/src/controls/textarea/mod.rs