From d7bd15d9d2f710d4473cd0fe2e9cad492e3a45f2 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Sun, 13 Oct 2024 04:28:18 -0400 Subject: [PATCH 1/2] Fix hidden cursor when TextEdit is inside ScrollArea --- crates/egui/src/containers/scroll_area.rs | 22 +++++++++++++++++++- crates/egui/src/widgets/text_edit/builder.rs | 16 +++++++++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index 3b3925c9dcd..c039df7f186 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -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; 2], + animated: bool, } @@ -693,6 +698,10 @@ impl ScrollArea { } } + let saved_scroll_target = content_ui + .ctx() + .frame_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().frame_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(); diff --git a/crates/egui/src/widgets/text_edit/builder.rs b/crates/egui/src/widgets/text_edit/builder.rs index f4824a38117..c6815aa1982 100644 --- a/crates/egui/src/widgets/text_edit/builder.rs +++ b/crates/egui/src/widgets/text_edit/builder.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use emath::Rect; use epaint::text::{cursor::CCursor, Galley, LayoutJob}; use crate::{ @@ -719,6 +720,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 { @@ -726,10 +737,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 { From ca0f0e88c3197da7b289ede793007a174016f684 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Sun, 13 Oct 2024 05:06:49 -0400 Subject: [PATCH 2/2] Use pass_state --- crates/egui/src/containers/scroll_area.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/egui/src/containers/scroll_area.rs b/crates/egui/src/containers/scroll_area.rs index c039df7f186..5c7639de7c1 100644 --- a/crates/egui/src/containers/scroll_area.rs +++ b/crates/egui/src/containers/scroll_area.rs @@ -502,7 +502,7 @@ struct Prepared { /// 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; 2], + saved_scroll_target: [Option; 2], animated: bool, } @@ -700,7 +700,7 @@ impl ScrollArea { let saved_scroll_target = content_ui .ctx() - .frame_state_mut(|state| std::mem::take(&mut state.scroll_target)); + .pass_state_mut(|state| std::mem::take(&mut state.scroll_target)); Prepared { id, @@ -914,7 +914,7 @@ impl Prepared { } // Restore scroll target meant for ScrollAreas up the stack (if any) - ui.ctx().frame_state_mut(|state| { + 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();