input: Add max_rows to support auto-grow. (#768)

Closes #762


https://github.com/user-attachments/assets/068a3f11-8498-4db6-a2ac-00cc043f16cf
This commit is contained in:
Jason Lee 2025-04-02 15:54:19 +08:00 committed by GitHub
parent 253c93063f
commit c91dce3139
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 65 additions and 24 deletions

View file

@ -63,7 +63,8 @@ impl FormStory {
let bio_input = cx.new(|cx| { let bio_input = cx.new(|cx| {
let mut input = TextInput::new(window, cx) let mut input = TextInput::new(window, cx)
.multi_line() .multi_line()
.rows(10) .rows(5)
.max_rows(20)
.placeholder("Enter text here..."); .placeholder("Enter text here...");
input.set_text("Hello 世界this is GPUI component.", window, cx); input.set_text("Hello 世界this is GPUI component.", window, cx);
input input

View file

@ -1,7 +1,7 @@
use gpui::{ use gpui::{
actions, div, prelude::FluentBuilder as _, px, App, AppContext as _, ClickEvent, Context, actions, div, prelude::FluentBuilder as _, px, App, AppContext as _, ClickEvent, Context,
Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding, Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,
ParentElement as _, Render, SharedString, Styled, Window, ParentElement as _, Render, SharedString, Styled, Subscription, Window,
}; };
use regex::Regex; use regex::Regex;
@ -46,6 +46,8 @@ pub struct InputStory {
otp_input_small: Entity<OtpInput>, otp_input_small: Entity<OtpInput>,
otp_input_large: Entity<OtpInput>, otp_input_large: Entity<OtpInput>,
opt_input_sized: Entity<OtpInput>, opt_input_sized: Entity<OtpInput>,
_subscriptions: Vec<Subscription>,
} }
impl super::Story for InputStory { impl super::Story for InputStory {
@ -77,12 +79,8 @@ impl InputStory {
); );
input input
}); });
cx.subscribe_in(&input1, window, Self::on_input_event)
.detach();
let input2 = cx.new(|cx| TextInput::new(window, cx).placeholder("Enter text here...")); let input2 = cx.new(|cx| TextInput::new(window, cx).placeholder("Enter text here..."));
cx.subscribe_in(&input2, window, Self::on_input_event)
.detach();
let textarea = cx.new(|cx| { let textarea = cx.new(|cx| {
let mut input = TextInput::new(window, cx) let mut input = TextInput::new(window, cx)
@ -115,8 +113,6 @@ impl InputStory {
); );
input input
}); });
cx.subscribe_in(&textarea, window, Self::on_input_event)
.detach();
let number_input1_value = 1; let number_input1_value = 1;
let number_input1 = cx.new(|cx| { let number_input1 = cx.new(|cx| {
@ -124,8 +120,6 @@ impl InputStory {
input.set_value(number_input1_value.to_string(), window, cx); input.set_value(number_input1_value.to_string(), window, cx);
input input
}); });
cx.subscribe_in(&number_input1, window, Self::on_number_input1_event)
.detach();
let number_input2 = cx.new(|cx| { let number_input2 = cx.new(|cx| {
NumberInput::new(window, cx) NumberInput::new(window, cx)
@ -134,9 +128,6 @@ impl InputStory {
.small() .small()
}); });
cx.subscribe_in(&number_input2, window, Self::on_number_input2_event)
.detach();
let mask_input = cx.new(|cx| { let mask_input = cx.new(|cx| {
let mut input = TextInput::new(window, cx) let mut input = TextInput::new(window, cx)
.masked(true) .masked(true)
@ -168,14 +159,21 @@ impl InputStory {
}); });
let otp_input = cx.new(|cx| OtpInput::new(6, window, cx).masked(true)); let otp_input = cx.new(|cx| OtpInput::new(6, window, cx).masked(true));
cx.subscribe(&otp_input, |this, _, ev: &InputEvent, cx| match ev {
InputEvent::Change(text) => { let _subscriptions = vec![
this.otp_value = Some(text.clone()); cx.subscribe_in(&input1, window, Self::on_input_event),
cx.notify(); cx.subscribe_in(&input2, window, Self::on_input_event),
} cx.subscribe_in(&textarea, window, Self::on_input_event),
_ => {} cx.subscribe_in(&number_input1, window, Self::on_number_input1_event),
}) cx.subscribe_in(&number_input2, window, Self::on_number_input2_event),
.detach(); cx.subscribe(&otp_input, |this, _, ev: &InputEvent, cx| match ev {
InputEvent::Change(text) => {
this.otp_value = Some(text.clone());
cx.notify();
}
_ => {}
}),
];
Self { Self {
input1, input1,
@ -230,6 +228,7 @@ impl InputStory {
.default_value("654321") .default_value("654321")
.with_size(px(55.)) .with_size(px(55.))
}), }),
_subscriptions,
} }
} }

