diff --git a/crates/story/src/textarea_story.rs b/crates/story/src/textarea_story.rs index 5889920a..54126cd3 100644 --- a/crates/story/src/textarea_story.rs +++ b/crates/story/src/textarea_story.rs @@ -23,6 +23,7 @@ pub fn init(cx: &mut App) { pub struct TextareaStory { textarea: Entity, textarea_auto_grow: Entity, + textarea_no_wrap: Entity, } impl super::Story for TextareaStory { @@ -84,9 +85,18 @@ impl TextareaStory { .default_value("Hello 世界,this is GPUI component.") }); + let textarea_no_wrap = cx.new(|cx| { + InputState::new(window, cx) + .multi_line() + .rows(6) + .soft_wrap(false) + .default_value("This is a very long line of text to test if the horizontal scrolling function is working properly, and it should not wrap automatically but display a horizontal scrollbar.\nThe second line is also very long text, used to test the horizontal scrolling effect under multiple lines, and you can input more content to test.\nThe third line: Here you can input other long text content that requires horizontal scrolling.\n") + }); + Self { textarea, textarea_auto_grow, + textarea_no_wrap, } } @@ -141,8 +151,6 @@ impl Render for TextareaStory { .id("textarea-story") .on_action(cx.listener(Self::tab)) .on_action(cx.listener(Self::tab_prev)) - .size_full() - .justify_start() .gap_3() .child( section("Textarea").child( @@ -179,12 +187,11 @@ impl Render for TextareaStory { ), ), ) + .child(section("Textarea Auto Grow").child(TextInput::new(&self.textarea_auto_grow))) .child( - section("Textarea Auto Grow").child( - v_flex() - .w_full() - .child(TextInput::new(&self.textarea_auto_grow)), - ), + section("No Wrap") + .max_w_md() + .child(TextInput::new(&self.textarea_no_wrap).h(px(200.))), ) } } diff --git a/crates/ui/src/input/element.rs b/crates/ui/src/input/element.rs index e11d7b2f..4e4e6bf7 100644 --- a/crates/ui/src/input/element.rs +++ b/crates/ui/src/input/element.rs @@ -660,7 +660,7 @@ impl Element for TextElement { vec![run] }; - let wrap_width = if multi_line { + let wrap_width = if multi_line && state.soft_wrap { Some(bounds.size.width - line_number_width) } else { None diff --git a/crates/ui/src/input/state.rs b/crates/ui/src/input/state.rs index 22100f4b..86702de9 100644 --- a/crates/ui/src/input/state.rs +++ b/crates/ui/src/input/state.rs @@ -266,6 +266,7 @@ pub struct InputState { pub(super) disabled: bool, pub(super) masked: bool, pub(super) clean_on_escape: bool, + pub(super) soft_wrap: bool, pub(super) pattern: Option, pub(super) validate: Option) -> bool + 'static>>, pub(crate) scroll_handle: ScrollHandle, @@ -335,6 +336,7 @@ impl InputState { disabled: false, masked: false, clean_on_escape: false, + soft_wrap: true, loading: false, pattern: None, validate: None, @@ -751,6 +753,18 @@ impl InputState { self } + /// Set the soft wrap mode for multi-line input, default is true. + pub fn soft_wrap(mut self, wrap: bool) -> Self { + self.soft_wrap = wrap; + self + } + + /// Update the soft wrap mode for multi-line input, default is true. + pub fn set_soft_wrap(&mut self, wrap: bool, _: &mut Window, cx: &mut Context) { + self.soft_wrap = wrap; + cx.notify(); + } + /// Set the regular expression pattern of the input field. pub fn pattern(mut self, pattern: regex::Regex) -> Self { self.pattern = Some(pattern); @@ -2100,7 +2114,14 @@ impl InputState { // Update text_wrapper wrap_width if changed. if let Some(last_layout) = self.last_layout.as_ref() { if wrap_width_changed { - self.text_wrapper.set_wrap_width(last_layout.wrap_width, cx); + let wrap_width = if !self.soft_wrap { + // None to disable wrapping (will use Pixels::MAX) + None + } else { + last_layout.wrap_width + }; + + self.text_wrapper.set_wrap_width(wrap_width, cx); self.mode.update_auto_grow(&self.text_wrapper); cx.notify(); } diff --git a/crates/ui/src/input/text_input.rs b/crates/ui/src/input/text_input.rs index 3d99b427..7c7c8e1e 100644 --- a/crates/ui/src/input/text_input.rs +++ b/crates/ui/src/input/text_input.rs @@ -301,6 +301,12 @@ impl RenderOnce for TextInput { .refine_style(&self.style) .when(state.mode.is_multi_line(), |this| { if state.last_layout.is_some() { + let scrollbar = if !state.soft_wrap { + Scrollbar::both(&state.scroll_state, &state.scroll_handle) + } else { + Scrollbar::vertical(&state.scroll_state, &state.scroll_handle) + }; + this.relative().child( div() .absolute() @@ -308,10 +314,7 @@ impl RenderOnce for TextInput { .left_0() .right(px(1.)) .bottom_0() - .child( - Scrollbar::vertical(&state.scroll_state, &state.scroll_handle) - .scroll_size(state.scroll_size), - ), + .child(scrollbar.scroll_size(state.scroll_size)), ) } else { this diff --git a/crates/ui/src/input/text_wrapper.rs b/crates/ui/src/input/text_wrapper.rs index b99294dd..d30f3f94 100644 --- a/crates/ui/src/input/text_wrapper.rs +++ b/crates/ui/src/input/text_wrapper.rs @@ -64,7 +64,7 @@ impl TextWrapper { let mut wrapped_lines = vec![]; let mut lines = vec![]; - let wrap_width = self.wrap_width.unwrap_or(Pixels::MAX); + let wrap_width = self.wrap_width; let mut line_wrapper = cx .text_system() .line_wrapper(self.font.clone(), self.font_size); @@ -73,10 +73,13 @@ impl TextWrapper { let mut line_wraps = vec![]; let mut prev_boundary_ix = 0; - // Here only have wrapped line, if there is no wrap meet, the `line_wraps` result will empty. - for boundary in line_wrapper.wrap_line(&[LineFragment::text(line)], wrap_width) { - line_wraps.push(prev_boundary_ix..boundary.ix); - prev_boundary_ix = boundary.ix; + // If wrap_width is Pixels::MAX, skip wrapping to disable word wrap + if let Some(wrap_width) = wrap_width { + // Here only have wrapped line, if there is no wrap meet, the `line_wraps` result will empty. + for boundary in line_wrapper.wrap_line(&[LineFragment::text(line)], wrap_width) { + line_wraps.push(prev_boundary_ix..boundary.ix); + prev_boundary_ix = boundary.ix; + } } lines.push(LineWrap {