Skip to content

Commit

Permalink
Scroll so that text cursor remains visible
Browse files Browse the repository at this point in the history
Closes #165
  • Loading branch information
emilk committed Feb 15, 2022
1 parent cfad289 commit 4b5948a
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 18 deletions.
19 changes: 10 additions & 9 deletions egui/src/containers/scroll_area.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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();
}
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions egui/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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| {
Expand Down
34 changes: 30 additions & 4 deletions egui/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,10 +889,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.").clicked();
/// 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;
Expand All @@ -909,7 +935,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];
Expand Down
8 changes: 6 additions & 2 deletions egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,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,
Expand All @@ -570,6 +570,8 @@ impl<'t> TextEdit<'t> {
&cursor_range.primary,
);

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!
Expand Down Expand Up @@ -887,7 +889,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());
Expand Down Expand Up @@ -915,6 +917,8 @@ fn paint_cursor_end(
(width, stroke.color),
);
}

cursor_pos
}

// ----------------------------------------------------------------------------
Expand Down

0 comments on commit 4b5948a

Please sign in to comment.