input: Add h, h_full to Input for multi-line mode. (#642)

Close #641
<img width="1712" alt="image"
src="https://github.com/user-attachments/assets/9cbe090c-a5d7-4929-98db-5f71349fe559"
/>
This commit is contained in:
Jason Lee 2025-02-25 16:17:39 +08:00 committed by GitHub
parent c74791aac8
commit 00e5385878
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 82 additions and 29 deletions

View file

@ -1,5 +1,5 @@
use gpui::*;
use gpui_component::{input::TextInput, text::TextView, ActiveTheme as _};
use gpui_component::{h_flex, input::TextInput, text::TextView, ActiveTheme as _};
use story::Assets;
pub struct Example {
@ -14,8 +14,9 @@ impl Example {
let text_input = cx.new(|cx| {
TextInput::new(window, cx)
.multi_line()
.rows(50)
.placeholder("Input your HTML here...")
.appearance(false)
.h_full()
.placeholder("Enter your HTML here...")
});
let _subscribe = cx.subscribe(
@ -42,9 +43,7 @@ impl Example {
impl Render for Example {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
div()
.flex()
.flex_row()
h_flex()
.h_full()
.child(
div()
@ -53,7 +52,6 @@ impl Render for Example {
.w_1_2()
.border_r_1()
.border_color(cx.theme().border)
.flex_1()
.child(self.text_input.clone()),
)
.child(
@ -62,7 +60,6 @@ impl Render for Example {
.h_full()
.w_1_2()
.p_5()
.flex_1()
.overflow_y_scroll()
.child(TextView::html("preview", self.text_input.read(cx).text())),
)

View file

@ -1,14 +1,35 @@
use gpui::*;
use gpui_component::{text::TextView, ActiveTheme as _};
use gpui_component::{input::TextInput, text::TextView, ActiveTheme as _};
use story::Assets;
pub struct Example {}
pub struct Example {
text_input: Entity<TextInput>,
}
const EXAMPLE: &str = include_str!("./markdown.md");
impl Example {
pub fn new(_: &mut Window, _: &mut Context<Self>) -> Self {
Self {}
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let text_input = cx.new(|cx| {
TextInput::new(window, cx)
.multi_line()
.appearance(false)
.h_full()
.placeholder("Enter your Markdown here...")
});
let _subscribe = cx.subscribe(
&text_input,
|_, _, _: &gpui_component::input::InputEvent, cx| {
cx.notify();
},
);
_ = text_input.update(cx, |input, cx| {
input.set_text(EXAMPLE, window, cx);
});
Self { text_input }
}
fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
@ -30,9 +51,7 @@ impl Render for Example {
.border_r_1()
.border_color(cx.theme().border)
.flex_1()
.p_5()
.overflow_y_scroll()
.child(EXAMPLE),
.child(self.text_input.clone()),
)
.child(
div()
@ -42,7 +61,10 @@ impl Render for Example {
.p_5()
.flex_1()
.overflow_y_scroll()
.child(TextView::markdown("preview", EXAMPLE)),
.child(TextView::markdown(
"preview",
self.text_input.read(cx).text(),
)),
)
}
}

View file

@ -69,7 +69,7 @@ impl InputStory {
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let input1 = cx.new(|cx| {
let mut input = TextInput::new(window, cx).cleanable();
let mut input = TextInput::new(window, cx).cleanable().h_full();
input.set_text(
"Hello 世界this is GPUI component, this is a long text.",
window,
@ -387,7 +387,9 @@ impl Render for InputStory {
.child(self.number_input2.clone()),
),
)
.child(section("Textarea", cx).child(self.textarea.clone()))
.child(
section("Textarea", cx).child(div().flex_1().child(self.textarea.clone())),
)
.child(
section("Input State", cx)
.child(self.disabled_input.clone())

View file

@ -182,10 +182,14 @@ impl Render for StoryRoot {
let modal_layer = Root::render_modal_layer(window, cx);
let notification_layer = Root::render_notification_layer(window, cx);
v_flex()
div()
.size_full()
.child(self.title_bar.clone())
.child(self.view.clone())
.child(
v_flex()
.size_full()
.child(self.title_bar.clone())
.child(div().flex_1().overflow_hidden().child(self.view.clone())),
)
.children(drawer_layer)
.children(modal_layer)
.child(div().absolute().top_8().children(notification_layer))

View file

@ -346,11 +346,19 @@ impl Element for TextElement {
let mut style = Style::default();
style.size.width = relative(1.).into();
if self.input.read(cx).is_multi_line() {
style.size.height = relative(1.).into();
style.min_size.height = (input.rows.max(1) as f32 * window.line_height()).into();
style.flex_grow = 1.0;
if let Some(h) = input.height {
style.size.height = h.into();
style.min_size.height = window.line_height().into();
} else {
style.size.height = relative(1.).into();
style.min_size.height = (input.rows.max(1) as f32 * window.line_height()).into();
}
} else {
// For single-line inputs, the minimum height should be the line height
style.size.height = window.line_height().into();
};
(window.request_layout(style, [], cx), ())
}

View file

@ -11,11 +11,12 @@ use unicode_segmentation::*;
use gpui::prelude::FluentBuilder as _;
use gpui::{
actions, div, point, px, AnyElement, App, AppContext, Bounds, ClickEvent, ClipboardItem,
Context, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable,
InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render, ScrollHandle,
ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, Window, WrappedLine,
actions, div, point, px, relative, AnyElement, App, AppContext, Bounds, ClickEvent,
ClipboardItem, Context, DefiniteLength, Entity, EntityInputHandler, EventEmitter, FocusHandle,
Focusable, InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton,
MouseDownEvent, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Rems, Render,
ScrollHandle, ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection,
Window, WrappedLine,
};
// TODO:
@ -223,6 +224,7 @@ pub struct TextInput {
pub(super) cleanable: bool,
pub(super) size: Size,
pub(super) rows: usize,
pub(super) height: Option<gpui::DefiniteLength>,
pattern: Option<regex::Regex>,
validate: Option<Box<dyn Fn(&str) -> bool + 'static>>,
pub(crate) scroll_handle: ScrollHandle,
@ -281,6 +283,7 @@ impl TextInput {
prefix: None,
suffix: None,
size: Size::Medium,
height: None,
pattern: None,
validate: None,
rows: 2,
@ -551,6 +554,18 @@ impl TextInput {
self
}
/// Set full height of the input (Multi-line only).
pub fn h_full(mut self) -> Self {
self.height = Some(relative(1.));
self
}
/// Set height of the input (Multi-line only).
pub fn h(mut self, height: impl Into<DefiniteLength>) -> Self {
self.height = Some(height.into());
self
}
/// Set the placeholder text of the input field.
pub fn placeholder(mut self, placeholder: impl Into<SharedString>) -> Self {
self.placeholder = placeholder.into();
@ -1638,7 +1653,10 @@ impl Render for TextInput {
.input_py(self.size)
.input_h(self.size)
.cursor_text()
.when(self.multi_line, |this| this.h_auto())
.when(self.multi_line, |this| {
this.h_auto()
.when_some(self.height, |this, height| this.h(height))
})
.when(self.appearance, |this| {
this.bg(if self.disabled {
cx.theme().muted
@ -1659,6 +1677,8 @@ impl Render for TextInput {
.child(
div()
.id("TextElement")
.w_full()
.when(self.is_multi_line(), |this| this.h_full())
.flex_grow()
.overflow_x_hidden()
.child(TextElement::new(cx.entity().clone())),