View file

@ -226,6 +226,8 @@ pub struct TextInput {
pub(super) cleanable: bool, pub(super) cleanable: bool,
pub(super) size: Size, pub(super) size: Size,
pub(super) rows: usize, pub(super) rows: usize,
pub(super) min_rows: usize,
pub(super) max_rows: Option<usize>,
/// For special case, e.g.: NumberInput + - button /// For special case, e.g.: NumberInput + - button
pub(super) no_gap: bool, pub(super) no_gap: bool,
pub(super) height: Option<gpui::DefiniteLength>, pub(super) height: Option<gpui::DefiniteLength>,
@ -293,6 +295,8 @@ impl TextInput {
pattern: None, pattern: None,
validate: None, validate: None,
rows: 2, rows: 2,
min_rows: 2,
max_rows: None,
last_layout: None, last_layout: None,
last_bounds: None, last_bounds: None,
last_selected_range: None, last_selected_range: None,
@ -456,6 +460,19 @@ impl TextInput {
/// default: 2 /// default: 2
pub fn rows(mut self, rows: usize) -> Self { pub fn rows(mut self, rows: usize) -> Self {
self.rows = rows; self.rows = rows;
self.min_rows = rows;
self
}
/// Set the maximum number of rows for the multi-line Textarea.
///
/// If max_rows is more than rows, then will enable auto-grow.
///
/// This is only used when `multi_line` is set to true.
///
/// default: None
pub fn max_rows(mut self, max_rows: usize) -> Self {
self.max_rows = Some(max_rows);
self self
} }
@ -1003,6 +1020,23 @@ impl TextInput {
cx.emit(InputEvent::PressEnter); cx.emit(InputEvent::PressEnter);
} }
fn check_to_auto_grow(&mut self, _: &mut Window, cx: &mut Context<Self>) {
if !self.is_multi_line() {
return;
}
let Some(max_rows) = self.max_rows else {
return;
};
let changed_rows = ((self.scroll_size.height - self.input_bounds.size.height)
/ self.last_line_height) as isize;
self.rows = (self.rows as isize + changed_rows)
.clamp(self.min_rows as isize, max_rows as isize)
.max(0) as usize;
cx.notify();
}
fn clean(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) { fn clean(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
self.replace_text("", window, cx); self.replace_text("", window, cx);
} }
@ -1037,19 +1071,24 @@ impl TextInput {
&mut self, &mut self,
event: &ScrollWheelEvent, event: &ScrollWheelEvent,
_window: &mut Window, _window: &mut Window,
_cx: &mut Context<Self>, cx: &mut Context<Self>,
) { ) {
let delta = event.delta.pixel_delta(self.last_line_height); let delta = event.delta.pixel_delta(self.last_line_height);
self.update_scroll_offset(Some(self.scroll_handle.offset() + delta), cx);
}
fn update_scroll_offset(&mut self, offset: Option<Point<Pixels>>, cx: &mut Context<Self>) {
let mut offset = offset.unwrap_or(self.scroll_handle.offset());
let safe_y_range = let safe_y_range =
(-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.); (-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
let safe_x_range = let safe_x_range =
(-self.scroll_size.width + self.input_bounds.size.width).min(px(0.0))..px(0.); (-self.scroll_size.width + self.input_bounds.size.width).min(px(0.0))..px(0.);
let mut offset = self.scroll_handle.offset() + delta;
offset.y = offset.y.clamp(safe_y_range.start, safe_y_range.end); offset.y = offset.y.clamp(safe_y_range.start, safe_y_range.end);
offset.x = offset.x.clamp(safe_x_range.start, safe_x_range.end); offset.x = offset.x.clamp(safe_x_range.start, safe_x_range.end);
self.scroll_handle.set_offset(offset); self.scroll_handle.set_offset(offset);
cx.notify();
} }
fn show_character_palette( fn show_character_palette(
@ -1576,6 +1615,8 @@ impl EntityInputHandler for TextInput {
self.selected_range = range.start + new_text.len()..range.start + new_text.len(); self.selected_range = range.start + new_text.len()..range.start + new_text.len();
self.marked_range.take(); self.marked_range.take();
self.update_preferred_x_offset(cx); self.update_preferred_x_offset(cx);
self.update_scroll_offset(None, cx);
self.check_to_auto_grow(window, cx);
cx.emit(InputEvent::Change(self.text.clone())); cx.emit(InputEvent::Change(self.text.clone()));
cx.notify(); cx.notify();
} }