Scroll so that text cursor remains visible (#1252)
Closes https://github.com/emilk/egui/issues/165
This commit is contained in:
parent
c1569ed0d7
commit
4af3cae26d
5 changed files with 71 additions and 39 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -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!
|
||||
|
@ -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).
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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| {
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in a new issue