11using System . Collections . Specialized ;
2+ using System . Diagnostics ;
23using System . Globalization ;
34using System . Text ;
45using Microsoft . UI . Dispatching ;
@@ -42,6 +43,7 @@ public sealed partial class SegmentedMeter : Control
4243 private const double FallbackThickness = 8 ;
4344 private const double FallbackMarkerSize = 8 ;
4445 private const double FallbackVerticalMinLength = 120 ;
46+ private const double AnimationDuration = 450 ;
4547
4648 private static readonly int KindCount = Enum . GetValues < MeterSegmentKind > ( ) . Length ;
4749 private static readonly int SizeCount = Enum . GetValues < MeterBarSize > ( ) . Length ;
@@ -58,10 +60,13 @@ public sealed partial class SegmentedMeter : Control
5860 private readonly ObservableCollection < MeterSegment > _segments = new ( ) ;
5961 private readonly List < MeterSegment > _tracked = new ( ) ;
6062 private readonly List < double > _fractions = new ( ) ;
63+ private readonly List < BarSlot > _bars = new ( ) ;
64+ private readonly Dictionary < MeterSegment , double > _previous = new ( ) ;
6165 private readonly Style ? [ ] _segmentStyles ;
6266 private readonly Style ? [ ] _iconStyles ;
6367 private readonly double [ ] _thicknesses ;
6468 private readonly DispatcherQueueHandler _rebuildHandler ;
69+ private readonly EventHandler < object > _renderingHandler ;
6570
6671 private FrameworkElement _root = null ! ;
6772 private ContentPresenter _start = null ! ;
@@ -71,12 +76,23 @@ public sealed partial class SegmentedMeter : Control
7176 private MeterLegendPanel _legend = null ! ;
7277 private ContentPresenter _end = null ! ;
7378
79+ private ColumnDefinition ? _restColumn ;
80+ private RowDefinition ? _restRow ;
7481 private Style ? _legendItemStyle ;
7582 private Style ? _legendLabelStyle ;
7683 private Style ? _legendValueStyle ;
7784 private double _markerSize = FallbackMarkerSize ;
7885 private double _verticalMinLength = FallbackVerticalMinLength ;
7986
87+ private bool _barHorizontal ;
88+ private double _barRadius ;
89+ private double _barGap ;
90+ private int _shapeFirst ;
91+ private int _shapeLast ;
92+ private bool _shapeFull ;
93+ private long _animationStart ;
94+ private bool _animating ;
95+
8096 private bool _loaded ;
8197 private bool _templated ;
8298 private bool _firstShow = true ;
@@ -95,6 +111,7 @@ public SegmentedMeter()
95111 _iconStyles = new Style ? [ KindCount ] ;
96112 _thicknesses = new double [ SizeCount ] ;
97113 _rebuildHandler = OnRebuildRequested ;
114+ _renderingHandler = OnRendering ;
98115
99116 _segments . CollectionChanged += OnSegmentsChanged ;
100117 Loaded += OnLoaded ;
@@ -162,6 +179,7 @@ private void OnLoaded(object sender, RoutedEventArgs e)
162179 private void OnUnloaded ( object sender , RoutedEventArgs e )
163180 {
164181 _loaded = false ;
182+ StopAnimation ( ) ;
165183 DetachSegments ( ) ;
166184 }
167185
@@ -255,7 +273,9 @@ private void Rebuild()
255273
256274 bool legend = BuildLegend ( ) ;
257275 ArrangeBody ( horizontal , legend ) ;
258- BuildBar ( horizontal , used ) ;
276+
277+ if ( BuildBar ( horizontal , used ) ) StartAnimation ( ) ;
278+ else StopAnimation ( ) ;
259279 }
260280
261281 private double Fraction ( double value )
@@ -326,12 +346,16 @@ private void ArrangeBody(bool horizontal, bool legend)
326346 }
327347 }
328348
329- private void BuildBar ( bool horizontal , double used )
349+ private bool BuildBar ( bool horizontal , double used )
330350 {
331351 double thickness = ResolveThickness ( ) ;
332352 double radius = thickness / 2 ;
333- double gap = Math . Max ( 0 , Gap ) ;
334- bool full = used >= 1 - Tolerance ;
353+
354+ _barHorizontal = horizontal ;
355+ _barRadius = radius ;
356+ _barGap = Math . Max ( 0 , Gap ) ;
357+ _shapeFirst = - 2 ;
358+ _shapeLast = - 2 ;
335359
336360 _track . CornerRadius = new CornerRadius ( radius ) ;
337361
@@ -348,87 +372,191 @@ private void BuildBar(bool horizontal, double used)
348372 _track . MinHeight = _verticalMinLength ;
349373 }
350374
375+ _previous . Clear ( ) ;
376+ for ( int i = 0 ; i < _bars . Count ; i ++ ) _previous [ _bars [ i ] . Segment ] = _bars [ i ] . Current ;
377+ _bars . Clear ( ) ;
378+
351379 _barGrid . Children . Clear ( ) ;
352380 _barGrid . ColumnDefinitions . Clear ( ) ;
353381 _barGrid . RowDefinitions . Clear ( ) ;
354382
355- int last = - 1 ;
356- for ( int i = _fractions . Count - 1 ; i >= 0 ; i -- )
383+ bool reveal = false ;
384+ if ( _firstShow && used > 0 )
357385 {
358- if ( _fractions [ i ] > 0 )
359- {
360- last = i ;
361- break ;
362- }
386+ _firstShow = false ;
387+ reveal = Animated ;
363388 }
364389
390+ bool animate = Animated && ! reveal ;
391+ bool changed = false ;
392+
365393 Compositor ? compositor = null ;
366394 CubicBezierEasingFunction ? easing = null ;
367395
368- if ( _firstShow && last >= 0 )
396+ if ( reveal )
369397 {
370- _firstShow = false ;
371-
372- if ( Animated )
373- {
374- compositor = ElementCompositionPreview . GetElementVisual ( _barGrid ) . Compositor ;
375- easing = compositor . CreateCubicBezierEasingFunction ( new Vector2 ( 0.22f , 1f ) , new Vector2 ( 0.36f , 1f ) ) ;
376- }
398+ compositor = ElementCompositionPreview . GetElementVisual ( _barGrid ) . Compositor ;
399+ easing = compositor . CreateCubicBezierEasingFunction ( new Vector2 ( 0.22f , 1f ) , new Vector2 ( 0.36f , 1f ) ) ;
377400 }
378401
379402 int ordinal = 0 ;
380403 for ( int i = 0 ; i < _fractions . Count ; i ++ )
381404 {
382- double fraction = _fractions [ i ] ;
383- if ( fraction <= 0 ) continue ;
384-
385405 var segment = _segments [ i ] ;
386- bool isLast = i == last ;
387- double start = ordinal == 0 ? radius : 0 ;
388- double end = isLast && full ? radius : 0 ;
389- double trailingGap = isLast ? 0 : gap ;
390-
391- var bar = new Border ( ) ;
406+ double target = _fractions [ i ] ;
407+ double from = target ;
392408
393- if ( horizontal )
394- {
395- bar . Margin = new Thickness ( 0 , 0 , trailingGap , 0 ) ;
396- bar . CornerRadius = new CornerRadius ( start , end , end , start ) ;
397- }
398- else
399- {
400- bar . Margin = new Thickness ( 0 , 0 , 0 , trailingGap ) ;
401- bar . CornerRadius = new CornerRadius ( start , start , end , end ) ;
402- }
409+ if ( animate ) from = _previous . TryGetValue ( segment , out var shown ) ? shown : 0 ;
410+ if ( target <= 0 && from <= 0 ) continue ;
403411
412+ var bar = new Border ( ) ;
404413 Paint ( bar , ResolveKind ( segment , i ) , segment . Fill ) ;
405414 ToolTipService . SetToolTip ( bar , FormatTip ( segment ) ) ;
406415
407- var length = new GridLength ( fraction , GridUnitType . Star ) ;
416+ var length = new GridLength ( from , GridUnitType . Star ) ;
417+ ColumnDefinition ? column = null ;
418+ RowDefinition ? row = null ;
419+
408420 if ( horizontal )
409421 {
410- _barGrid . ColumnDefinitions . Add ( new ColumnDefinition { Width = length } ) ;
422+ column = new ColumnDefinition { Width = length } ;
423+ _barGrid . ColumnDefinitions . Add ( column ) ;
411424 Grid . SetColumn ( bar , ordinal ) ;
412425 }
413426 else
414427 {
415- _barGrid . RowDefinitions . Add ( new RowDefinition { Height = length } ) ;
428+ row = new RowDefinition { Height = length } ;
429+ _barGrid . RowDefinitions . Add ( row ) ;
416430 Grid . SetRow ( bar , ordinal ) ;
417431 }
418432
419433 _barGrid . Children . Add ( bar ) ;
434+ _bars . Add ( new BarSlot ( segment , bar , column , row , from , target ) ) ;
420435
436+ if ( from != target ) changed = true ;
421437 if ( compositor is not null && easing is not null ) Reveal ( bar , ordinal , compositor , easing , horizontal ) ;
422438
423439 ordinal ++ ;
424440 }
425441
426- if ( ! full )
442+ _restColumn = null ;
443+ _restRow = null ;
444+
445+ if ( horizontal )
446+ {
447+ _restColumn = new ColumnDefinition ( ) ;
448+ _barGrid . ColumnDefinitions . Add ( _restColumn ) ;
449+ }
450+ else
427451 {
428- var rest = new GridLength ( 1 - used , GridUnitType . Star ) ;
429- if ( horizontal ) _barGrid . ColumnDefinitions . Add ( new ColumnDefinition { Width = rest } ) ;
430- else _barGrid . RowDefinitions . Add ( new RowDefinition { Height = rest } ) ;
452+ _restRow = new RowDefinition ( ) ;
453+ _barGrid . RowDefinitions . Add ( _restRow ) ;
431454 }
455+
456+ ApplyFractions ( ) ;
457+ return changed ;
458+ }
459+
460+ private void ApplyFractions ( )
461+ {
462+ double used = 0 ;
463+ int first = - 1 ;
464+ int last = - 1 ;
465+
466+ for ( int i = 0 ; i < _bars . Count ; i ++ )
467+ {
468+ double current = _bars [ i ] . Current ;
469+
470+ if ( current > 0 )
471+ {
472+ if ( first < 0 ) first = i ;
473+ last = i ;
474+ }
475+
476+ used += current ;
477+ }
478+
479+ bool full = used >= 1 - Tolerance ;
480+
481+ if ( first != _shapeFirst || last != _shapeLast || full != _shapeFull )
482+ {
483+ _shapeFirst = first ;
484+ _shapeLast = last ;
485+ _shapeFull = full ;
486+ ApplyShape ( first , last , full ) ;
487+ }
488+
489+ var rest = new GridLength ( Math . Max ( 0 , 1 - used ) , GridUnitType . Star ) ;
490+ if ( _restColumn is not null ) _restColumn . Width = rest ;
491+ if ( _restRow is not null ) _restRow . Height = rest ;
492+ }
493+
494+ private void ApplyShape ( int first , int last , bool full )
495+ {
496+ for ( int i = 0 ; i < _bars . Count ; i ++ )
497+ {
498+ var bar = _bars [ i ] . Element ;
499+ double start = i == first ? _barRadius : 0 ;
500+ double end = i == last && full ? _barRadius : 0 ;
501+ double trailingGap = i == last ? 0 : _barGap ;
502+
503+ if ( _barHorizontal )
504+ {
505+ bar . Margin = new Thickness ( 0 , 0 , trailingGap , 0 ) ;
506+ bar . CornerRadius = new CornerRadius ( start , end , end , start ) ;
507+ }
508+ else
509+ {
510+ bar . Margin = new Thickness ( 0 , 0 , 0 , trailingGap ) ;
511+ bar . CornerRadius = new CornerRadius ( start , start , end , end ) ;
512+ }
513+ }
514+ }
515+
516+ private void StartAnimation ( )
517+ {
518+ _animationStart = Stopwatch . GetTimestamp ( ) ;
519+ if ( _animating ) return ;
520+
521+ _animating = true ;
522+ Microsoft . UI . Xaml . Media . CompositionTarget . Rendering += _renderingHandler ;
523+ }
524+
525+ private void StopAnimation ( )
526+ {
527+ if ( ! _animating ) return ;
528+
529+ _animating = false ;
530+ Microsoft . UI . Xaml . Media . CompositionTarget . Rendering -= _renderingHandler ;
531+ }
532+
533+ private void OnRendering ( object ? sender , object e )
534+ {
535+ if ( ! Active )
536+ {
537+ StopAnimation ( ) ;
538+ return ;
539+ }
540+
541+ double progress = Math . Min ( 1 , Stopwatch . GetElapsedTime ( _animationStart ) . TotalMilliseconds / AnimationDuration ) ;
542+ double inverse = 1 - progress ;
543+ double eased = 1 - inverse * inverse * inverse ;
544+
545+ for ( int i = 0 ; i < _bars . Count ; i ++ )
546+ {
547+ var slot = _bars [ i ] ;
548+ if ( slot . From == slot . To ) continue ;
549+
550+ slot . Current = progress >= 1 ? slot . To : slot . From + ( slot . To - slot . From ) * eased ;
551+
552+ var length = new GridLength ( slot . Current , GridUnitType . Star ) ;
553+ if ( slot . Column is not null ) slot . Column . Width = length ;
554+ else if ( slot . Row is not null ) slot . Row . Height = length ;
555+ }
556+
557+ ApplyFractions ( ) ;
558+
559+ if ( progress >= 1 ) StopAnimation ( ) ;
432560 }
433561
434562 private bool BuildLegend ( )
0 commit comments