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 c1569ed commit ac2065e
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 25 deletions.
17 changes: 10 additions & 7 deletions CHANGELOG.md
Expand Up @@ -8,27 +8,29 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
## Unreleased

### Added ⭐
* `Ui::input_mut` to modify how subsequent widgets see the `InputState` and a convenience method `InputState::consume_key` for shortcuts or hotkeys ([#1212](https://github.com/emilk/egui/pull/1212)).
* Much improved font selection ([#1154](https://github.com/emilk/egui/pull/1154)):
* You can now select any font size and family using `RichText::size` amd `RichText::family` and the new `FontId`.
* Easily change text styles with `Style::text_styles`.
* Added `Ui::text_style_height`.
* Added `TextStyle::resolve`.
* Made v-align and scale of user fonts tweakable ([#1241](https://github.com/emilk/egui/pull/1027)).
* Made v-align and scale of user fonts tweakable ([#1241](https://github.com/emilk/egui/pull/1027)).
* Plot:
* Added `Plot::x_axis_formatter` and `Plot::y_axis_formatter` for custom axis labels ([#1130](https://github.com/emilk/egui/pull/1130)).
* Added `Plot::allow_boxed_zoom()`, `Plot::boxed_zoom_pointer()` for boxed zooming on plots ([#1188](https://github.com/emilk/egui/pull/1188)).
* Added plot pointer coordinates with `Plot::coordinates_formatter`. ([#1235](https://github.com/emilk/egui/pull/1235)).
* Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)).
* `Context::load_texture` to convert an image into a texture which can be displayed using e.g. `ui.image(texture, size)` ([#1110](https://github.com/emilk/egui/pull/1110)).
* `Ui::input_mut` to modify how subsequent widgets see the `InputState` and a convenience method `InputState::consume_key` for shortcuts or hotkeys ([#1212](https://github.com/emilk/egui/pull/1212)).
* Added `Ui::add_visible` and `Ui::add_visible_ui`.
* Opt-in dependency on `tracing` crate for logging warnings ([#1192](https://github.com/emilk/egui/pull/1192)).
* Added `CollapsingHeader::icon` to override the default open/close icon using a custom function. ([1147](https://github.com/emilk/egui/pull/1147)).
* Added `Plot::x_axis_formatter` and `Plot::y_axis_formatter` for custom axis labels ([#1130](https://github.com/emilk/egui/pull/1130)).
* Added `ui.data()`, `ctx.data()`, `ctx.options()` and `ctx.tessellation_options()` ([#1175](https://github.com/emilk/egui/pull/1175)).
* Added `Plot::allow_boxed_zoom()`, `Plot::boxed_zoom_pointer()` for boxed zooming on plots ([#1188](https://github.com/emilk/egui/pull/1188)).
* Added linked axis support for plots via `plot::LinkedAxisGroup` ([#1184](https://github.com/emilk/egui/pull/1184)).
* Added `Response::on_hover_text_at_pointer` as a convenience akin to `Response::on_hover_text` ([1179](https://github.com/emilk/egui/pull/1179)).
* Added `ui.weak(text)`.
* Added `Context::move_to_top` and `Context::top_most_layer` for managing the layer on the top ([#1242](https://github.com/emilk/egui/pull/1242)).
* Added plot pointer coordinates with `Plot::coordinates_formatter`. ([#1235](https://github.com/emilk/egui/pull/1235)).
* Added `Slider::step_by` ([1255](https://github.com/emilk/egui/pull/1225)).
* Added `Slider::step_by` ([1225](https://github.com/emilk/egui/pull/1225)).
* Added ability to scroll an UI into view without specifying an alignment ([1247](https://github.com/emilk/egui/pull/1247)).
* Added `Ui::scroll_to_rect` ([1252](https://github.com/emilk/egui/pull/1252)).

### Changed 🔧
* ⚠️ `Context::input` and `Ui::input` now locks a mutex. This can lead to a dead-lock is used in an `if let` binding!
Expand Down Expand Up @@ -60,6 +62,7 @@ NOTE: [`epaint`](epaint/CHANGELOG.md), [`eframe`](eframe/CHANGELOG.md), [`egui_w
* Fixed `enable_drag` for Windows ([#1108](https://github.com/emilk/egui/pull/1108)).
* Calling `Context::set_pixels_per_point` before the first frame will now work.
* Tooltips that don't fit the window don't flicker anymore ([#1240](https://github.com/emilk/egui/pull/1240)).
* Scroll areas now follow text cursor ([1252](https://github.com/emilk/egui/pull/1252)).

### Contributors 🙏
* [AlexxxRu](https://github.com/alexxxru): [#1108](https://github.com/emilk/egui/pull/1108).
Expand Down
19 changes: 10 additions & 9 deletions egui/src/containers/scroll_area.rs
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
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
Expand Up @@ -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.").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 @@ -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];
Expand Down
8 changes: 6 additions & 2 deletions egui/src/widgets/text_edit/builder.rs
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 ac2065e

Please sign in to comment.