@@ -739,7 +739,11 @@ impl<T> TextState<T> {
739739 }
740740 }
741741
742- /// Sets the absolute scroll position of the text buffer.
742+ /// Sets the absolute scroll position of the text buffer. Note that text that has fixed
743+ /// alignment (e.g. `VerticalTextAlignment::Top`) will not be affected by this method,
744+ /// and the scroll position will be calculated based on the current text layout and line
745+ /// heights. For the scroll to take effect, alignment must be set to
746+ /// `VerticalTextAlignment::None`.
743747 ///
744748 /// This allows you to programmatically scroll the text content to a specific position.
745749 /// The scroll position is calculated based on line heights and text layout.
@@ -758,34 +762,39 @@ impl<T> TextState<T> {
758762 pub fn set_absolute_scroll ( & mut self , scroll : Point ) {
759763 let mut new_scroll = self . buffer . scroll ( ) ;
760764
765+ let can_scroll_vertically =
766+ matches ! ( self . style( ) . vertical_alignment, VerticalTextAlignment :: None ) ;
767+
761768 new_scroll. horizontal = scroll. x ;
762769
763- let line_height = self . style ( ) . line_height_pt ( ) ;
764- let mut line_index = 0 ;
765- let mut accumulated_height = 0.0 ;
770+ if can_scroll_vertically {
771+ let line_height = self . style ( ) . line_height_pt ( ) ;
772+ let mut line_index = 0 ;
773+ let mut accumulated_height = 0.0 ;
766774
767- for ( i, line) in self . buffer . lines . iter ( ) . enumerate ( ) {
768- let mut line_height_total = 0.0 ;
775+ for ( i, line) in self . buffer . lines . iter ( ) . enumerate ( ) {
776+ let mut line_height_total = 0.0 ;
769777
770- if let Some ( layout_lines) = line. layout_opt ( ) {
771- for layout_line in layout_lines {
772- line_height_total += layout_line. line_height_opt . unwrap_or ( line_height) ;
778+ if let Some ( layout_lines) = line. layout_opt ( ) {
779+ for layout_line in layout_lines {
780+ line_height_total += layout_line. line_height_opt . unwrap_or ( line_height) ;
781+ }
773782 }
774- }
775783
776- if accumulated_height + line_height_total > scroll. y {
777- line_index = i;
778- break ;
784+ if accumulated_height + line_height_total > scroll. y {
785+ line_index = i;
786+ break ;
787+ }
788+
789+ accumulated_height += line_height_total;
790+ line_index = i + 1 ; // In case we don't break, this will be the last line
779791 }
780792
781- accumulated_height += line_height_total;
782- line_index = i + 1 ; // In case we don't break, this will be the last line
793+ // Set the line and calculate the remaining vertical offset
794+ new_scroll. line = line_index;
795+ new_scroll. vertical = scroll. y - accumulated_height;
783796 }
784797
785- // Set the line and calculate the remaining vertical offset
786- new_scroll. line = line_index;
787- new_scroll. vertical = scroll. y - accumulated_height;
788-
789798 self . buffer . set_scroll ( new_scroll) ;
790799 }
791800
@@ -899,6 +908,8 @@ impl<T> TextState<T> {
899908 if update_reason. is_cursor_updated ( ) {
900909 let text_area_size = self . params . size ( ) ;
901910 let old_scroll = self . buffer . scroll ( ) ;
911+ let old_relative_caret_x = self . relative_caret_position . map_or ( 0.0 , |p| p. x ) ;
912+ let old_absolute_caret_x = old_relative_caret_x + old_scroll. horizontal ;
902913
903914 let caret_position_relative_to_buffer = adjust_vertical_scroll_to_make_caret_visible (
904915 & mut self . buffer ,
@@ -908,17 +919,13 @@ impl<T> TextState<T> {
908919 self . params . style ( ) ,
909920 ) ?;
910921 let mut new_scroll = self . buffer . scroll ( ) ;
911-
912- let current_relative_caret_offset = caret_position_relative_to_buffer. x ;
913-
914922 let text_area_width = text_area_size. x ;
915923
916924 // TODO: there was some other implementation that took horizontal alignment into account,
917925 // check if it is needed
918926 let new_absolute_caret_offset = caret_position_relative_to_buffer. x ;
919927
920928 // TODO: A little hack to set horizontal scroll
921-
922929 let current_absolute_visible_text_area = (
923930 old_scroll. horizontal ,
924931 old_scroll. horizontal + text_area_width,
@@ -928,23 +935,32 @@ impl<T> TextState<T> {
928935 let is_new_caret_visible =
929936 new_absolute_caret_offset >= min && new_absolute_caret_offset <= max;
930937
931- // If caret is within the visible text area, we don't need to scroll.
938+ // If the caret is within the visible text area, we don't need to scroll.
932939 // In that case, we should return the old scroll and modify the caret offset
933940 if is_new_caret_visible {
934- let should_update_horizontal_scroll = self . should_update_horizontal_scroll (
935- text_area_width,
936- current_relative_caret_offset,
937- new_absolute_caret_offset,
938- old_scroll. horizontal ,
939- ) ;
940-
941- let is_moving_caret = matches ! ( update_reason, UpdateReason :: MoveCaret ) ;
942-
943- if should_update_horizontal_scroll && !is_moving_caret {
944- new_scroll. horizontal =
945- new_absolute_caret_offset - current_relative_caret_offset;
946- } else {
947- new_scroll. horizontal = old_scroll. horizontal ;
941+ let is_moving_caret_without_updating_the_text =
942+ matches ! ( update_reason, UpdateReason :: MoveCaret ) ;
943+ if !is_moving_caret_without_updating_the_text {
944+ let text_shift = old_absolute_caret_x - new_absolute_caret_offset;
945+
946+ // If a text was deleted (caret moved left), adjust the scroll to compensate
947+ if text_shift > 0.0 {
948+ // Adjust scroll to keep the caret visually in the same position
949+ new_scroll. horizontal = ( old_scroll. horizontal - text_shift) . max ( 0.0 ) ;
950+
951+ // Ensure we don't scroll beyond the text boundaries
952+ let inner_dimensions = self . inner_size ( ) ;
953+ let area_width = self . outer_size ( ) . x ;
954+
955+ if inner_dimensions. x > area_width {
956+ // Text is larger than viewport - clamp scroll to valid range
957+ let max_scroll = inner_dimensions. x - area_width + self . caret_width ;
958+ new_scroll. horizontal = new_scroll. horizontal . min ( max_scroll) ;
959+ } else {
960+ // Text fits within the viewport - no scroll needed
961+ new_scroll. horizontal = 0.0 ;
962+ }
963+ }
948964 }
949965 } else if new_absolute_caret_offset > max {
950966 new_scroll. horizontal =
@@ -963,57 +979,6 @@ impl<T> TextState<T> {
963979 None
964980 }
965981
966- /// Determines if we should use improved scroll behavior where the caret stays visually
967- /// fixed while deleting overflowing text, instead of moving the caret within the visible area.
968- ///
969- /// This behavior is used when:
970- /// 1. Text overflows the visible area (text is longer than area width)
971- /// 2. We're likely deleting from the end (caret moved to the left)
972- /// 3. There's horizontal scroll present
973- fn should_update_horizontal_scroll (
974- & self ,
975- text_area_width : f32 ,
976- old_relative_caret_x : f32 ,
977- new_absolute_caret_x : f32 ,
978- current_scroll_x : f32 ,
979- ) -> bool {
980- // Only apply improved behavior when there's existing scroll
981- if current_scroll_x <= 0.0 {
982- return false ;
983- }
984-
985- // Calculate approximate text width based on buffer content
986- let text_overflows = self . estimate_text_overflows ( text_area_width) ;
987- if !text_overflows {
988- return false ;
989- }
990-
991- // Check if caret moved to the left (likely deletion from end)
992- let old_absolute_caret_x = old_relative_caret_x + current_scroll_x;
993-
994- // Use improved behavior when text overflows and caret moved left
995- new_absolute_caret_x < old_absolute_caret_x
996- }
997-
998- /// Estimates if a text overflows the given width by examining the buffer's layout
999- fn estimate_text_overflows ( & self , text_area_width : f32 ) -> bool {
1000- // TODO: check if it's better done with inner_dimensions instead of trying to figure out width
1001- // Look at the last glyph position to estimate if text overflows
1002- if let Some ( line) = & self . buffer . lines . last ( ) {
1003- if let Some ( layouts) = line. layout_opt ( ) . as_ref ( ) {
1004- if let Some ( layout) = layouts. last ( ) {
1005- if let Some ( last_glyph) = layout. glyphs . last ( ) {
1006- let text_width = last_glyph. x + last_glyph. w ;
1007- return text_width > text_area_width;
1008- }
1009- }
1010- }
1011- }
1012-
1013- // Fallback: assume no overflow if we can't determine
1014- false
1015- }
1016-
1017982 /// Reshapes the text buffer if parameters have changed since the last reshape.
1018983 ///
1019984 /// This method checks if any text parameters (content, style, size) have changed
0 commit comments