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,
|
scrolling_enabled: bool,
|
||||||
stick_to_end: Vec2b,
|
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,
|
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 {
|
Prepared {
|
||||||
id,
|
id,
|
||||||
state,
|
state,
|
||||||
|
|
@ -707,6 +716,7 @@ impl ScrollArea {
|
||||||
viewport,
|
viewport,
|
||||||
scrolling_enabled,
|
scrolling_enabled,
|
||||||
stick_to_end,
|
stick_to_end,
|
||||||
|
saved_scroll_target,
|
||||||
animated,
|
animated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -820,6 +830,7 @@ impl Prepared {
|
||||||
viewport: _,
|
viewport: _,
|
||||||
scrolling_enabled,
|
scrolling_enabled,
|
||||||
stick_to_end,
|
stick_to_end,
|
||||||
|
saved_scroll_target,
|
||||||
animated,
|
animated,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
|
|
@ -853,7 +864,7 @@ impl Prepared {
|
||||||
let (start, end) = (range.min, range.max);
|
let (start, end) = (range.min, range.max);
|
||||||
let clip_start = clip_rect.min[d];
|
let clip_start = clip_rect.min[d];
|
||||||
let clip_end = clip_rect.max[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 delta_update = if let Some(align) = align {
|
||||||
let center_factor = align.to_factor();
|
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 = {
|
let inner_rect = {
|
||||||
// At this point this is the available size for the inner rect.
|
// At this point this is the available size for the inner rect.
|
||||||
let mut inner_size = inner_rect.size();
|
let mut inner_size = inner_rect.size();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use emath::Rect;
|
||||||
use epaint::text::{cursor::CCursor, Galley, LayoutJob};
|
use epaint::text::{cursor::CCursor, Galley, LayoutJob};
|
||||||
|
|
||||||
use crate::{
|
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);
|
painter.galley(galley_pos, galley.clone(), text_color);
|
||||||
|
|
||||||
if has_focus {
|
if has_focus {
|
||||||
|
|
@ -727,10 +738,9 @@ impl<'t> TextEdit<'t> {
|
||||||
let primary_cursor_rect =
|
let primary_cursor_rect =
|
||||||
cursor_rect(galley_pos, &galley, &cursor_range.primary, row_height);
|
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 {
|
||||||
if (response.changed || selection_changed) && !is_fully_visible {
|
|
||||||
// Scroll to keep primary cursor in view:
|
// 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 {
|
if text.is_mutable() && interactive {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue