66using System . Runtime . CompilerServices ;
77using WinUIAnnotatedScrollBar = Microsoft . UI . Xaml . Controls . AnnotatedScrollBar ;
88using WinUIItemsView = Microsoft . UI . Xaml . Controls . ItemsView ;
9- using WinUIScrollingInteractionState = Microsoft . UI . Xaml . Controls . ScrollingInteractionState ;
109using WinUIScrollView = Microsoft . UI . Xaml . Controls . ScrollView ;
1110
1211namespace OpenClawTray . Chat ;
@@ -16,7 +15,7 @@ file sealed record ItemsViewVerticalScrollControllerElement(
1615 ElementRef < WinUIAnnotatedScrollBar > ScrollBarRef ,
1716 int InitialTailIndex ,
1817 string InitialTailRequestKey ,
19- ReactorStreamingTailState ? StreamingTailState ) : Element
18+ string ? DisplayedTailKey ) : Element
2019{
2120 static ItemsViewVerticalScrollControllerElement ( ) =>
2221 ControlRegistry . RegisterDecorator < ItemsViewVerticalScrollControllerElement > (
@@ -40,7 +39,10 @@ public UIElement Mount(MountContext context, ItemsViewVerticalScrollControllerEl
4039 ( ( WinUIItemsView ) value ) . VerticalScrollController = scrollBar ? . ScrollController ) ;
4140 var positioner = new InitialTailPositioner ( itemsView ) ;
4241 Positioners . Add ( itemsView , positioner ) ;
43- positioner . Request ( element . InitialTailIndex , element . InitialTailRequestKey ) ;
42+ positioner . Request (
43+ element . InitialTailIndex ,
44+ element . InitialTailRequestKey ,
45+ element . DisplayedTailKey ) ;
4446 return itemsView ;
4547 }
4648
@@ -55,12 +57,14 @@ public UIElement Update(
5557 throw new InvalidOperationException ( "ItemsView scroll controller binding requires an ItemsView child." ) ;
5658 if ( ! string . Equals ( oldElement . InitialTailRequestKey , newElement . InitialTailRequestKey , StringComparison . Ordinal )
5759 && Positioners . TryGetValue ( itemsView , out var positioner ) )
58- positioner . Request ( newElement . InitialTailIndex , newElement . InitialTailRequestKey ) ;
60+ positioner . Request (
61+ newElement . InitialTailIndex ,
62+ newElement . InitialTailRequestKey ,
63+ newElement . DisplayedTailKey ) ;
5964 else if ( Positioners . TryGetValue ( itemsView , out var existingPositioner ) )
60- existingPositioner . UpdateTailIndex (
65+ existingPositioner . UpdateTail (
6166 newElement . InitialTailIndex ,
62- oldElement . StreamingTailState ,
63- newElement . StreamingTailState ) ;
67+ newElement . DisplayedTailKey ) ;
6468 return itemsView ;
6569 }
6670
@@ -78,22 +82,18 @@ public V1UnmountDisposition Unmount(UnmountContext context, ItemsViewVerticalScr
7882file sealed class InitialTailPositioner : IDisposable
7983{
8084 private const double FollowThreshold = 60 ;
81- private const int StreamingTailFollowIntervalMs = 125 ;
8285
8386 private readonly WinUIItemsView itemsView ;
8487 private string ? _requestKey ;
88+ private string ? _displayedTailKey ;
8589 private int _tailIndex ;
8690 private int _version ;
8791 private bool _valid ;
8892 private bool _awaitingLayout ;
8993 private WinUIScrollView ? _awaitingScrollView ;
9094 private WinUIScrollView ? _scrollView ;
91- private DispatcherTimer ? _streamingTailFollowTimer ;
92- private ReactorStreamingTailState ? _pendingStreamingTail ;
9395 private bool _following ;
9496 private bool _tailRequestQueued ;
95- private bool _streamingTailFollowPending ;
96- private bool _userInteractionObserved ;
9797 private bool _disposed ;
9898
9999 public InitialTailPositioner ( WinUIItemsView itemsView )
@@ -103,41 +103,34 @@ public InitialTailPositioner(WinUIItemsView itemsView)
103103 itemsView . Unloaded += OnUnloaded ;
104104 }
105105
106- public void Request ( int tailIndex , string requestKey )
106+ public void Request ( int tailIndex , string requestKey , string ? displayedTailKey )
107107 {
108108 if ( _disposed || string . Equals ( _requestKey , requestKey , StringComparison . Ordinal ) )
109109 return ;
110110
111111 _requestKey = requestKey ;
112112 _version ++ ;
113113 DetachLayout ( ) ;
114- StopStreamingTailFollow ( ) ;
115114 _valid = tailIndex >= 0 ;
116115 if ( ! _valid )
117116 return ;
118117
119118 _tailIndex = tailIndex ;
119+ _displayedTailKey = displayedTailKey ;
120120 _following = true ;
121121 if ( itemsView . IsLoaded )
122122 AwaitLayout ( ) ;
123123 }
124124
125- public void UpdateTailIndex (
126- int tailIndex ,
127- ReactorStreamingTailState ? previousStreamingTail ,
128- ReactorStreamingTailState ? currentStreamingTail )
125+ public void UpdateTail ( int tailIndex , string ? displayedTailKey )
129126 {
130- var changed = _tailIndex != tailIndex ;
127+ var changed = ! string . Equals ( _displayedTailKey , displayedTailKey , StringComparison . Ordinal ) ;
131128 _tailIndex = tailIndex ;
132- if ( changed && _following && tailIndex >= 0 && itemsView . IsLoaded )
129+ _displayedTailKey = displayedTailKey ;
130+ if ( changed && _following && tailIndex >= 0 && itemsView . IsLoaded && displayedTailKey is not null )
133131 {
134- StopStreamingTailFollow ( ) ;
135132 QueueTailRequest ( _version ) ;
136- return ;
137133 }
138-
139- if ( _following && IsStreamingTailUpdate ( previousStreamingTail , currentStreamingTail ) )
140- RequestStreamingTailFollow ( currentStreamingTail ! ) ;
141134 }
142135
143136 private void OnLoaded ( object sender , RoutedEventArgs args )
@@ -206,24 +199,15 @@ private void AttachScrollView()
206199 DetachScrollView ( ) ;
207200 _scrollView = nextScrollView ;
208201 if ( _scrollView is not null )
202+ {
203+ _scrollView . VerticalAnchorRatio = 1.0 ;
209204 _scrollView . ViewChanged += OnViewChanged ;
205+ }
210206 }
211207
212208 private void OnViewChanged ( WinUIScrollView sender , object args )
213209 {
214- if ( sender . State == WinUIScrollingInteractionState . Interaction )
215- {
216- _following = false ;
217- _userInteractionObserved = true ;
218- StopStreamingTailFollow ( ) ;
219- return ;
220- }
221-
222- if ( _userInteractionObserved )
223- {
224- _userInteractionObserved = false ;
225- _following = IsNearBottom ( sender ) ;
226- }
210+ _following = IsNearBottom ( sender ) ;
227211 }
228212
229213 private void QueueTailRequest ( int version )
@@ -252,7 +236,6 @@ private void StartTailRequest(int index)
252236 {
253237 if ( itemsView . ScrollView is not { IsLoaded : true } )
254238 return ;
255-
256239 _following = true ;
257240 itemsView . StartBringItemIntoView ( index , new BringIntoViewOptions
258241 {
@@ -261,67 +244,13 @@ private void StartTailRequest(int index)
261244 } ) ;
262245 }
263246
264- private void RequestStreamingTailFollow ( ReactorStreamingTailState streamingTail )
265- {
266- _pendingStreamingTail = streamingTail ;
267- _streamingTailFollowPending = true ;
268- _streamingTailFollowTimer ??= CreateStreamingTailFollowTimer ( ) ;
269- if ( ! _streamingTailFollowTimer . IsEnabled )
270- _streamingTailFollowTimer . Start ( ) ;
271- }
272-
273- private DispatcherTimer CreateStreamingTailFollowTimer ( )
274- {
275- var timer = new DispatcherTimer
276- {
277- Interval = TimeSpan . FromMilliseconds ( StreamingTailFollowIntervalMs ) ,
278- } ;
279- timer . Tick += OnStreamingTailFollowTick ;
280- return timer ;
281- }
282-
283- private void OnStreamingTailFollowTick ( object ? sender , object args )
284- {
285- _streamingTailFollowTimer ? . Stop ( ) ;
286- if ( _disposed
287- || ! _valid
288- || ! _following
289- || ! _streamingTailFollowPending
290- || ! itemsView . IsLoaded
291- || _scrollView is not { IsLoaded : true , State : not WinUIScrollingInteractionState . Interaction } )
292- {
293- StopStreamingTailFollow ( ) ;
294- return ;
295- }
296-
297- _streamingTailFollowPending = false ;
298- var streamingTail = _pendingStreamingTail ;
299- _pendingStreamingTail = null ;
300- StartTailRequest ( _tailIndex ) ;
301- if ( streamingTail is { IsStreaming : true } && _following )
302- return ;
303-
304- StopStreamingTailFollow ( ) ;
305- }
306-
307247 private static bool IsNearBottom ( WinUIScrollView scrollView ) =>
308248 scrollView . ScrollableHeight - scrollView . VerticalOffset <= FollowThreshold ;
309249
310- private static bool IsStreamingTailUpdate (
311- ReactorStreamingTailState ? previous ,
312- ReactorStreamingTailState ? current ) =>
313- previous is not null
314- && current is not null
315- && string . Equals ( previous . EntryId , current . EntryId , StringComparison . Ordinal )
316- && ( ( current . IsStreaming
317- && ( ! previous . IsStreaming || previous . TextLength != current . TextLength ) )
318- || ( previous . IsStreaming && ! current . IsStreaming ) ) ;
319-
320250 private void OnUnloaded ( object sender , RoutedEventArgs args )
321251 {
322252 _version ++ ;
323253 DetachLayout ( ) ;
324- StopStreamingTailFollow ( ) ;
325254 DetachScrollView ( ) ;
326255 }
327256
@@ -343,18 +272,14 @@ private void DetachLayout()
343272 private void DetachScrollView ( )
344273 {
345274 if ( _scrollView is not null )
275+ {
276+ _scrollView . VerticalAnchorRatio = double . NaN ;
346277 _scrollView . ViewChanged -= OnViewChanged ;
278+ }
347279
348280 _scrollView = null ;
349281 }
350282
351- private void StopStreamingTailFollow ( )
352- {
353- _streamingTailFollowTimer ? . Stop ( ) ;
354- _pendingStreamingTail = null ;
355- _streamingTailFollowPending = false ;
356- }
357-
358283 public void Dispose ( )
359284 {
360285 if ( _disposed )
@@ -363,12 +288,6 @@ public void Dispose()
363288 _disposed = true ;
364289 _version ++ ;
365290 DetachLayout ( ) ;
366- StopStreamingTailFollow ( ) ;
367- if ( _streamingTailFollowTimer is not null )
368- {
369- _streamingTailFollowTimer . Tick -= OnStreamingTailFollowTick ;
370- _streamingTailFollowTimer = null ;
371- }
372291 DetachScrollView ( ) ;
373292 itemsView . Loaded -= OnLoaded ;
374293 itemsView . Unloaded -= OnUnloaded ;
@@ -382,11 +301,11 @@ public static Element BindVerticalScrollController<T>(
382301 ElementRef < WinUIAnnotatedScrollBar > scrollBarRef ,
383302 int initialTailIndex ,
384303 string initialTailRequestKey ,
385- ReactorStreamingTailState ? streamingTailState ) =>
304+ string ? displayedTailKey ) =>
386305 new ItemsViewVerticalScrollControllerElement (
387306 itemsView ,
388307 scrollBarRef ,
389308 initialTailIndex ,
390309 initialTailRequestKey ,
391- streamingTailState ) ;
310+ displayedTailKey ) ;
392311}
0 commit comments