Fix cursor clipping in `TextEdit` inside a `ScrollArea` (#3660)
<!-- Please read the "Making a PR" section of [`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md) before opening a Pull Request! * Keep your PR:s small and focused. * If applicable, add a screenshot or gif. * If it is a non-trivial addition, consider adding a demo for it to `egui_demo_lib`, or a new example. * Do NOT open PR:s from your `master` branch, as that makes it hard for maintainers to add commits to your PR. * Remember to run `cargo fmt` and `cargo cranky`. * Open the PR as a draft until you have self-reviewed it and run `./scripts/check.sh`. * When you have addressed a PR comment, mark it as resolved. Please be patient! I will review your PR, but my time is limited! --> * Closes #1531 ### Before Notice how the cursor hides after third enter and when the line is long. https://github.com/user-attachments/assets/8e45736e-d6c7-4dc6-94d0-213188c199ff ### After Cursor is always visible https://github.com/user-attachments/assets/43200683-3524-471b-990a-eb7b49385fa9 - `ScrollArea` now checks if there's a `scroll_target` in `begin`, if there is, it saves it because it's not from its children, then restore it in `end`. - `TextEdit` now allocates additional space if its galley grows during the frame. This is needed so that any surrounding `ScrollArea` can bring the cursor to view, otherwise the cursor lays outside the the `ScrollArea`'s `content_ui`.
This commit is contained in:
parent
6833cf56e1
commit
4f7f23ef5e
|
|
@ -499,6 +499,11 @@ struct Prepared {
|
|||
|
||||
scrolling_enabled: bool,
|
||||
stick_to_end: Vec2b,
|
||||
|
||||
/// If there was a scroll target before the ScrollArea was added this frame, it's
|
||||
/// not for us to handle so we save it and restore it after this ScrollArea is done.
|
||||
saved_scroll_target: [Option<pass_state::ScrollTarget>; 2],
|
||||
|
||||
animated: bool,
|
||||
}
|
||||
|
||||
|
|
@ -693,6 +698,10 @@ impl ScrollArea {
|
|||
}
|
||||
}
|
||||
|
||||
let saved_scroll_target = content_ui
|
||||
.ctx()
|
||||
.pass_state_mut(|state| std::mem::take(&mut state.scroll_target));
|
||||
|
||||
Prepared {
|
||||
id,
|
||||
state,
|
||||
|
|
@ -707,6 +716,7 @@ impl ScrollArea {
|
|||
viewport,
|
||||
scrolling_enabled,
|
||||
stick_to_end,
|
||||
saved_scroll_target,
|
||||
animated,
|
||||
}
|
||||
}
|
||||
|
|
@ -820,6 +830,7 @@ impl Prepared {
|
|||
viewport: _,
|
||||
scrolling_enabled,
|
||||
stick_to_end,
|
||||
saved_scroll_target,
|
||||
animated,
|
||||
} = self;
|
||||
|
||||
|
|
@ -853,7 +864,7 @@ impl Prepared {
|
|||
let (start, end) = (range.min, range.max);
|
||||
let clip_start = clip_rect.min[d];
|
||||
let clip_end = clip_rect.max[d];
|
||||
let mut spacing = ui.spacing().item_spacing[d];
|
||||
let mut spacing = content_ui.spacing().item_spacing[d];
|
||||
|
||||
let delta_update = if let Some(align) = align {
|
||||
let center_factor = align.to_factor();
|
||||
|
|
@ -902,6 +913,15 @@ impl Prepared {
|
|||
}
|
||||
}
|
||||
|
||||
// Restore scroll target meant for ScrollAreas up the stack (if any)
|
||||
ui.ctx().pass_state_mut(|state| {
|
||||
for d in 0..2 {
|
||||
if saved_scroll_target[d].is_some() {
|
||||
state.scroll_target[d] = saved_scroll_target[d].clone();
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
let inner_rect = {
|
||||
// At this point this is the available size for the inner rect.
|
||||
let mut inner_size = inner_rect.size();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use emath::Rect;
|
||||
use epaint::text::{cursor::CCursor, Galley, LayoutJob};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -720,6 +721,16 @@ impl<'t> TextEdit<'t> {
|
|||
}
|
||||
}
|
||||
|
||||
// Allocate additional space if edits were made this frame that changed the size. This is important so that,
|
||||
// if there's a ScrollArea, it can properly scroll to the cursor.
|
||||
let extra_size = galley.size() - rect.size();
|
||||
if extra_size.x > 0.0 || extra_size.y > 0.0 {
|
||||
ui.allocate_rect(
|
||||
Rect::from_min_size(outer_rect.max, extra_size),
|
||||
Sense::hover(),
|
||||
);
|
||||
}
|
||||
|
||||
painter.galley(galley_pos, galley.clone(), text_color);
|
||||
|
||||
if has_focus {
|
||||
|
|
@ -727,10 +738,9 @@ impl<'t> TextEdit<'t> {
|
|||
let primary_cursor_rect =
|
||||
cursor_rect(galley_pos, &galley, &cursor_range.primary, row_height);
|
||||
|
||||
let is_fully_visible = ui.clip_rect().contains_rect(rect); // TODO(emilk): remove this HACK workaround for https://github.com/emilk/egui/issues/1531
|
||||
if (response.changed || selection_changed) && !is_fully_visible {
|
||||
if response.changed || selection_changed {
|
||||
// Scroll to keep primary cursor in view:
|
||||
ui.scroll_to_rect(primary_cursor_rect, None);
|
||||
ui.scroll_to_rect(primary_cursor_rect + margin, None);
|
||||
}
|
||||
|
||||
if text.is_mutable() && interactive {
|
||||
|
|
|
|||
Loading…
Reference in New Issue