17 private const int DefaultDropDownHeight = 144;
19 private readonly DropDownChevronDrawing _chevron;
20 private bool _isDropDownOpen;
21 private bool _disposed;
22 private string _placeholder =
"Select...";
41 IEnumerable<string>? items =
null,
42 int dropDownHeight = DefaultDropDownHeight,
43 string? nickname =
null)
46 ArgumentNullException.ThrowIfNull(view);
47 ValidateDropDownHeight(dropDownHeight);
49 Header =
new ButtonWidget(renderSurfaceHost, view, bounds,
string.Empty, $
"{Nickname}.header");
52 new Rectangle(bounds.X, bounds.Bottom, bounds.Width, dropDownHeight),
55 _chevron =
new DropDownChevronDrawing(
58 ResolveChevronBounds(bounds),
59 $
"{Nickname}.chevron");
61 CompleteInitialization(bounds);
76 IEnumerable<string>? items =
null,
77 int dropDownHeight = DefaultDropDownHeight,
78 string? nickname =
null)
81 ArgumentNullException.ThrowIfNull(sceneLayer);
82 ValidateDropDownHeight(dropDownHeight);
84 Header =
new ButtonWidget(renderSurfaceHost, sceneLayer, bounds,
string.Empty, $
"{Nickname}.header");
87 new Rectangle(bounds.X, bounds.Bottom, bounds.Width, dropDownHeight),
90 _chevron =
new DropDownChevronDrawing(
93 ResolveChevronBounds(bounds),
94 $
"{Nickname}.chevron");
96 CompleteInitialization(bounds);
125 set => DropDown.SelectedIndex = value;
141 _placeholder = value ??
string.Empty;
175 if (_isDropDownOpen ||
Items.Count == 0)
178 _isDropDownOpen =
true;
181 WidgetInputRouterRegistry.TryFocus(
DropDown);
191 CollapseDropDown(restoreHeaderFocus:
true);
211 Header.SetButtonZOrder(zOrder);
212 _chevron.ZOrder = zOrder + 2;
213 DropDown.SetListBoxZOrder(zOrder + 10);
224 Header.SetTextColor(color);
225 _chevron.SetColor(color);
236 Header.Clicked -= OnHeaderClicked;
237 DropDown.SelectedIndexChanged -= OnSelectedIndexChanged;
238 DropDown.SelectionCommitted -= OnDropDownSelectionCommitted;
239 DropDown.FocusLost -= OnDropDownFocusLost;
248 if (!_isDropDownOpen)
255 _isDropDownOpen =
false;
256 base.ProcessHidden();
259 private void CompleteInitialization(Rectangle bounds)
261 ConfigureHeaderContentBounds(bounds);
266 Header.Clicked += OnHeaderClicked;
267 DropDown.SelectedIndexChanged += OnSelectedIndexChanged;
268 DropDown.SelectionCommitted += OnDropDownSelectionCommitted;
269 DropDown.FocusLost += OnDropDownFocusLost;
281 private void OnHeaderClicked()
286 private void OnSelectedIndexChanged(
int index)
292 private void OnDropDownSelectionCommitted(
int index)
298 private void OnDropDownFocusLost()
300 if (!_isDropDownOpen)
308 if (ReferenceEquals(focusedWidget,
Header) || ReferenceEquals(focusedWidget,
DropDown))
311 CollapseDropDown(restoreHeaderFocus:
false);
314 private void CollapseDropDown(
bool restoreHeaderFocus)
316 if (!_isDropDownOpen)
319 _isDropDownOpen =
false;
322 if (!restoreHeaderFocus)
326 WidgetInputRouterRegistry.TryFocus(
Header);
329 private void RefreshHeaderText()
334 private void ConfigureHeaderContentBounds(Rectangle bounds)
339 Math.Max(1, bounds.Width - 28),
343 Header.Label.ScreenBounds = labelBounds;
345 Header.Label.WorldBounds = labelBounds;
348 private static Rectangle ResolveChevronBounds(Rectangle bounds)
350 const int width = 18;
352 Math.Max(bounds.Left, bounds.Right - width - 4),
354 Math.Min(width, bounds.Width),
358 private static Rectangle ValidateBounds(Rectangle bounds)
360 if (bounds.Width < 60 || bounds.Height < 20)
361 throw new ArgumentOutOfRangeException(nameof(bounds), bounds,
"Combo-box bounds are too small.");
366 private static void ValidateDropDownHeight(
int height)
369 throw new ArgumentOutOfRangeException(nameof(height),
"Drop-down height must be at least 28 pixels.");
372 private sealed class DropDownChevronDrawing : DirectDrawingMovableBase
374 private SKColor _color = SKColors.White;
376 internal DropDownChevronDrawing(
377 RenderSurfaceHostBase host,
385 internal DropDownChevronDrawing(
386 RenderSurfaceHostBase host,
394 internal void SetColor(Color color)
396 _color =
new SKColor(color.R, color.G, color.B, color.A);
401 protected override void OnDraw(BackbufferBase backbuffer, RectangleF destRectScreen)
403 float width = Math.Min(8f, destRectScreen.Width - 4f);
404 float centerX = destRectScreen.Left + destRectScreen.Width / 2f;
405 float centerY = destRectScreen.Top + destRectScreen.Height / 2f;
407 using var paint =
new SKPaint
411 Style = SKPaintStyle.Stroke,
413 StrokeCap = SKStrokeCap.Round,
414 StrokeJoin = SKStrokeJoin.Round
416 using var path =
new SKPath();
418 path.MoveTo(centerX - width / 2f, centerY - 2f);
419 path.LineTo(centerX, centerY + 2f);
420 path.LineTo(centerX + width / 2f, centerY - 2f);
422 backbuffer.
Canvas.DrawPath(path, paint);
SceneLayer? SceneLayer
Gets the scene layer shared by all children in scene-layer mode.
View? View
Gets the view shared by all children in view mode.
DirectDrawingMode Mode
Gets the coordinate mode shared by the composite and its children.
View? View
Gets the view to which this direct drawing is attached, or null if the drawing is in scene-layer mode...
SceneLayer? SceneLayer
Gets the scene layer to which this direct drawing is attached, or null if the drawing is in view mode...
SKCanvas Canvas
Gets the SkiaSharp canvas used for drawing operations on this backbuffer.
Represents a base class for hosting a render surface, providing functionality for managing rendering ...
@ Rectangle
Particles are spawned uniformly within a rectangular area. This is the default distribution.
DirectDrawingMode
Specifies where direct drawing operations are applied within the rendering pipeline.