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 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
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue