11using System ;
2+ using System ;
23using System . Collections . Generic ;
3- using System . Collections . ObjectModel ;
4- using System . Diagnostics ;
54using System . Linq ;
5+ using Avalonia ;
66using Avalonia . Controls ;
7+ using Avalonia . Controls . Primitives ;
78using Avalonia . Interactivity ;
9+ using Avalonia . Layout ;
10+ using Avalonia . Media ;
811using PlanViewer . Core . Models ;
912
1013namespace PlanViewer . App . Controls ;
1114
1215public partial class WaitStatsProfileControl : UserControl
1316{
14- private enum ViewMode { Bar , Ribbon , Table }
17+ private enum ViewMode { Bar , Ribbon }
1518
16- private ViewMode _viewMode = ViewMode . Bar ;
19+ private ViewMode _viewMode = ViewMode . Ribbon ;
1720 private bool _isCollapsed ;
1821 private WaitProfile ? _currentProfile ;
19- private readonly ObservableCollection < WaitTableRow > _tableRows = new ( ) ;
22+ private Popup ? _legendPopup ;
2023
2124 public event EventHandler < string > ? CategoryClicked ;
2225 public event EventHandler < string > ? CategoryDoubleClicked ;
2326 public event EventHandler < bool > ? CollapsedChanged ;
2427
2528 public bool IsCollapsed => _isCollapsed ;
2629
30+ // All known wait categories in the order they appear in the theme
31+ private static readonly string [ ] AllWaitCategories =
32+ [
33+ "Unknown" , "CPU" , "Worker Thread" , "Lock" , "Latch" , "Buffer Latch" ,
34+ "Buffer IO" , "Compilation" , "SQL CLR" , "Mirroring" , "Transaction" ,
35+ "Preemptive" , "Service Broker" , "Tran Log IO" , "Network IO" ,
36+ "Parallelism" , "Memory" , "Tracing" , "Full Text Search" ,
37+ "Other Disk IO" , "Replication" , "Log Rate Governor" , "Others"
38+ ] ;
39+
2740 public WaitStatsProfileControl ( )
2841 {
2942 InitializeComponent ( ) ;
30- TableGrid . ItemsSource = _tableRows ;
3143 GlobalBar . CategoryClicked += ( _ , cat ) => CategoryClicked ? . Invoke ( this , cat ) ;
3244 GlobalBar . CategoryDoubleClicked += ( _ , cat ) => CategoryDoubleClicked ? . Invoke ( this , cat ) ;
3345 GlobalRibbon . CategoryClicked += ( _ , cat ) => CategoryClicked ? . Invoke ( this , cat ) ;
@@ -38,7 +50,6 @@ public void SetBarProfile(WaitProfile? profile)
3850 {
3951 _currentProfile = profile ;
4052 GlobalBar . SetProfile ( profile ) ;
41- RefreshTableRows ( ) ;
4253 }
4354
4455 public void SetRibbonData ( List < WaitCategoryTimeSlice > data )
@@ -59,7 +70,7 @@ public void Expand()
5970 ContentArea . IsVisible = true ;
6071 TitleText . IsVisible = true ;
6172 ToggleChartButton . IsVisible = true ;
62- TableViewButton . IsVisible = true ;
73+ LegendButton . IsVisible = true ;
6374 CollapsedChanged ? . Invoke ( this , false ) ;
6475 }
6576
@@ -70,7 +81,7 @@ public void Collapse()
7081 ContentArea . IsVisible = false ;
7182 TitleText . IsVisible = false ;
7283 ToggleChartButton . IsVisible = false ;
73- TableViewButton . IsVisible = false ;
84+ LegendButton . IsVisible = false ;
7485 CollapsedChanged ? . Invoke ( this , true ) ;
7586 }
7687
@@ -81,57 +92,99 @@ public void SetLoading(bool isLoading)
8192
8293 private void ToggleChart_Click ( object ? sender , RoutedEventArgs e )
8394 {
84- // Cycle: Bar -> Ribbon -> Bar (skip table; table has its own button)
85- if ( _viewMode == ViewMode . Table )
86- {
87- // If in table mode, toggle goes back to bar
88- _viewMode = ViewMode . Bar ;
89- }
90- else
91- {
92- _viewMode = _viewMode == ViewMode . Bar ? ViewMode . Ribbon : ViewMode . Bar ;
93- }
94- ApplyViewMode ( ) ;
95- }
96-
97- private void TableView_Click ( object ? sender , RoutedEventArgs e )
98- {
99- _viewMode = _viewMode == ViewMode . Table ? ViewMode . Bar : ViewMode . Table ;
95+ _viewMode = _viewMode == ViewMode . Bar ? ViewMode . Ribbon : ViewMode . Bar ;
10096 ApplyViewMode ( ) ;
10197 }
10298
10399 private void ApplyViewMode ( )
104100 {
105101 GlobalBar . IsVisible = _viewMode == ViewMode . Bar ;
106102 GlobalRibbon . IsVisible = _viewMode == ViewMode . Ribbon ;
107- TableGrid . IsVisible = _viewMode == ViewMode . Table ;
108- ToggleChartButton . Content = _viewMode == ViewMode . Ribbon ? "▤" : "☰" ;
109-
110- // The ContentArea lives inside an Auto-sized parent row, so a *-row
111- // DataGrid would collapse to zero height. Give an explicit height
112- // when in table mode; reset to NaN (auto) for chart modes.
113- ContentArea . Height = _viewMode == ViewMode . Table ? 120 : double . NaN ;
103+ ToggleChartButton . Content = _viewMode == ViewMode . Ribbon ? "☰" : "▤" ;
114104 }
115105
116- private void RefreshTableRows ( )
106+ private void Legend_Click ( object ? sender , RoutedEventArgs e )
117107 {
118- _tableRows . Clear ( ) ;
119- if ( _currentProfile == null ) { return ; }
120- foreach ( var seg in _currentProfile . Segments . OrderByDescending ( s => s . Ratio ) )
108+ if ( _legendPopup != null )
121109 {
122- _tableRows . Add ( new WaitTableRow
110+ _legendPopup . IsOpen = ! _legendPopup . IsOpen ;
111+ return ;
112+ }
113+
114+ var grid = new Grid { ColumnDefinitions = new ColumnDefinitions ( "Auto,Auto" ) } ;
115+ for ( int i = 0 ; i < AllWaitCategories . Length ; i ++ )
116+ grid . RowDefinitions . Add ( new RowDefinition ( GridLength . Auto ) ) ;
117+
118+ for ( int i = 0 ; i < AllWaitCategories . Length ; i ++ )
119+ {
120+ var cat = AllWaitCategories [ i ] ;
121+ var brush = TryFindBrush ( $ "WaitCategory.{ cat } ", new SolidColorBrush ( Color . Parse ( "#555D66" ) ) ) ;
122+
123+ var swatch = new Border
123124 {
124- Category = seg . Category ,
125- WaitRatioText = seg . WaitRatio . ToString ( "P2" ) ,
126- RatioText = seg . Ratio . ToString ( "P1" )
127- } ) ;
125+ Width = 14 ,
126+ Height = 14 ,
127+ Background = brush ,
128+ CornerRadius = new CornerRadius ( 2 ) ,
129+ Margin = new Thickness ( 4 , 2 ) ,
130+ VerticalAlignment = VerticalAlignment . Center ,
131+ } ;
132+ Grid . SetRow ( swatch , i ) ;
133+ Grid . SetColumn ( swatch , 0 ) ;
134+ grid . Children . Add ( swatch ) ;
135+
136+ var label = new TextBlock
137+ {
138+ Text = cat ,
139+ FontSize = 11 ,
140+ VerticalAlignment = VerticalAlignment . Center ,
141+ Margin = new Thickness ( 4 , 2 , 8 , 2 ) ,
142+ } ;
143+ if ( this . TryFindResource ( "ForegroundBrush" , this . ActualThemeVariant , out var fg ) && fg is IBrush fgBrush )
144+ label . Foreground = fgBrush ;
145+ Grid . SetRow ( label , i ) ;
146+ Grid . SetColumn ( label , 1 ) ;
147+ grid . Children . Add ( label ) ;
128148 }
149+
150+ var scroll = new ScrollViewer
151+ {
152+ Content = grid ,
153+ MaxHeight = 300 ,
154+ VerticalScrollBarVisibility = Avalonia . Controls . Primitives . ScrollBarVisibility . Auto ,
155+ } ;
156+
157+ var bgBrush = TryFindBrush ( "BackgroundLightBrush" , new SolidColorBrush ( Color . Parse ( "#22252D" ) ) ) ;
158+ var borderBrush = TryFindBrush ( "BorderBrush" , new SolidColorBrush ( Color . Parse ( "#3A3D45" ) ) ) ;
159+ var container = new Border
160+ {
161+ Background = bgBrush ,
162+ BorderBrush = borderBrush ,
163+ BorderThickness = new Thickness ( 1 ) ,
164+ CornerRadius = new CornerRadius ( 6 ) ,
165+ Padding = new Thickness ( 4 ) ,
166+ Child = scroll ,
167+ } ;
168+
169+ _legendPopup = new Popup
170+ {
171+ Child = container ,
172+ IsLightDismissEnabled = true ,
173+ Placement = PlacementMode . Bottom ,
174+ PlacementTarget = LegendButton ,
175+ } ;
176+
177+ // Add to visual tree so DynamicResources resolve
178+ if ( this . Content is Grid rootGrid )
179+ rootGrid . Children . Add ( _legendPopup ) ;
180+
181+ _legendPopup . IsOpen = true ;
129182 }
130- }
131183
132- public class WaitTableRow
133- {
134- public string Category { get ; set ; } = "" ;
135- public string WaitRatioText { get ; set ; } = "" ;
136- public string RatioText { get ; set ; } = "" ;
184+ private IBrush TryFindBrush ( string key , IBrush fallback )
185+ {
186+ if ( this . TryFindResource ( key , this . ActualThemeVariant , out var resource ) && resource is IBrush brush )
187+ return brush ;
188+ return fallback ;
189+ }
137190}
0 commit comments