Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scroll so that text cursor remains visible #1252

Merged
merged 3 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
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
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 @@ -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;
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
33 changes: 17 additions & 16 deletions egui/src/widgets/text_edit/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -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,
Expand All @@ -570,30 +578,21 @@ 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());
}
}
}
}

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

cursor_pos
}

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