chore: fix input auto-grow height
This commit is contained in:
@@ -78,6 +78,7 @@ impl Chat {
|
|||||||
.multi_line()
|
.multi_line()
|
||||||
.prevent_new_line_on_enter()
|
.prevent_new_line_on_enter()
|
||||||
.rows(1)
|
.rows(1)
|
||||||
|
.auto_grow()
|
||||||
.clean_on_escape()
|
.clean_on_escape()
|
||||||
.max_rows(20)
|
.max_rows(20)
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ use theme::ActiveTheme;
|
|||||||
use super::InputState;
|
use super::InputState;
|
||||||
use crate::Root;
|
use crate::Root;
|
||||||
|
|
||||||
const RIGHT_MARGIN: Pixels = px(5.);
|
|
||||||
const BOTTOM_MARGIN: Pixels = px(20.);
|
|
||||||
const CURSOR_THICKNESS: Pixels = px(2.);
|
const CURSOR_THICKNESS: Pixels = px(2.);
|
||||||
|
const RIGHT_MARGIN: Pixels = px(5.);
|
||||||
|
const BOTTOM_MARGIN_ROWS: usize = 1;
|
||||||
|
|
||||||
pub(super) struct TextElement {
|
pub(super) struct TextElement {
|
||||||
input: Entity<InputState>,
|
input: Entity<InputState>,
|
||||||
@@ -60,6 +60,13 @@ impl TextElement {
|
|||||||
let mut scroll_offset = input.scroll_handle.offset();
|
let mut scroll_offset = input.scroll_handle.offset();
|
||||||
let mut cursor = None;
|
let mut cursor = None;
|
||||||
|
|
||||||
|
// If the input has a fixed height (Otherwise is auto-grow), we need to add a bottom margin to the input.
|
||||||
|
let bottom_margin = if input.auto_grow {
|
||||||
|
px(0.)
|
||||||
|
} else {
|
||||||
|
BOTTOM_MARGIN_ROWS * line_height + line_height
|
||||||
|
};
|
||||||
|
|
||||||
// The cursor corresponds to the current cursor position in the text no only the line.
|
// The cursor corresponds to the current cursor position in the text no only the line.
|
||||||
let mut cursor_pos = None;
|
let mut cursor_pos = None;
|
||||||
let mut cursor_start = None;
|
let mut cursor_start = None;
|
||||||
@@ -115,16 +122,17 @@ impl TextElement {
|
|||||||
} else {
|
} else {
|
||||||
scroll_offset.x
|
scroll_offset.x
|
||||||
};
|
};
|
||||||
scroll_offset.y =
|
scroll_offset.y = if scroll_offset.y + cursor_pos.y + line_height
|
||||||
if scroll_offset.y + cursor_pos.y > (bounds.size.height - BOTTOM_MARGIN) {
|
> bounds.size.height - bottom_margin
|
||||||
// cursor is out of bottom
|
{
|
||||||
bounds.size.height - BOTTOM_MARGIN - cursor_pos.y
|
// cursor is out of bottom
|
||||||
} else if scroll_offset.y + cursor_pos.y < px(0.) {
|
bounds.size.height - bottom_margin - cursor_pos.y
|
||||||
// cursor is out of top
|
} else if scroll_offset.y + cursor_pos.y < px(0.) {
|
||||||
scroll_offset.y - cursor_pos.y
|
// cursor is out of top
|
||||||
} else {
|
scroll_offset.y - cursor_pos.y
|
||||||
scroll_offset.y
|
} else {
|
||||||
};
|
scroll_offset.y
|
||||||
|
};
|
||||||
|
|
||||||
if input.selection_reversed {
|
if input.selection_reversed {
|
||||||
if scroll_offset.x + cursor_start.x < px(0.) {
|
if scroll_offset.x + cursor_start.x < px(0.) {
|
||||||
@@ -351,20 +359,36 @@ impl Element for TextElement {
|
|||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> (LayoutId, Self::RequestLayoutState) {
|
) -> (LayoutId, Self::RequestLayoutState) {
|
||||||
let input = self.input.read(cx);
|
let input = self.input.read(cx);
|
||||||
|
let line_height = window.line_height();
|
||||||
|
|
||||||
let mut style = Style::default();
|
let mut style = Style::default();
|
||||||
style.size.width = relative(1.).into();
|
style.size.width = relative(1.).into();
|
||||||
|
|
||||||
if self.input.read(cx).is_multi_line() {
|
if self.input.read(cx).is_multi_line() {
|
||||||
style.flex_grow = 1.0;
|
style.flex_grow = 1.0;
|
||||||
if let Some(h) = input.height {
|
if let Some(h) = input.height {
|
||||||
style.size.height = h.into();
|
style.size.height = h.into();
|
||||||
style.min_size.height = window.line_height().into();
|
style.min_size.height = line_height.into();
|
||||||
} else {
|
} else {
|
||||||
|
// Check to auto grow
|
||||||
|
let rows = if input.auto_grow {
|
||||||
|
let rows = (input.scroll_size.height / line_height) as usize;
|
||||||
|
let max_rows = input
|
||||||
|
.max_rows
|
||||||
|
.unwrap_or(usize::MAX)
|
||||||
|
.min(rows)
|
||||||
|
.max(input.min_rows);
|
||||||
|
rows.clamp(input.min_rows, max_rows)
|
||||||
|
} else {
|
||||||
|
input.rows
|
||||||
|
};
|
||||||
|
|
||||||
style.size.height = relative(1.).into();
|
style.size.height = relative(1.).into();
|
||||||
style.min_size.height = (input.rows.max(1) as f32 * window.line_height()).into();
|
style.min_size.height = (rows.max(1) as f32 * line_height).into();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For single-line inputs, the minimum height should be the line height
|
// For single-line inputs, the minimum height should be the line height
|
||||||
style.size.height = window.line_height().into();
|
style.size.height = line_height.into();
|
||||||
};
|
};
|
||||||
|
|
||||||
(window.request_layout(style, [], cx), ())
|
(window.request_layout(style, [], cx), ())
|
||||||
@@ -569,25 +593,22 @@ impl Element for TextElement {
|
|||||||
.map(|l| l.width())
|
.map(|l| l.width())
|
||||||
.max()
|
.max()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let height = prepaint
|
|
||||||
.lines
|
|
||||||
.iter()
|
|
||||||
.map(|l| l.size(line_height).height.0)
|
|
||||||
.sum::<f32>();
|
|
||||||
|
|
||||||
let scroll_size = size(width, px(height));
|
let height = offset_y;
|
||||||
|
let scroll_size = size(width, height);
|
||||||
|
|
||||||
self.input.update(cx, |input, _cx| {
|
self.input.update(cx, |input, cx| {
|
||||||
input.last_layout = Some(prepaint.lines.clone());
|
input.last_layout = Some(prepaint.lines.clone());
|
||||||
input.last_bounds = Some(bounds);
|
input.last_bounds = Some(bounds);
|
||||||
input.last_cursor_offset = Some(input.cursor_offset());
|
input.last_cursor_offset = Some(input.cursor_offset());
|
||||||
input.last_line_height = line_height;
|
input.last_line_height = line_height;
|
||||||
input.input_bounds = input_bounds;
|
input.input_bounds = input_bounds;
|
||||||
input.last_selected_range = Some(selected_range);
|
input.last_selected_range = Some(selected_range);
|
||||||
|
input.scroll_size = scroll_size;
|
||||||
input
|
input
|
||||||
.scroll_handle
|
.scroll_handle
|
||||||
.set_offset(prepaint.cursor_scroll_offset);
|
.set_offset(prepaint.cursor_scroll_offset);
|
||||||
input.scroll_size = scroll_size;
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
self.paint_mouse_listeners(window, cx);
|
self.paint_mouse_listeners(window, cx);
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ pub struct InputState {
|
|||||||
pub(super) rows: usize,
|
pub(super) rows: usize,
|
||||||
pub(super) min_rows: usize,
|
pub(super) min_rows: usize,
|
||||||
pub(super) max_rows: Option<usize>,
|
pub(super) max_rows: Option<usize>,
|
||||||
|
pub(super) auto_grow: bool,
|
||||||
pub(super) pattern: Option<regex::Regex>,
|
pub(super) pattern: Option<regex::Regex>,
|
||||||
pub(super) validate: Validate,
|
pub(super) validate: Validate,
|
||||||
pub(crate) scroll_handle: ScrollHandle,
|
pub(crate) scroll_handle: ScrollHandle,
|
||||||
@@ -284,6 +285,7 @@ impl InputState {
|
|||||||
rows: 3,
|
rows: 3,
|
||||||
min_rows: 3,
|
min_rows: 3,
|
||||||
max_rows: None,
|
max_rows: None,
|
||||||
|
auto_grow: false,
|
||||||
height: None,
|
height: None,
|
||||||
last_layout: None,
|
last_layout: None,
|
||||||
last_bounds: None,
|
last_bounds: None,
|
||||||
@@ -489,6 +491,12 @@ impl InputState {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the auto-grow mode for the multi-line Textarea.
|
||||||
|
pub fn auto_grow(mut self) -> Self {
|
||||||
|
self.auto_grow = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Set the text of the input field.
|
/// Set the text of the input field.
|
||||||
///
|
///
|
||||||
/// And the selection_range will be reset to 0..0.
|
/// And the selection_range will be reset to 0..0.
|
||||||
@@ -1010,25 +1018,6 @@ impl InputState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
pub(super) fn clean(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
self.replace_text("", window, cx);
|
self.replace_text("", window, cx);
|
||||||
}
|
}
|
||||||
@@ -1591,6 +1580,10 @@ impl EntityInputHandler for InputState {
|
|||||||
self.marked_range = None;
|
self.marked_range = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Replace text in range.
|
||||||
|
///
|
||||||
|
/// - If the new text is invalid, it will not be replaced.
|
||||||
|
/// - If `range_utf16` is not provided, the current selected range will be used.
|
||||||
fn replace_text_in_range(
|
fn replace_text_in_range(
|
||||||
&mut self,
|
&mut self,
|
||||||
range_utf16: Option<Range<usize>>,
|
range_utf16: Option<Range<usize>>,
|
||||||
@@ -1628,7 +1621,6 @@ impl EntityInputHandler for InputState {
|
|||||||
|
|
||||||
self.update_preferred_x_offset(cx);
|
self.update_preferred_x_offset(cx);
|
||||||
self.update_scroll_offset(None, cx);
|
self.update_scroll_offset(None, cx);
|
||||||
self.check_to_auto_grow(window, cx);
|
|
||||||
|
|
||||||
cx.emit(InputEvent::Change(self.unmask_value()));
|
cx.emit(InputEvent::Change(self.unmask_value()));
|
||||||
cx.notify();
|
cx.notify();
|
||||||
|
|||||||
Reference in New Issue
Block a user