1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 14:20:04 -04:00

Scroll so that text cursor remains visible (#1252)

Closes https://github.com/emilk/egui/issues/165
This commit is contained in:
Emil Ernerfeldt
2022-02-16 21:34:47 +01:00
committed by GitHub
parent c1569ed0d7
commit 4af3cae26d
5 changed files with 71 additions and 39 deletions

View File

@@ -498,7 +498,7 @@ impl Prepared {
let clip_end = clip_rect.max[d];
let mut spacing = ui.spacing().item_spacing[d];
if let Some(align) = align {
let delta = if let Some(align) = align {
let center_factor = align.to_factor();
let offset =
@@ -507,19 +507,20 @@ impl Prepared {
// Depending on the alignment we need to add or subtract the spacing
spacing *= remap(center_factor, 0.0..=1.0, -1.0..=1.0);
state.offset[d] = offset + spacing;
offset + spacing
} else if start < clip_start && end < clip_end {
let min_adjust =
(clip_start - start + spacing).min(clip_end - end - spacing);
state.offset[d] -= min_adjust;
-(clip_start - start + spacing).min(clip_end - end - spacing)
} else if end > clip_end && start > clip_start {
let min_adjust =
(end - clip_end + spacing).min(start - clip_start - spacing);
state.offset[d] += min_adjust;
(end - clip_end + spacing).min(start - clip_start - spacing)
} else {
// Ui is already in view, no need to adjust scroll.
continue;
0.0
};
if delta != 0.0 {
state.offset[d] += delta;
ui.ctx().request_repaint();
}
}
}
}

View File

@@ -443,10 +443,11 @@ impl Response {
)
}
/// Adjust the scroll position until this UI becomes visible. If `align` is not provided, it'll scroll enough to
/// bring the UI into view.
/// Adjust the scroll position until this UI becomes visible.
///
/// See also [`Ui::scroll_to_cursor`]
/// If `align` is `None`, it'll scroll enough to bring the UI into view.
///
/// See also: [`Ui::scroll_to_cursor`], [`Ui::scroll_to`].
///
/// ```
/// # egui::__run_test_ui(|ui| {

View File

@@ -904,10 +904,36 @@ impl Ui {
(response, painter)
}
/// Adjust the scroll position until the cursor becomes visible. If `align` is not provided, it'll scroll enough to
/// bring the cursor into view.
/// Adjust the scroll position of any parent [`ScrollArea`] so that the given `Rect` becomes visible.
///
/// See also [`Response::scroll_to_me`]
/// If `align` is `None`, it'll scroll enough to bring the cursor into view.
///
/// See also: [`Response::scroll_to_me`], [`Ui::scroll_to`].
///
/// ```
/// # use egui::Align;
/// # egui::__run_test_ui(|ui| {
/// egui::ScrollArea::vertical().show(ui, |ui| {
/// // …
/// let response = ui.button("Center on me.");
/// if response.clicked() {
/// ui.scroll_to_rect(response.rect, Some(Align::Center));
/// }
/// });
/// # });
/// ```
pub fn scroll_to_rect(&self, rect: Rect, align: Option<Align>) {
for d in 0..2 {
let range = rect.min[d]..=rect.max[d];
self.ctx().frame_state().scroll_target[d] = Some((range, align));
}
}
/// Adjust the scroll position of any parent [`ScrollArea`] so that the cursor (where the next widget goes) becomes visible.
///
/// If `align` is not provided, it'll scroll enough to bring the cursor into view.
///
/// See also: [`Response::scroll_to_me`], [`Ui::scroll_to`].
///
/// ```
/// # use egui::Align;
@@ -924,7 +950,7 @@ impl Ui {
/// });
/// # });
/// ```
pub fn scroll_to_cursor(&mut self, align: Option<Align>) {
pub fn scroll_to_cursor(&self, align: Option<Align>) {
let target = self.next_widget_position();
for d in 0..2 {
let target = target[d];

View File

@@ -543,6 +543,14 @@ impl<'t> TextEdit<'t> {
text_draw_pos -= vec2(offset_x, 0.0);
}
let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) =
(cursor_range, prev_cursor_range)
{
prev_cursor_range.as_ccursor_range() != cursor_range.as_ccursor_range()
} else {
false
};
if ui.is_rect_visible(rect) {
painter.galley(text_draw_pos, galley.clone());
@@ -561,7 +569,7 @@ impl<'t> TextEdit<'t> {
// We paint the cursor on top of the text, in case
// the text galley has backgrounds (as e.g. `code` snippets in markup do).
paint_cursor_selection(ui, &painter, text_draw_pos, &galley, &cursor_range);
paint_cursor_end(
let cursor_pos = paint_cursor_end(
ui,
row_height,
&painter,
@@ -570,15 +578,14 @@ impl<'t> TextEdit<'t> {
&cursor_range.primary,
);
if response.changed || selection_changed {
ui.scroll_to_rect(cursor_pos, None); // keep cursor in view
}
if interactive && text.is_mutable() {
// egui_web uses `text_cursor_pos` when showing IME,
// so only set it when text is editable and visible!
ui.ctx().output().text_cursor_pos = Some(
galley
.pos_from_cursor(&cursor_range.primary)
.translate(response.rect.min.to_vec2())
.left_top(),
);
ui.ctx().output().text_cursor_pos = Some(cursor_pos.left_top());
}
}
}
@@ -586,14 +593,6 @@ impl<'t> TextEdit<'t> {
state.clone().store(ui.ctx(), id);
let selection_changed = if let (Some(cursor_range), Some(prev_cursor_range)) =
(cursor_range, prev_cursor_range)
{
prev_cursor_range.as_ccursor_range() != cursor_range.as_ccursor_range()
} else {
false
};
if response.changed {
response.widget_info(|| {
WidgetInfo::text_edit(
@@ -887,7 +886,7 @@ fn paint_cursor_end(
pos: Pos2,
galley: &Galley,
cursor: &Cursor,
) {
) -> Rect {
let stroke = ui.visuals().selection.stroke;
let mut cursor_pos = galley.pos_from_cursor(cursor).translate(pos.to_vec2());
@@ -915,6 +914,8 @@ fn paint_cursor_end(
(width, stroke.color),
);
}
cursor_pos
}
// ----------------------------------------------------------------------------