1
1
// Licensed to the .NET Foundation under one or more agreements.
2
2
// The .NET Foundation licenses this file to you under the MIT license.
3
3
4
+ using CommunityToolkit . WinUI ;
5
+ using Microsoft . UI . Input ;
4
6
using Microsoft . UI . Xaml . Automation . Peers ;
7
+ using Microsoft . UI . Xaml . Input ;
8
+ using Microsoft . UI . Xaml . Media ;
9
+ using Windows . Foundation ;
5
10
6
11
namespace Files . App . Controls
7
12
{
@@ -11,7 +16,14 @@ namespace Files.App.Controls
11
16
[ TemplatePart ( Name = "CloseButton" , Type = typeof ( Button ) ) ]
12
17
public partial class BladeItem : ContentControl
13
18
{
19
+ private const double MINIMUM_WIDTH = 150 ;
20
+ private const double DEFAULT_WIDTH = 300 ; // Default width for the blade item
21
+
14
22
private Button _closeButton ;
23
+ private Border _bladeResizer ;
24
+ private bool _draggingSidebarResizer ;
25
+ private double _preManipulationSidebarWidth = 0 ;
26
+
15
27
/// <summary>
16
28
/// Initializes a new instance of the <see cref="BladeItem"/> class.
17
29
/// </summary>
@@ -36,7 +48,31 @@ protected override void OnApplyTemplate()
36
48
37
49
_closeButton . Click -= CloseButton_Click ;
38
50
_closeButton . Click += CloseButton_Click ;
51
+
52
+ _bladeResizer = GetTemplateChild ( "BladeResizer" ) as Border ;
53
+
54
+ if ( _bladeResizer != null )
55
+ {
56
+ _bladeResizer . ManipulationStarted -= BladeResizer_ManipulationStarted ;
57
+ _bladeResizer . ManipulationStarted += BladeResizer_ManipulationStarted ;
58
+
59
+ _bladeResizer . ManipulationDelta -= BladeResizer_ManipulationDelta ;
60
+ _bladeResizer . ManipulationDelta += BladeResizer_ManipulationDelta ;
61
+
62
+ _bladeResizer . ManipulationCompleted -= BladeResizer_ManipulationCompleted ;
63
+ _bladeResizer . ManipulationCompleted += BladeResizer_ManipulationCompleted ;
64
+
65
+ _bladeResizer . PointerEntered -= BladeResizer_PointerEntered ;
66
+ _bladeResizer . PointerEntered += BladeResizer_PointerEntered ;
67
+
68
+ _bladeResizer . PointerExited -= BladeResizer_PointerExited ;
69
+ _bladeResizer . PointerExited += BladeResizer_PointerExited ;
70
+
71
+ _bladeResizer . DoubleTapped -= BladeResizer_DoubleTapped ;
72
+ _bladeResizer . DoubleTapped += BladeResizer_DoubleTapped ;
73
+ }
39
74
}
75
+
40
76
/// <summary>
41
77
/// Creates AutomationPeer (<see cref="UIElement.OnCreateAutomationPeer"/>)
42
78
/// </summary>
@@ -50,5 +86,166 @@ private void CloseButton_Click(object sender, RoutedEventArgs e)
50
86
{
51
87
IsOpen = false ;
52
88
}
89
+
90
+ private void BladeResizer_ManipulationStarted ( object sender , ManipulationStartedRoutedEventArgs e )
91
+ {
92
+ _draggingSidebarResizer = true ;
93
+ _preManipulationSidebarWidth = ActualWidth ;
94
+ VisualStateManager . GoToState ( this , "ResizerPressed" , true ) ;
95
+ e . Handled = true ;
96
+ }
97
+
98
+ private void BladeResizer_ManipulationDelta ( object sender , ManipulationDeltaRoutedEventArgs e )
99
+ {
100
+ var newWidth = _preManipulationSidebarWidth + e . Cumulative . Translation . X ;
101
+ if ( newWidth < MINIMUM_WIDTH )
102
+ newWidth = MINIMUM_WIDTH ;
103
+
104
+ Width = newWidth ;
105
+ e . Handled = true ;
106
+ }
107
+
108
+ private void BladeResizer_ManipulationCompleted ( object sender , ManipulationCompletedRoutedEventArgs e )
109
+ {
110
+ _draggingSidebarResizer = false ;
111
+ VisualStateManager . GoToState ( this , "ResizerNormal" , true ) ;
112
+ e . Handled = true ;
113
+ }
114
+
115
+ private void BladeResizer_DoubleTapped ( object sender , DoubleTappedRoutedEventArgs e )
116
+ {
117
+ var optimalWidth = CalculateOptimalWidth ( ) ;
118
+ if ( optimalWidth > 0 )
119
+ {
120
+ Width = Math . Max ( optimalWidth , MINIMUM_WIDTH ) ;
121
+ }
122
+ else
123
+ {
124
+ // Fallback to default width if calculation fails
125
+ Width = DEFAULT_WIDTH ;
126
+ }
127
+
128
+ e . Handled = true ;
129
+ }
130
+
131
+ private double CalculateOptimalWidth ( )
132
+ {
133
+ try
134
+ {
135
+ // Look for any ListView within this BladeItem that contains text content
136
+ var listView = this . FindDescendant < ListView > ( ) ;
137
+ if ( listView ? . Items == null || ! listView . Items . Any ( ) )
138
+ return 0 ;
139
+
140
+ // Calculate the maximum width needed by measuring text content
141
+ var maxTextWidth = MeasureContentWidth ( listView ) ;
142
+
143
+ // Add padding for icon, margins, and other UI elements
144
+ // Icon width (32) + margins (24) + padding (24) + chevron/tags (40) = 120
145
+ var totalPadding = 120 ;
146
+
147
+ return maxTextWidth + totalPadding ;
148
+ }
149
+ catch ( Exception )
150
+ {
151
+ return 0 ;
152
+ }
153
+ }
154
+
155
+ private double MeasureContentWidth ( ListView listView )
156
+ {
157
+ try
158
+ {
159
+ double maxWidth = 0 ;
160
+
161
+ // Find all TextBlocks in the ListView using visual tree walking
162
+ var textBlocks = GetTextBlocksFromVisualTree ( listView ) ;
163
+
164
+ if ( textBlocks . Any ( ) )
165
+ {
166
+ // Measure each TextBlock and find the widest one
167
+ foreach ( var textBlock in textBlocks )
168
+ {
169
+ if ( string . IsNullOrEmpty ( textBlock . Text ) )
170
+ continue ;
171
+
172
+ // Create a measuring TextBlock with the same properties
173
+ var measuringBlock = new TextBlock
174
+ {
175
+ Text = textBlock . Text ,
176
+ FontSize = textBlock . FontSize ,
177
+ FontFamily = textBlock . FontFamily ,
178
+ FontWeight = textBlock . FontWeight ,
179
+ FontStyle = textBlock . FontStyle
180
+ } ;
181
+
182
+ measuringBlock . Measure ( new Size ( double . PositiveInfinity , double . PositiveInfinity ) ) ;
183
+ maxWidth = Math . Max ( maxWidth , measuringBlock . DesiredSize . Width ) ;
184
+ }
185
+ }
186
+ else
187
+ {
188
+ // Fallback: estimate based on item count and average text width
189
+ var itemCount = listView . Items . Count ;
190
+ if ( itemCount > 0 )
191
+ {
192
+ // Estimate average filename length and multiply by character width
193
+ var estimatedCharWidth = 8 ; // Approximate pixel width per character
194
+ var estimatedMaxLength = Math . Min ( 50 , Math . Max ( 20 , itemCount * 2 ) ) ; // Heuristic
195
+ maxWidth = estimatedCharWidth * estimatedMaxLength ;
196
+ }
197
+ }
198
+
199
+ return maxWidth ;
200
+ }
201
+ catch ( Exception )
202
+ {
203
+ // Fallback calculation
204
+ return 200 ; // Default reasonable width
205
+ }
206
+ }
207
+
208
+ private List < TextBlock > GetTextBlocksFromVisualTree ( DependencyObject parent )
209
+ {
210
+ var textBlocks = new List < TextBlock > ( ) ;
211
+
212
+ if ( parent == null )
213
+ return textBlocks ;
214
+
215
+ var childrenCount = VisualTreeHelper . GetChildrenCount ( parent ) ;
216
+ for ( int i = 0 ; i < childrenCount ; i ++ )
217
+ {
218
+ var child = VisualTreeHelper . GetChild ( parent , i ) ;
219
+
220
+ if ( child is TextBlock textBlock )
221
+ {
222
+ textBlocks . Add ( textBlock ) ;
223
+ }
224
+
225
+ // Recursively search child elements
226
+ textBlocks . AddRange ( GetTextBlocksFromVisualTree ( child ) ) ;
227
+ }
228
+
229
+ return textBlocks ;
230
+ }
231
+
232
+ private void BladeResizer_PointerEntered ( object sender , PointerRoutedEventArgs e )
233
+ {
234
+ var sidebarResizer = ( FrameworkElement ) sender ;
235
+ sidebarResizer . ChangeCursor ( InputSystemCursor . Create ( InputSystemCursorShape . SizeWestEast ) ) ;
236
+ VisualStateManager . GoToState ( this , "ResizerPointerOver" , true ) ;
237
+ e . Handled = true ;
238
+ }
239
+
240
+ private void BladeResizer_PointerExited ( object sender , PointerRoutedEventArgs e )
241
+ {
242
+ if ( _draggingSidebarResizer )
243
+ return ;
244
+
245
+ var sidebarResizer = ( FrameworkElement ) sender ;
246
+ sidebarResizer . ChangeCursor ( InputSystemCursor . Create ( InputSystemCursorShape . Arrow ) ) ;
247
+ VisualStateManager . GoToState ( this , "ResizerNormal" , true ) ;
248
+ e . Handled = true ;
249
+ }
53
250
}
54
251
}
0 commit comments