diff --git a/crates/story/examples/html.rs b/crates/story/examples/html.rs index c4296143..2aa3cb02 100644 --- a/crates/story/examples/html.rs +++ b/crates/story/examples/html.rs @@ -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) -> 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())), ) diff --git a/crates/story/examples/markdown.rs b/crates/story/examples/markdown.rs index 103a82d7..84f66776 100644 --- a/crates/story/examples/markdown.rs +++ b/crates/story/examples/markdown.rs @@ -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, +} const EXAMPLE: &str = include_str!("./markdown.md"); impl Example { - pub fn new(_: &mut Window, _: &mut Context) -> Self { - Self {} + pub fn new(window: &mut Window, cx: &mut Context) -> 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 { @@ -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(), + )), ) } } diff --git a/crates/story/src/input_story.rs b/crates/story/src/input_story.rs index f13a5f7a..7f7cbc33 100644 --- a/crates/story/src/input_story.rs +++ b/crates/story/src/input_story.rs @@ -69,7 +69,7 @@ impl InputStory { fn new(window: &mut Window, cx: &mut Context) -> 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()) diff --git a/crates/story/src/lib.rs b/crates/story/src/lib.rs index d9383a0c..dbca91ec 100644 --- a/crates/story/src/lib.rs +++ b/crates/story/src/lib.rs @@ -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)) diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index 7b6a44fd..8d6d929d 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -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), ()) } diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index 18b2b8a7..455806c3 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -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, pattern: Option, validate: Option 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) -> Self { + self.height = Some(height.into()); + self + } + /// Set the placeholder text of the input field. pub fn placeholder(mut self, placeholder: impl Into) -> 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())),