@@ -594,13 +594,25 @@ protected override void OnOpened(EventArgs e)
594594
595595 private void SetupScrollSync ( )
596596 {
597- // Capture wheel events using Tunnel strategy to intercept before TextEditor handles them
597+ var oursScroll = _oursPresenter ? . GetScrollViewer ( ) ;
598+ var theirsScroll = _theirsPresenter ? . GetScrollViewer ( ) ;
599+ var resultScroll = _resultPresenter ? . GetScrollViewer ( ) ;
600+
601+ // Wheel events
598602 if ( _oursPresenter != null )
599603 _oursPresenter . AddHandler ( PointerWheelChangedEvent , OnPresenterPointerWheelChanged , RoutingStrategies . Tunnel ) ;
600604 if ( _theirsPresenter != null )
601605 _theirsPresenter . AddHandler ( PointerWheelChangedEvent , OnPresenterPointerWheelChanged , RoutingStrategies . Tunnel ) ;
602606 if ( _resultPresenter != null )
603607 _resultPresenter . AddHandler ( PointerWheelChangedEvent , OnPresenterPointerWheelChanged , RoutingStrategies . Tunnel ) ;
608+
609+ // ScrollChanged for scrollbar drag
610+ if ( oursScroll != null )
611+ oursScroll . ScrollChanged += OnScrollChanged ;
612+ if ( theirsScroll != null )
613+ theirsScroll . ScrollChanged += OnScrollChanged ;
614+ if ( resultScroll != null )
615+ resultScroll . ScrollChanged += OnScrollChanged ;
604616 }
605617
606618 private void OnPresenterPointerWheelChanged ( object sender , PointerWheelEventArgs e )
@@ -609,27 +621,49 @@ private void OnPresenterPointerWheelChanged(object sender, PointerWheelEventArgs
609621 var theirsScroll = _theirsPresenter ? . GetScrollViewer ( ) ;
610622 var resultScroll = _resultPresenter ? . GetScrollViewer ( ) ;
611623
612- // Calculate scroll delta (negative because wheel up = scroll up = decrease offset)
613624 var delta = e . Delta . Y * 50 ;
614-
615- // Get current offset
616625 var currentOffset = oursScroll ? . Offset ?? Vector . Zero ;
617- var newY = Math . Max ( 0 , currentOffset . Y - delta ) ;
618- var newOffset = new Vector ( currentOffset . X , newY ) ;
626+ var newOffset = new Vector ( currentOffset . X , Math . Max ( 0 , currentOffset . Y - delta ) ) ;
619627
620- // Apply to all scroll viewers
621- if ( oursScroll != null )
622- oursScroll . Offset = newOffset ;
623- if ( theirsScroll != null )
624- theirsScroll . Offset = newOffset ;
625- if ( resultScroll != null )
626- resultScroll . Offset = newOffset ;
628+ SyncAllScrollViewers ( newOffset ) ;
629+ e . Handled = true ;
630+ }
627631
628- // Update ViewModel
629- if ( DataContext is ViewModels . MergeConflictEditor vm )
630- vm . ScrollOffset = newOffset ;
632+ private void OnScrollChanged ( object sender , ScrollChangedEventArgs e )
633+ {
634+ if ( _isSyncingScroll || sender is not ScrollViewer source )
635+ return ;
631636
632- e . Handled = true ;
637+ // Only sync if significant movement (scrollbar drag)
638+ if ( e . OffsetDelta . SquaredLength > 0.5f )
639+ {
640+ SyncAllScrollViewers ( source . Offset ) ;
641+ }
642+ }
643+
644+ private void SyncAllScrollViewers ( Vector offset )
645+ {
646+ _isSyncingScroll = true ;
647+ try
648+ {
649+ var oursScroll = _oursPresenter ? . GetScrollViewer ( ) ;
650+ var theirsScroll = _theirsPresenter ? . GetScrollViewer ( ) ;
651+ var resultScroll = _resultPresenter ? . GetScrollViewer ( ) ;
652+
653+ if ( oursScroll != null )
654+ oursScroll . Offset = offset ;
655+ if ( theirsScroll != null )
656+ theirsScroll . Offset = offset ;
657+ if ( resultScroll != null )
658+ resultScroll . Offset = offset ;
659+
660+ if ( DataContext is ViewModels . MergeConflictEditor vm )
661+ vm . ScrollOffset = offset ;
662+ }
663+ finally
664+ {
665+ _isSyncingScroll = false ;
666+ }
633667 }
634668
635669 private void OnViewModelPropertyChanged ( object sender , System . ComponentModel . PropertyChangedEventArgs e )
@@ -869,39 +903,37 @@ private void ScrollToCurrentConflict()
869903 var lineHeight = _oursPresenter . TextArea . TextView . DefaultLineHeight ;
870904 var vOffset = lineHeight * vm . CurrentConflictLine ;
871905 var targetOffset = new Vector ( 0 , Math . Max ( 0 , vOffset - _oursPresenter . Bounds . Height * 0.3 ) ) ;
872-
873- // Sync all panels to this offset
874- var oursScroll = _oursPresenter ? . GetScrollViewer ( ) ;
875- var theirsScroll = _theirsPresenter ? . GetScrollViewer ( ) ;
876- var resultScroll = _resultPresenter ? . GetScrollViewer ( ) ;
877-
878- if ( oursScroll != null )
879- oursScroll . Offset = targetOffset ;
880- if ( theirsScroll != null )
881- theirsScroll . Offset = targetOffset ;
882- if ( resultScroll != null )
883- resultScroll . Offset = targetOffset ;
884-
885- vm . ScrollOffset = targetOffset ;
906+ SyncAllScrollViewers ( targetOffset ) ;
886907 }
887908 }
888909 }
889910
890911 protected override void OnClosed ( EventArgs e )
891912 {
892- // Clean up scroll handlers
913+ var oursScroll = _oursPresenter ? . GetScrollViewer ( ) ;
914+ var theirsScroll = _theirsPresenter ? . GetScrollViewer ( ) ;
915+ var resultScroll = _resultPresenter ? . GetScrollViewer ( ) ;
916+
893917 if ( _oursPresenter != null )
894918 _oursPresenter . RemoveHandler ( PointerWheelChangedEvent , OnPresenterPointerWheelChanged ) ;
895919 if ( _theirsPresenter != null )
896920 _theirsPresenter . RemoveHandler ( PointerWheelChangedEvent , OnPresenterPointerWheelChanged ) ;
897921 if ( _resultPresenter != null )
898922 _resultPresenter . RemoveHandler ( PointerWheelChangedEvent , OnPresenterPointerWheelChanged ) ;
899923
924+ if ( oursScroll != null )
925+ oursScroll . ScrollChanged -= OnScrollChanged ;
926+ if ( theirsScroll != null )
927+ theirsScroll . ScrollChanged -= OnScrollChanged ;
928+ if ( resultScroll != null )
929+ resultScroll . ScrollChanged -= OnScrollChanged ;
930+
900931 base . OnClosed ( e ) ;
901932 GC . Collect ( ) ;
902933 }
903934
904935 private bool _forceClose = false ;
936+ private bool _isSyncingScroll = false ;
905937 private MergeDiffPresenter _oursPresenter ;
906938 private MergeDiffPresenter _theirsPresenter ;
907939 private MergeDiffPresenter _resultPresenter ;
0 commit comments