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:
parent
253c93063f
commit
c91dce3139
3 changed files with 65 additions and 24 deletions
|
|
@ -63,7 +63,8 @@ impl FormStory {
|
|||
let bio_input = cx.new(|cx| {
|
||||
let mut input = TextInput::new(window, cx)
|
||||
.multi_line()
|
||||
.rows(10)
|
||||
.rows(5)
|
||||
.max_rows(20)
|
||||
.placeholder("Enter text here...");
|
||||
input.set_text("Hello 世界,this is GPUI component.", window, cx);
|
||||
input
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use gpui::{
|
||||
actions, div, prelude::FluentBuilder as _, px, App, AppContext as _, ClickEvent, Context,
|
||||
Entity, FocusHandle, Focusable, InteractiveElement, IntoElement, KeyBinding,
|
||||
ParentElement as _, Render, SharedString, Styled, Window,
|
||||
ParentElement as _, Render, SharedString, Styled, Subscription, Window,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
|
|
@ -46,6 +46,8 @@ pub struct InputStory {
|
|||
otp_input_small: Entity<OtpInput>,
|
||||
otp_input_large: Entity<OtpInput>,
|
||||
opt_input_sized: Entity<OtpInput>,
|
||||
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
impl super::Story for InputStory {
|
||||
|
|
@ -77,12 +79,8 @@ impl InputStory {
|
|||
);
|
||||
input
|
||||
});
|
||||
cx.subscribe_in(&input1, window, Self::on_input_event)
|
||||
.detach();
|
||||
|
||||
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 mut input = TextInput::new(window, cx)
|
||||
|
|
@ -115,8 +113,6 @@ impl InputStory {
|
|||
);
|
||||
input
|
||||
});
|
||||
cx.subscribe_in(&textarea, window, Self::on_input_event)
|
||||
.detach();
|
||||
|
||||
let number_input1_value = 1;
|
||||
let number_input1 = cx.new(|cx| {
|
||||
|
|
@ -124,8 +120,6 @@ impl InputStory {
|
|||
input.set_value(number_input1_value.to_string(), window, cx);
|
||||
input
|
||||
});
|
||||
cx.subscribe_in(&number_input1, window, Self::on_number_input1_event)
|
||||
.detach();
|
||||
|
||||
let number_input2 = cx.new(|cx| {
|
||||
NumberInput::new(window, cx)
|
||||
|
|
@ -134,9 +128,6 @@ impl InputStory {
|
|||
.small()
|
||||
});
|
||||
|
||||
cx.subscribe_in(&number_input2, window, Self::on_number_input2_event)
|
||||
.detach();
|
||||
|
||||
let mask_input = cx.new(|cx| {
|
||||
let mut input = TextInput::new(window, cx)
|
||||
.masked(true)
|
||||
|
|
@ -168,14 +159,21 @@ impl InputStory {
|
|||
});
|
||||
|
||||
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) => {
|
||||
this.otp_value = Some(text.clone());
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.detach();
|
||||
|
||||
let _subscriptions = vec![
|
||||
cx.subscribe_in(&input1, window, Self::on_input_event),
|
||||
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),
|
||||
cx.subscribe(&otp_input, |this, _, ev: &InputEvent, cx| match ev {
|
||||
InputEvent::Change(text) => {
|
||||
this.otp_value = Some(text.clone());
|
||||
cx.notify();
|
||||
}
|
||||
_ => {}
|
||||
}),
|
||||
];
|
||||
|
||||
Self {
|
||||
input1,
|
||||
|
|
@ -230,6 +228,7 @@ impl InputStory {
|
|||
.default_value("654321")
|
||||
.with_size(px(55.))
|
||||
}),
|
||||
_subscriptions,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,6 +226,8 @@ pub struct TextInput {
|
|||
pub(super) cleanable: bool,
|
||||
pub(super) size: Size,
|
||||
pub(super) rows: usize,
|
||||
pub(super) min_rows: usize,
|
||||
pub(super) max_rows: Option<usize>,
|
||||
/// For special case, e.g.: NumberInput + - button
|
||||
pub(super) no_gap: bool,
|
||||
pub(super) height: Option<gpui::DefiniteLength>,
|
||||
|
|
@ -293,6 +295,8 @@ impl TextInput {
|
|||
pattern: None,
|
||||
validate: None,
|
||||
rows: 2,
|
||||
min_rows: 2,
|
||||
max_rows: None,
|
||||
last_layout: None,
|
||||
last_bounds: None,
|
||||
last_selected_range: None,
|
||||
|
|
@ -456,6 +460,19 @@ impl TextInput {
|
|||
/// default: 2
|
||||
pub fn rows(mut self, rows: usize) -> Self {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -1003,6 +1020,23 @@ impl TextInput {
|
|||
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>) {
|
||||
self.replace_text("", window, cx);
|
||||
}
|
||||
|
|
@ -1037,19 +1071,24 @@ impl TextInput {
|
|||
&mut self,
|
||||
event: &ScrollWheelEvent,
|
||||
_window: &mut Window,
|
||||
_cx: &mut Context<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
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 =
|
||||
(-self.scroll_size.height + self.input_bounds.size.height).min(px(0.0))..px(0.);
|
||||
let safe_x_range =
|
||||
(-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.x = offset.x.clamp(safe_x_range.start, safe_x_range.end);
|
||||
|
||||
self.scroll_handle.set_offset(offset);
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
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.marked_range.take();
|
||||
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.notify();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue