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:
parent
faf104220b
commit
a6c3daff6f
1 changed files with 57 additions and 18 deletions
|
@ -14,6 +14,10 @@ pub(crate) struct State {
|
||||||
// If IME candidate window is shown on this text edit.
|
// If IME candidate window is shown on this text edit.
|
||||||
#[cfg_attr(feature = "persistence", serde(skip))]
|
#[cfg_attr(feature = "persistence", serde(skip))]
|
||||||
has_ime: bool,
|
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)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
@ -455,12 +459,13 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
|
||||||
.unwrap_or_else(|| ui.style().body_text_style);
|
.unwrap_or_else(|| ui.style().body_text_style);
|
||||||
let line_spacing = ui.fonts().row_height(text_style);
|
let line_spacing = ui.fonts().row_height(text_style);
|
||||||
let available_width = ui.available_width();
|
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 make_galley = |ui: &Ui, text: &str| {
|
||||||
let text = mask_if_password(text);
|
let text = mask_if_password(text);
|
||||||
if multiline {
|
if multiline {
|
||||||
ui.fonts()
|
ui.fonts()
|
||||||
.layout_multiline(text_style, text, available_width)
|
.layout_multiline(text_style, text, desired_width.min(available_width))
|
||||||
} else {
|
} else {
|
||||||
ui.fonts().layout_single_line(text_style, text)
|
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 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_height = (desired_height_rows.at_least(1) as f32) * line_spacing;
|
||||||
let desired_size = vec2(
|
let desired_size = vec2(
|
||||||
galley.size.x.max(desired_width.min(available_width)),
|
desired_width.min(available_width),
|
||||||
galley.size.y.max(desired_height),
|
galley.size.y.max(desired_height),
|
||||||
);
|
);
|
||||||
let (auto_id, rect) = ui.allocate_space(desired_size);
|
let (auto_id, rect) = ui.allocate_space(desired_size);
|
||||||
|
@ -497,20 +501,22 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
|
||||||
Sense::hover()
|
Sense::hover()
|
||||||
};
|
};
|
||||||
let mut response = ui.interact(rect, id, sense);
|
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 enabled {
|
||||||
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
|
if let Some(pointer_pos) = ui.input().pointer.interact_pos() {
|
||||||
// TODO: triple-click to select whole paragraph
|
// TODO: triple-click to select whole paragraph
|
||||||
// TODO: drag selected text to either move or clone (ctrl on windows, alt on mac)
|
// TODO: drag selected text to either move or clone (ctrl on windows, alt on mac)
|
||||||
|
let singleline_offset = vec2(state.singleline_offset, 0.0);
|
||||||
let cursor_at_pointer = galley.cursor_from_pos(pointer_pos - response.rect.min);
|
let cursor_at_pointer =
|
||||||
|
galley.cursor_from_pos(pointer_pos - response.rect.min + singleline_offset);
|
||||||
|
|
||||||
if ui.visuals().text_cursor_preview
|
if ui.visuals().text_cursor_preview
|
||||||
&& response.hovered()
|
&& response.hovered()
|
||||||
&& ui.input().pointer.is_moving()
|
&& ui.input().pointer.is_moving()
|
||||||
{
|
{
|
||||||
// preview:
|
// 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() {
|
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 ui.memory().has_focus(id) {
|
||||||
if let Some(cursorp) = state.cursorp {
|
if let Some(cursorp) = state.cursorp {
|
||||||
paint_cursor_selection(ui, response.rect.min, &galley, &cursorp);
|
paint_cursor_selection(ui, &painter, text_draw_pos, &galley, &cursorp);
|
||||||
paint_cursor_end(ui, response.rect.min, &galley, &cursorp.primary);
|
paint_cursor_end(ui, &painter, text_draw_pos, &galley, &cursorp.primary);
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
ui.ctx().output().text_cursor_pos = Some(
|
ui.ctx().output().text_cursor_pos = Some(
|
||||||
|
@ -746,18 +780,17 @@ impl<'t, S: TextBuffer> TextEdit<'t, S> {
|
||||||
.or(ui.visuals().override_text_color)
|
.or(ui.visuals().override_text_color)
|
||||||
// .unwrap_or_else(|| ui.style().interact(&response).text_color()); // too bright
|
// .unwrap_or_else(|| ui.style().interact(&response).text_color()); // too bright
|
||||||
.unwrap_or_else(|| ui.visuals().widgets.inactive.text_color());
|
.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() {
|
if text.as_ref().is_empty() && !hint_text.is_empty() {
|
||||||
let galley = if multiline {
|
let galley = if multiline {
|
||||||
ui.fonts()
|
ui.fonts()
|
||||||
.layout_multiline(text_style, hint_text, available_width)
|
.layout_multiline(text_style, hint_text, desired_size.x)
|
||||||
} else {
|
} else {
|
||||||
ui.fonts().layout_single_line(text_style, hint_text)
|
ui.fonts().layout_single_line(text_style, hint_text)
|
||||||
};
|
};
|
||||||
let hint_text_color = ui.visuals().weak_text_color();
|
let hint_text_color = ui.visuals().weak_text_color();
|
||||||
ui.painter()
|
painter.galley(response.rect.min, galley, hint_text_color);
|
||||||
.galley(response.rect.min, galley, hint_text_color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.memory().id_data.insert(id, state);
|
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;
|
let color = ui.visuals().selection.bg_fill;
|
||||||
if cursorp.is_empty() {
|
if cursorp.is_empty() {
|
||||||
return;
|
return;
|
||||||
|
@ -830,11 +869,11 @@ fn paint_cursor_selection(ui: &mut Ui, pos: Pos2, galley: &Galley, cursorp: &Cur
|
||||||
row.max_x() + newline_size
|
row.max_x() + newline_size
|
||||||
};
|
};
|
||||||
let rect = Rect::from_min_max(pos + vec2(left, row.y_min), pos + vec2(right, row.y_max));
|
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 stroke = ui.visuals().selection.stroke;
|
||||||
|
|
||||||
let cursor_pos = galley.pos_from_cursor(cursor).translate(pos.to_vec2());
|
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 top = cursor_pos.center_top();
|
||||||
let bottom = cursor_pos.center_bottom();
|
let bottom = cursor_pos.center_bottom();
|
||||||
|
|
||||||
ui.painter().line_segment(
|
painter.line_segment(
|
||||||
[top, bottom],
|
[top, bottom],
|
||||||
(ui.visuals().text_cursor_width, stroke.color),
|
(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:
|
// Roof/floor:
|
||||||
let extrusion = 3.0;
|
let extrusion = 3.0;
|
||||||
let width = 1.0;
|
let width = 1.0;
|
||||||
ui.painter().line_segment(
|
painter.line_segment(
|
||||||
[top - vec2(extrusion, 0.0), top + vec2(extrusion, 0.0)],
|
[top - vec2(extrusion, 0.0), top + vec2(extrusion, 0.0)],
|
||||||
(width, stroke.color),
|
(width, stroke.color),
|
||||||
);
|
);
|
||||||
ui.painter().line_segment(
|
painter.line_segment(
|
||||||
[bottom - vec2(extrusion, 0.0), bottom + vec2(extrusion, 0.0)],
|
[bottom - vec2(extrusion, 0.0), bottom + vec2(extrusion, 0.0)],
|
||||||
(width, stroke.color),
|
(width, stroke.color),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue