TextEdit: Add visual clipping for singleline inputs when text is large. (#531)

* TextEdit: Add visual clipping for singleline inputs when text is large.

* TextEdit: Add reviewer suggestions.
This commit is contained in:
lucaspoffo 2021-07-06 13:59:52 -03:00 committed by GitHub
parent faf104220b
commit a6c3daff6f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -14,6 +14,10 @@ pub(crate) struct State {
// If IME candidate window is shown on this text edit.
#[cfg_attr(feature = "persistence", serde(skip))]
has_ime: bool,
// Visual offset when editing singleline text bigger than the width.
#[cfg_attr(feature = "persistence", serde(skip))]
singleline_offset: f32,
}
#[derive(Clone, Copy, Debug, Default)]
@ -455,12 +459,13 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
.unwrap_or_else(|| ui.style().body_text_style);
let line_spacing = ui.fonts().row_height(text_style);
let available_width = ui.available_width();
let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width);
let make_galley = |ui: &Ui, text: &str| {
let text = mask_if_password(text);
if multiline {
ui.fonts()
.layout_multiline(text_style, text, available_width)
.layout_multiline(text_style, text, desired_width.min(available_width))
} else {
ui.fonts().layout_single_line(text_style, text)
}
@ -474,10 +479,9 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
let mut galley = make_galley(ui, text.as_ref());
let desired_width = desired_width.unwrap_or_else(|| ui.spacing().text_edit_width);
let desired_height = (desired_height_rows.at_least(1) as f32) * line_spacing;
let desired_size = vec2(
galley.size.x.max(desired_width.min(available_width)),
desired_width.min(available_width),
galley.size.y.max(desired_height),
);
let (auto_id, rect) = ui.allocate_space(desired_size);
@ -497,20 +501,22 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
Sense::hover()
};
let mut response = ui.interact(rect, id, sense);
let painter = ui.painter_at(Rect::from_min_size(response.rect.min, desired_size));
if enabled {
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
// TODO: triple-click to select whole paragraph
// TODO: drag selected text to either move or clone (ctrl on windows, alt on mac)
let cursor_at_pointer = galley.cursor_from_pos(pointer_pos - response.rect.min);
let singleline_offset = vec2(state.singleline_offset, 0.0);
let cursor_at_pointer =
galley.cursor_from_pos(pointer_pos - response.rect.min + singleline_offset);
if ui.visuals().text_cursor_preview
&& response.hovered()
&& ui.input().pointer.is_moving()
{
// preview:
paint_cursor_end(ui, response.rect.min, &galley, &cursor_at_pointer);
paint_cursor_end(ui, &painter, response.rect.min, &galley, &cursor_at_pointer);
}
if response.double_clicked() {
@ -726,10 +732,38 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
);
}
let mut text_draw_pos = response.rect.min;
// Visual clipping for singleline text editor with text larger than width
if !multiline {
let cursor_pos = match (state.cursorp, ui.memory().has_focus(id)) {
(Some(cursorp), true) => galley.pos_from_cursor(&cursorp.primary).min.x,
_ => 0.0,
};
let mut offset_x = state.singleline_offset;
let visible_range = offset_x..=offset_x + desired_size.x;
if !visible_range.contains(&cursor_pos) {
if cursor_pos < *visible_range.start() {
offset_x = cursor_pos;
} else {
offset_x = cursor_pos - desired_size.x;
}
}
offset_x = offset_x
.at_most(galley.size.x - desired_size.x)
.at_least(0.0);
state.singleline_offset = offset_x;
text_draw_pos -= vec2(offset_x, 0.0);
}
if ui.memory().has_focus(id) {
if let Some(cursorp) = state.cursorp {
paint_cursor_selection(ui, response.rect.min, &galley, &cursorp);
paint_cursor_end(ui, response.rect.min, &galley, &cursorp.primary);
paint_cursor_selection(ui, &painter, text_draw_pos, &galley, &cursorp);
paint_cursor_end(ui, &painter, text_draw_pos, &galley, &cursorp.primary);
if enabled {
ui.ctx().output().text_cursor_pos = Some(
@ -746,18 +780,17 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
.or(ui.visuals().override_text_color)
// .unwrap_or_else(|| ui.style().interact(&response).text_color()); // too bright
.unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
ui.painter().galley(response.rect.min, galley, text_color);
painter.galley(text_draw_pos, galley, text_color);
if text.as_ref().is_empty() && !hint_text.is_empty() {
let galley = if multiline {
ui.fonts()
.layout_multiline(text_style, hint_text, available_width)
.layout_multiline(text_style, hint_text, desired_size.x)
} else {
ui.fonts().layout_single_line(text_style, hint_text)
};
let hint_text_color = ui.visuals().weak_text_color();
ui.painter()
.galley(response.rect.min, galley, hint_text_color);
painter.galley(response.rect.min, galley, hint_text_color);
}
ui.memory().id_data.insert(id, state);
@ -803,7 +836,13 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
// ----------------------------------------------------------------------------
fn paint_cursor_selection(ui: &mut Ui, pos: Pos2, galley: &Galley, cursorp: &CursorPair) {
fn paint_cursor_selection(
ui: &mut Ui,
painter: &Painter,
pos: Pos2,
galley: &Galley,
cursorp: &CursorPair,
) {
let color = ui.visuals().selection.bg_fill;
if cursorp.is_empty() {
return;
@ -830,11 +869,11 @@ fn paint_cursor_selection(ui: &mut Ui, pos: Pos2, galley: &Galley, cursorp: &Cur
row.max_x() + newline_size
};
let rect = Rect::from_min_max(pos + vec2(left, row.y_min), pos + vec2(right, row.y_max));
ui.painter().rect_filled(rect, 0.0, color);
painter.rect_filled(rect, 0.0, color);
}
}
fn paint_cursor_end(ui: &mut Ui, pos: Pos2, galley: &Galley, cursor: &Cursor) {
fn paint_cursor_end(ui: &mut Ui, painter: &Painter, pos: Pos2, galley: &Galley, cursor: &Cursor) {
let stroke = ui.visuals().selection.stroke;
let cursor_pos = galley.pos_from_cursor(cursor).translate(pos.to_vec2());
@ -843,7 +882,7 @@ fn paint_cursor_end(ui: &mut Ui, pos: Pos2, galley: &Galley, cursor: &Cursor) {
let top = cursor_pos.center_top();
let bottom = cursor_pos.center_bottom();
ui.painter().line_segment(
painter.line_segment(
[top, bottom],
(ui.visuals().text_cursor_width, stroke.color),
);
@ -852,11 +891,11 @@ fn paint_cursor_end(ui: &mut Ui, pos: Pos2, galley: &Galley, cursor: &Cursor) {
// Roof/floor:
let extrusion = 3.0;
let width = 1.0;
ui.painter().line_segment(
painter.line_segment(
[top - vec2(extrusion, 0.0), top + vec2(extrusion, 0.0)],
(width, stroke.color),
);
ui.painter().line_segment(
painter.line_segment(
[bottom - vec2(extrusion, 0.0), bottom + vec2(extrusion, 0.0)],
(width, stroke.color),
);