input: Add to support horizontal scrolling to multi-line text. (#1131)
- Add `soft_wrap` option to build InputState, default true. <img width="1156" height="1166" alt="image" src="https://github.com/user-attachments/assets/2eab2db0-b3fc-48ce-83cb-1d6715313247" /> --------- Co-authored-by: Jason Lee <huacnlee@gmail.com>
This commit is contained in:
parent
34d2a75894
commit
ca548bd856
5 changed files with 52 additions and 18 deletions
|
|
@ -23,6 +23,7 @@ pub fn init(cx: &mut App) {
|
|||
pub struct TextareaStory {
|
||||
textarea: Entity<InputState>,
|
||||
textarea_auto_grow: Entity<InputState>,
|
||||
textarea_no_wrap: Entity<InputState>,
|
||||
}
|
||||
|
||||
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.))),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<regex::Regex>,
|
||||
pub(super) validate: Option<Box<dyn Fn(&str, &mut Context<Self>) -> 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>) {
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue