Gondwana Game Engine Development (master) - 2.6.1
Gondwana is a cross-platform 2.5D game and rendering engine written in C#/.NET 8. It provides fine-grained control over rendering, timing, and scene composition, with built-in support for parallax, z-ordering, pixel overhang, collision detection, and particle effects. Gondwana targets desktop and web platforms using SkiaSharp for graphics and pluggable audio backends for desktop and browser targets.
Loading...
Searching...
No Matches
MenuDropDownWidget.cs
1using System.Drawing;
2using System.Numerics;
6using SkiaSharp;
7
9
13public sealed class MenuDropDownWidget : ContainerWidget
14{
15 private readonly MenuBarTheme _theme;
16 private readonly MenuBarWidget _bar;
17 private int _zOrder;
18 private int _minimumWidth;
19 internal MenuItemWidget? ParentItem { get; private set; }
20 internal MenuDropDownWidget? ParentMenu { get; private set; }
21 internal MenuDropDownWidget? OpenChild { get; private set; }
22 internal MenuDropDownWidget ActiveMenu => OpenChild?.ActiveMenu ?? this;
23 internal MenuItemWidget? SelectedItem => _selectedIndex >= 0 && _selectedIndex < _items.Count ? _items[_selectedIndex] : null;
24 internal bool CanInvoke => !_disposed && _bar.IsAvailable && (ParentItem?.CanInvoke ?? true);
25 private readonly List<Entry> _entries = new();
26 private readonly List<MenuItemWidget> _items = new();
27
28 private EventHandler<DirectDrawingBase>? _closeCompletionHandler;
29 private int _nextChildNicknameSuffix;
30 private int _selectedIndex = -1;
31 private bool _disposed;
32
33 internal MenuDropDownWidget(RenderSurfaceHostBase host,
34 View view,
35 Point location,
36 MenuBarTheme theme,
37 string? nickname,
38 MenuBarWidget bar)
39 : base(host, DirectDrawingMode.View, location, nickname)
40 {
41 _bar = bar;
42 _theme = theme ?? throw new ArgumentNullException(nameof(theme));
43 _minimumWidth = Width = Math.Max(_theme.MinimumDropDownWidth, _theme.DefaultDropDownWidth);
44
46 _theme.DropDownBackgroundColor,
47 host,
48 view,
49 new Rectangle(location.X, location.Y, Width, 1),
50 $"{Nickname}.panel")
51 .SetFilled(true)
52 .SetBorderColor(_theme.DropDownBorderColor)
53 .SetStrokeWidth(_theme.BorderWidth)
54 .SetCornerRadius(_theme.CornerRadius);
55
56 Panel.HideWhenFullyTransparent = false;
57 Add(Panel);
58
61 CanReceiveFocus = false;
62
63 RecalculateLayout();
64 Hide();
65 }
66
68 public DirectRectangle Panel { get; }
69
71 public IReadOnlyList<MenuItemWidget> Items => _items.AsReadOnly();
72
74 public int Width { get; private set; }
75
77 public int Height { get; private set; }
78
80 public bool IsOpen { get; private set; }
81
83 public int SelectedIndex => _selectedIndex;
84
95 public MenuDropDownWidget AddItem(string text, Action? action = null,
96 string? shortcutText = null, bool enabled = true, string? key = null,
97 KeyGesture? shortcut = null, char? mnemonic = null, SKImage? icon = null)
98 {
99 AddEntry(text, action, shortcutText, enabled, key, shortcut, mnemonic, icon);
100 return this;
101 }
102
113 public MenuDropDownWidget AddCheckItem(string text, Action<bool>? action = null,
114 bool isChecked = false, bool enabled = true, string? key = null,
115 KeyGesture? shortcut = null, char? mnemonic = null, SKImage? icon = null)
116 {
117 AddEntry(text, null, null, enabled, key, shortcut, mnemonic, icon,
118 checkable: true, checkedAction: action).SetChecked(isChecked);
119 return this;
120 }
121
133 public MenuDropDownWidget AddRadioItem(string text, Action? action, string group,
134 bool isChecked = false, bool enabled = true, string? key = null,
135 KeyGesture? shortcut = null, char? mnemonic = null, SKImage? icon = null)
136 {
137 ArgumentException.ThrowIfNullOrWhiteSpace(group);
138 AddEntry(text, action, null, enabled, key, shortcut, mnemonic, icon,
139 checkable: true, radioGroup: group).SetChecked(isChecked);
140 return this;
141 }
142
151 public MenuDropDownWidget AddSubMenu(string text, Action<MenuDropDownWidget> configure,
152 bool enabled = true, string? key = null, char? mnemonic = null, SKImage? icon = null)
153 {
154 ArgumentNullException.ThrowIfNull(configure);
155 ValidateEntry(text, key, null, mnemonic);
156 var child = new MenuDropDownWidget(RenderSurfaceHost, View!, Point.Empty, _theme,
157 CreateChildNickname("submenu"), _bar);
158 try
159 {
160 configure(child);
161 var item = AddEntry(text, null, null, enabled, key, null, mnemonic, icon, subMenu: child);
162 child.ParentMenu = this;
163 child.ParentItem = item;
164 Add(child, Vector2.Zero);
165 child.CloseAnimated(MenuDropDownAnimation.None, 0, immediate: true);
166 child.SetDropDownZOrder(_zOrder + 10);
167 }
168 catch
169 {
170 child.Dispose();
171 throw;
172 }
173 return this;
174 }
175
176 private void ValidateEntry(string text, string? key, KeyGesture? shortcut, char? mnemonic)
177 {
178 ObjectDisposedException.ThrowIf(_disposed, this);
179 ArgumentNullException.ThrowIfNull(text);
180 _bar.ValidateRegistration(key, shortcut);
181 if (mnemonic is char character && _items.Any(item =>
182 item.Mnemonic is char existing && char.ToUpperInvariant(existing) == char.ToUpperInvariant(character)))
183 throw new ArgumentException($"Duplicate menu mnemonic '{character}'.", nameof(mnemonic));
184 }
185
186 private MenuItemWidget AddEntry(string text, Action? action, string? shortcutText,
187 bool enabled, string? key, KeyGesture? shortcut, char? mnemonic, SKImage? icon,
188 bool checkable = false, string? radioGroup = null, Action<bool>? checkedAction = null,
189 MenuDropDownWidget? subMenu = null)
190 {
191 ValidateEntry(text, key, shortcut, mnemonic);
192 var anchor = Point.Round(new PointF(GetPosition().X, GetPosition().Y));
193 var item = new MenuItemWidget(RenderSurfaceHost, View!,
194 new Rectangle(anchor.X, anchor.Y, Width, _theme.ItemHeight), text, shortcutText,
195 action, _theme, CreateChildNickname("item"), this, key, shortcut, mnemonic,
196 icon, checkable, radioGroup, checkedAction, subMenu);
197 item.SetEnabled(enabled);
198 item.Hovered += OnItemHovered;
199 _items.Add(item);
200 _entries.Add(Entry.ForItem(item));
201 Add(item, Vector2.Zero);
202 _bar.RegisterItem(item);
203 if (!IsOpen)
204 {
205 item.ApplyInputState(false);
206 item.Hide();
207 }
208 RecalculateLayout();
209 SetDropDownZOrder(_zOrder);
210 return item;
211 }
212
213 internal void UncheckRadioPeers(MenuItemWidget selected)
214 {
215 foreach (var item in _items)
216 if (!ReferenceEquals(item, selected) && item.RadioGroup == selected.RadioGroup && item.IsChecked)
217 item.SetChecked(false);
218 }
219
220 private string CreateChildNickname(string kind) => $"{Nickname}.{kind}.{_nextChildNicknameSuffix++}";
221
222 internal void UnregisterItem(MenuItemWidget item)
223 {
224 _bar.UnregisterItem(item);
225 if (_disposed) return;
226 OnItemDisabled(item);
227 item.SubMenu?.Dispose();
228 item.Hovered -= OnItemHovered;
229 _items.Remove(item);
230 _entries.RemoveAll(entry => ReferenceEquals(entry.Item, item));
231 _selectedIndex = -1;
232 foreach (var remaining in _items) remaining.SetSelected(false);
233 RecalculateLayout();
234 }
235
236 internal void OnItemDisabled(MenuItemWidget item)
237 {
238 if (item.SubMenu is not null && ReferenceEquals(OpenChild, item.SubMenu)) CloseChild();
239 if (ReferenceEquals(SelectedItem, item)) SetSelectedIndex(-1);
240 }
241
242 internal void NotifyInvoked(MenuItemWidget item) => _bar.OnItemInvoked(item);
243
244 internal void OpenSubMenu(MenuItemWidget item, bool selectFirst)
245 {
246 if (!IsOpen || !item.CanInvoke || item.SubMenu is not { } child) return;
247 SetSelectedIndex(_items.IndexOf(item));
248 if (!ReferenceEquals(OpenChild, child))
249 {
250 CloseChild();
251 var viewport = View!.Viewport.TargetRectPx;
252 var row = item.Background.ScreenBounds;
253 int x = Panel.ScreenBounds.Right + _theme.SubMenuGap;
254 if (x + child.Width > viewport.Right)
255 x = Panel.ScreenBounds.Left - child.Width - _theme.SubMenuGap;
256 x = Math.Clamp(x, viewport.Left, Math.Max(viewport.Left, viewport.Right - child.Width));
257 int y = Math.Clamp(row.Top, viewport.Top, Math.Max(viewport.Top, viewport.Bottom - child.Height));
258 SetLocalOffset(child, new Vector2(x - GetPosition().X, y - GetPosition().Y));
259 OpenChild = child;
260 child.OpenAnimated(_bar.DropDownAnimation, Math.Max(0, _bar.DropDownAnimationDurationSec));
261 child.Activate();
262 }
263 if (selectFirst) child.SelectFirstEnabled();
264 }
265
266 internal void CloseChild()
267 {
268 var child = OpenChild;
269 OpenChild = null;
270 child?.CloseAnimated(MenuDropDownAnimation.None, 0, immediate: true);
271 }
272
274 protected override void ProcessShown()
275 {
276 base.ProcessShown();
277 foreach (var item in _items)
278 item.SubMenu?.CloseAnimated(MenuDropDownAnimation.None, 0, immediate: true);
279 }
280
282 protected override void ProcessHidden()
283 {
284 CloseChild();
285 IsOpen = false;
286 SetEntryInputEnabled(false);
287 CancelPendingClose();
288 CancelVisualFades();
289 base.ProcessHidden();
290 }
291
294 public MenuDropDownWidget AddSeparator()
295 {
296 ObjectDisposedException.ThrowIf(_disposed, this);
297 Point anchor = Point.Round(new PointF(GetPosition().X, GetPosition().Y));
298 var separator = new DirectRectangle(
299 _theme.SeparatorColor,
301 View!,
302 new Rectangle(anchor.X, anchor.Y, 1, 1),
303 CreateChildNickname("separator"))
304 .SetFilled(true)
305 .SetStrokeWidth(0f);
306
307 separator.HideWhenFullyTransparent = false;
308 _entries.Add(Entry.ForSeparator(separator));
309 Add(separator, keepCurrentOffset: false, explicitLocalOffsetPx: Vector2.Zero);
310 separator.Visible = IsOpen;
311 separator.ZOrder = _zOrder + 2;
312
313 RecalculateLayout();
314 return this;
315 }
316
320 public MenuDropDownWidget SetWidth(int width)
321 {
322 ObjectDisposedException.ThrowIf(_disposed, this);
323 if (width < _theme.MinimumDropDownWidth)
324 {
325 throw new ArgumentOutOfRangeException(
326 nameof(width),
327 width,
328 $"Menu dropdown width must be at least {_theme.MinimumDropDownWidth} pixels.");
329 }
330
331 _minimumWidth = width;
332 RecalculateLayout();
333 return this;
334 }
335
336 internal void OpenAnimated(MenuDropDownAnimation animation,
337 float durationSec)
338 {
339 CancelPendingClose();
340
341 if (!Visible)
342 Show();
343
345 IsOpen = true;
346 SetEntryInputEnabled(true);
347
348 PrepareVisualsForAnimation();
349
350 if (animation == MenuDropDownAnimation.None || durationSec <= 0f)
351 {
352 SetOpacity(1f);
353 SetRevealProgress(1f);
354 return;
355 }
356
357 SetOpacity(0f);
358 SetRevealProgress(animation == MenuDropDownAnimation.FadeAndReveal ? 0f : 1f);
359 foreach (var visual in EnumerateVisuals()) visual.FadeIn(durationSec);
360
361 if (animation == MenuDropDownAnimation.FadeAndReveal)
362 {
363 foreach (DirectDrawingBase visual in EnumerateVisuals())
364 {
366 .RevealTo(1f, durationSec, EaseOutCubic);
367 }
368 }
369 }
370
371 internal void CloseAnimated(MenuDropDownAnimation animation,
372 float durationSec,
373 bool immediate = false)
374 {
375 CloseChild();
376 IsOpen = false;
377 IsPointerInputEnabled = false;
378 SetEntryInputEnabled(false);
379 SetSelectedIndex(-1);
380 CancelPendingClose();
381
382 if (immediate || animation == MenuDropDownAnimation.None || durationSec <= 0f)
383 {
384 CancelVisualFades();
385 SetOpacity(1f);
386 SetRevealProgress(1f);
387
388 if (Visible)
389 Hide();
390
391 return;
392 }
393
394 _closeCompletionHandler = (_, _) =>
395 {
396 CancelPendingClose();
397
398 if (Visible)
399 Hide();
400 };
401
402 Panel.FadeToCompleted += _closeCompletionHandler;
403
404 // Closing is a clean fade. Any in-progress opening reveal is snapped
405 // fully open so it cannot continue unfolding while disappearing.
406 SetRevealProgress(1f);
407 foreach (var visual in EnumerateVisuals()) visual.FadeOut(durationSec);
408 }
409
410 internal void SetDropDownZOrder(int zOrder)
411 {
412 _zOrder = zOrder;
413 Panel.ZOrder = zOrder;
414 foreach (var item in _items) item.SubMenu?.SetDropDownZOrder(zOrder + 10);
415
416 foreach (Entry entry in _entries)
417 {
418 if (entry.Item is not null)
419 entry.Item.SetItemZOrder(zOrder + 1);
420 else if (entry.Separator is not null)
421 entry.Separator.ZOrder = zOrder + 2;
422 }
423 }
424
425 internal void SelectFirstEnabled()
426 {
427 SetSelectedIndex(FindNextEnabledIndex(-1, 1));
428 }
429
430 internal void SelectNextEnabled()
431 {
432 SetSelectedIndex(FindNextEnabledIndex(_selectedIndex, 1));
433 }
434
435 internal void SelectPreviousEnabled()
436 {
437 int start = _selectedIndex < 0 ? _items.Count : _selectedIndex;
438 SetSelectedIndex(FindNextEnabledIndex(start, -1));
439 }
440
441 internal void InvokeSelectedItem()
442 {
443 if (_selectedIndex < 0 || _selectedIndex >= _items.Count)
444 return;
445
446 _items[_selectedIndex].PerformClick();
447 }
448
450 public override void Dispose()
451 {
452 if (_disposed)
453 return;
454
455 _disposed = true;
456 CloseChild();
457 CancelPendingClose();
458 CancelVisualFades();
459 IsOpen = false;
460
461 foreach (MenuItemWidget item in _items)
462 {
463 item.Hovered -= OnItemHovered;
464 }
465
466 base.Dispose();
467 }
468
469 private void RecalculateLayout()
470 {
471 int markerWidth = _items.Any(item => item.IsCheckable) ? _theme.MarkerColumnWidth : 0;
472 int iconWidth = _items.Any(item => item.Icon is not null) ? _theme.IconSize + _theme.IconGap : 0;
473 int arrowWidth = _items.Any(item => item.SubMenu is not null) ? _theme.SubMenuArrowWidth : 0;
474 int shortcutWidth = _items.Select(item => MeasureText(item.ShortcutText)).DefaultIfEmpty().Max();
475 int labelWidth = _items.Select(item => MeasureText(item.Text)).DefaultIfEmpty().Max();
476 Width = Math.Max(_minimumWidth, markerWidth + iconWidth + arrowWidth + shortcutWidth + labelWidth +
477 (shortcutWidth > 0 ? _theme.ShortcutGap : 0) + 2 * (_theme.DropDownHorizontalPadding + _theme.ItemHorizontalPadding));
478 int y = _theme.DropDownVerticalPadding;
479
480 foreach (Entry entry in _entries)
481 {
482 if (entry.Item is not null)
483 {
484 var offset = new Vector2(_theme.DropDownHorizontalPadding, y);
485 SetLocalOffset(entry.Item, offset);
486
487 Point position = Point.Round(new PointF(
488 GetPosition().X + offset.X,
489 GetPosition().Y + offset.Y));
490
491 entry.Item.SetBounds(new Rectangle(
492 position.X,
493 position.Y,
494 Width - (_theme.DropDownHorizontalPadding * 2),
495 _theme.ItemHeight), markerWidth, iconWidth, shortcutWidth, arrowWidth);
496
497 y += _theme.ItemHeight;
498 continue;
499 }
500
501 if (entry.Separator is not null)
502 {
503 int lineX = _theme.DropDownHorizontalPadding + 5;
504 int lineY = y + (_theme.SeparatorHeight / 2);
505 var offset = new Vector2(lineX, lineY);
506
507 SetLocalOffset(entry.Separator, offset);
508
509 Point position = Point.Round(new PointF(
510 GetPosition().X + offset.X,
511 GetPosition().Y + offset.Y));
512
513 entry.Separator.ScreenBounds = new Rectangle(
514 position.X,
515 position.Y,
516 Math.Max(1, Width - (lineX * 2)),
517 1);
518
519 y += _theme.SeparatorHeight;
520 }
521 }
522
523 Height = Math.Max(1, y + _theme.DropDownVerticalPadding);
524
525 Point panelPosition = Point.Round(new PointF(GetPosition().X, GetPosition().Y));
526 Panel.ScreenBounds = new Rectangle(panelPosition.X, panelPosition.Y, Width, Height);
527 }
528
529 private void SetEntryInputEnabled(bool menuAcceptsInput)
530 {
531 foreach (MenuItemWidget item in _items)
532 item.ApplyInputState(menuAcceptsInput);
533 }
534
535 private void SetSelectedIndex(int index)
536 {
537 if (index < -1 || index >= _items.Count)
538 index = -1;
539
540 if (_selectedIndex == index)
541 return;
542
543 if (_selectedIndex >= 0 && _selectedIndex < _items.Count)
544 _items[_selectedIndex].SetSelected(false);
545
546 CloseChild();
547 _selectedIndex = index;
548
549 if (_selectedIndex >= 0)
550 _items[_selectedIndex].SetSelected(true);
551 }
552
553 private int FindNextEnabledIndex(int startIndex, int direction)
554 {
555 if (_items.Count == 0)
556 return -1;
557
558 int index = startIndex;
559
560 for (int attempt = 0; attempt < _items.Count; attempt++)
561 {
562 index = (index + direction + _items.Count) % _items.Count;
563
564 if (_items[index].CanInvoke)
565 return index;
566 }
567
568 return -1;
569 }
570
571 private void OnItemHovered(MenuItemWidget item)
572 {
573 int index = _items.IndexOf(item);
574
575 if (index >= 0)
576 {
577 SetSelectedIndex(index);
578 if (item.SubMenu is not null) OpenSubMenu(item, selectFirst: false);
579 }
580 }
581
582 private int MeasureText(string text)
583 {
584 using var font = new SKFont(SKTypeface.Default, _theme.FontSize);
585 return (int)Math.Ceiling(font.MeasureText(text));
586 }
587
588 private void PrepareVisualsForAnimation()
589 {
590 foreach (DirectDrawingBase visual in EnumerateVisuals())
591 {
592 visual.CancelFade();
593 visual.HideWhenFullyTransparent = false;
594 }
595 }
596
597 private void CancelVisualFades()
598 {
599 foreach (DirectDrawingBase visual in EnumerateVisuals())
600 visual.CancelFade().CancelReveal();
601 }
602
603 private void SetRevealProgress(float progress)
604 {
605 foreach (DirectDrawingBase visual in EnumerateVisuals())
606 {
607 visual.CancelReveal()
608 .SetRevealDirection(DirectDrawingBase.RevealDirection.TopToBottom)
609 .SetReveal(progress);
610 }
611 }
612
613 private IEnumerable<DirectDrawingBase> EnumerateVisuals()
614 {
615 return EnumerateVisuals(this);
616 }
617
618 private static IEnumerable<DirectDrawingBase> EnumerateVisuals(IDirectCompositeContainer container)
619 {
620 foreach (IDirectCompositeChild child in container.Children)
621 {
622 if (child is DirectDrawingBase drawing)
623 yield return drawing;
624
625 if (child is IDirectCompositeContainer nestedContainer && child is not MenuDropDownWidget)
626 {
627 foreach (DirectDrawingBase nestedDrawing in EnumerateVisuals(nestedContainer))
628 yield return nestedDrawing;
629 }
630 }
631 }
632
633 private void CancelPendingClose()
634 {
635 if (_closeCompletionHandler is null)
636 return;
637
638 Panel.FadeToCompleted -= _closeCompletionHandler;
639 _closeCompletionHandler = null;
640 }
641
642 private static float EaseOutCubic(float t)
643 {
644 float inverse = 1f - t;
645 return 1f - (inverse * inverse * inverse);
646 }
647
648 private sealed class Entry
649 {
650 private Entry(MenuItemWidget? item,
651 DirectRectangle? separator)
652 {
653 Item = item;
654 Separator = separator;
655 }
656
657 internal MenuItemWidget? Item { get; }
658 internal DirectRectangle? Separator { get; }
659
660 internal static Entry ForItem(MenuItemWidget item) => new(item, null);
661 internal static Entry ForSeparator(DirectRectangle separator) => new(null, separator);
662 }
663}
bool Visible
Gets or sets the visibility of all descendants.
RenderSurfaceHostBase RenderSurfaceHost
Gets the render surface host shared by the composite and its children.
DirectComposite SetOpacity(float opacity)
Applies one opacity to every descendant.
DirectComposite SetLocalOffset(IDirectCompositeChild child, Vector2 newLocalOffsetPx)
Changes one child's local offset and reapplies its position.
View? View
Gets the view shared by all children in view mode.
Vector2 GetPosition()
Gets the current composite anchor.
Provides the base implementation for direct drawing objects that can be rendered to the backbuffer wi...
DirectDrawingBase SetRevealDirection(RevealDirection dir)
Sets the direction from which the reveal animation progresses.
DirectDrawingBase CancelReveal()
Stops an active reveal animation at its current progress.
DirectDrawingBase RevealTo(float t01, float durationSec, Func< float, float >? easing=null)
Initiates a smooth reveal animation to the specified target progress over the given duration,...
DirectDrawingBase SetReveal(float t01)
Instantly sets the reveal progress to the specified value without animation.
DirectDrawingBase CancelFade()
Cancels any active fade transition, leaving the opacity at its current value.
RevealDirection
Defines the direction from which a reveal animation progressively displays the direct drawing.
Renders a configurable rectangle with support for fill, border, rounded corners, dash patterns,...
DirectRectangle SetFilled(bool isFilled)
Sets whether the rectangle interior is filled with the base color (or pattern).
DirectRectangle SetBorderColor(Color color)
Sets a distinct border (stroke) color, independent of the fill color.
DirectRectangle SetStrokeWidth(float width)
Sets the width (thickness) of the rectangle's stroke (outline) in pixels.
DirectRectangle SetCornerRadius(float radius)
Sets the radius for rounded corners in pixels.
Represents a base class for hosting a render surface, providing functionality for managing rendering ...
Represents a rendered view of a scene, combining a camera position with a viewport configuration to c...
Definition View.cs:14
override DirectComposite Add(IDirectCompositeChild child, bool keepCurrentOffset=true, Vector2? explicitLocalOffsetPx=null)
Adds a composite-compatible direct drawable as a child.The current composite.
ContainerWidget(RenderSurfaceHostBase renderSurfaceHost, DirectDrawingMode mode, PointF anchor=default, string? nickname=null)
Initializes a new instance of the ContainerWidget class.
Provides a view-level menu bar with animated dropdown command lists.
DirectRectangle Panel
Gets the popup panel drawing.
MenuDropDownWidget AddSubMenu(string text, Action< MenuDropDownWidget > configure, bool enabled=true, string? key=null, char? mnemonic=null, SKImage? icon=null)
Adds an owned submenu. Submenus may themselves contain submenus to any depth.
MenuDropDownWidget SetWidth(int width)
Sets a fixed minimum width for this dropdown.
bool IsOpen
Gets whether this dropdown accepts input. False while its close animation finishes.
MenuDropDownWidget AddCheckItem(string text, Action< bool >? action=null, bool isChecked=false, bool enabled=true, string? key=null, KeyGesture? shortcut=null, char? mnemonic=null, SKImage? icon=null)
Adds a check item. Invocation toggles state before passing the resulting value to the callback.
override void ProcessShown()
Performs processing when the widget is shown before notification callbacks are invoked.
IReadOnlyList< MenuItemWidget > Items
Gets the actionable entries in insertion order.
int Width
Gets the current popup width.
MenuDropDownWidget AddRadioItem(string text, Action? action, string group, bool isChecked=false, bool enabled=true, string? key=null, KeyGesture? shortcut=null, char? mnemonic=null, SKImage? icon=null)
Adds a radio command. Group names are scoped to this dropdown; the last checked item wins.
override void Dispose()
Disposes the composite and every child it owns.
int SelectedIndex
Gets the selected actionable-item index, or -1.
MenuDropDownWidget AddItem(string text, Action? action=null, string? shortcutText=null, bool enabled=true, string? key=null, KeyGesture? shortcut=null, char? mnemonic=null, SKImage? icon=null)
Adds a command, preserving fluent construction. Gesture text overrides display-only shortcutText.
override void ProcessHidden()
Performs processing when the widget is hidden before notification callbacks are invoked.
MenuDropDownWidget AddSeparator()
Adds a visual separator.
int Height
Gets the current popup height.
A command, check/radio item, or submenu owner in a menu dropdown.
string Text
Gets the command label.
bool IsCheckable
Gets whether this item has check or radio state.
string ShortcutText
Gets gesture-generated text, or the legacy display-only shortcut text.
MenuDropDownWidget? SubMenu
Gets the child dropdown, if this item owns a submenu.
SKImage? Icon
Gets the caller-owned image. Keep it alive until the item is disposed.
WidgetBase Show()
Shows the widget and registers it for input routing.
bool CanReceiveFocus
Gets or sets a value indicating whether the widget can receive keyboard focus.
bool IsPointerInputEnabled
Gets or sets a value indicating whether the widget can receive pointer input.
WidgetBase Hide()
Hides the widget and releases any routed input state.
bool IsKeyboardInputEnabled
Gets or sets a value indicating whether the widget can receive keyboard input.
IReadOnlyCollection< IDirectCompositeChild > Children
Gets the read-only collection of child elements in this container.
DirectDrawingMode
Specifies where direct drawing operations are applied within the rendering pipeline.
MenuDropDownAnimation
Specifies how a menu dropdown is animated when opened or closed.
record MenuBarTheme
Defines the dimensions and colors used by menu bar widgets.
A host key code and an exact combination of keyboard modifiers.
Definition KeyGesture.cs:9