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
ListBoxWidget.cs
1using System.Drawing;
2using System.Numerics;
8using SkiaSharp;
9
11
15public sealed class ListBoxWidget : WidgetBase
16{
17 private const int ContentPadding = 2;
18 private const int DefaultItemHeight = 24;
19 private const int ScrollBarWidth = 12;
20 private const int ScrollBarMargin = 3;
21 private const int MinimumScrollBarThumbHeight = 18;
22
23 private readonly List<string> _items = [];
24 private readonly List<TextBlock> _rowTextBlocks = [];
25
26 private int _selectedIndex = -1;
27 private int _topIndex;
28 private int _itemHeight = DefaultItemHeight;
29 private int _baseZOrder;
30 private int _mouseWheelScrollItems = 3;
31 private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
32 private bool _isDraggingScrollBarThumb;
33 private float _scrollBarDragOffset;
34 private int? _scrollBarPointerId;
35 private bool _suppressScrollBarClick;
36
40 public event Action<int>? SelectedIndexChanged;
41
45 public event Action<int>? SelectionCommitted;
46
55 public ListBoxWidget(RenderSurfaceHostBase renderSurfaceHost,
56 View view,
57 Rectangle bounds,
58 IEnumerable<string>? items = null,
59 string? nickname = null)
60 : base(renderSurfaceHost, DirectDrawingMode.View, ValidateBounds(bounds).Location, nickname)
61 {
62 ArgumentNullException.ThrowIfNull(view);
63
64 Background = CreateBackground(renderSurfaceHost, view, bounds);
65 SelectionHighlight = CreateSelectionHighlight(renderSurfaceHost, view, GetInitialSelectionBounds(bounds));
66 VerticalScrollBarTrack = CreateScrollBarTrack(renderSurfaceHost, view, bounds);
67 VerticalScrollBarThumb = CreateScrollBarThumb(renderSurfaceHost, view, bounds);
68
73
74 CompleteInitialization(items);
75 }
76
85 public ListBoxWidget(RenderSurfaceHostBase renderSurfaceHost,
86 SceneLayer sceneLayer,
87 Rectangle bounds,
88 IEnumerable<string>? items = null,
89 string? nickname = null)
90 : base(renderSurfaceHost, DirectDrawingMode.SceneLayer, ValidateBounds(bounds).Location, nickname)
91 {
92 ArgumentNullException.ThrowIfNull(sceneLayer);
93
94 Background = CreateBackground(renderSurfaceHost, sceneLayer, bounds);
95 SelectionHighlight = CreateSelectionHighlight(renderSurfaceHost, sceneLayer, GetInitialSelectionBounds(bounds));
96 VerticalScrollBarTrack = CreateScrollBarTrack(renderSurfaceHost, sceneLayer, bounds);
97 VerticalScrollBarThumb = CreateScrollBarThumb(renderSurfaceHost, sceneLayer, bounds);
98
103
104 CompleteInitialization(items);
105 }
106
111
116
119
122
126 public IReadOnlyList<string> Items => _items;
127
131 public Rectangle Bounds => Mode == DirectDrawingMode.View
132 ? Background.ScreenBounds
133 : Background.WorldBounds;
134
138 public int ItemHeight
139 {
140 get => _itemHeight;
141 set
142 {
143 if (value < 12)
144 throw new ArgumentOutOfRangeException(nameof(value), "Item height must be at least 12 pixels.");
145
146 if (_itemHeight == value)
147 return;
148
149 _itemHeight = value;
150 ClampTopIndex();
151 EnsureSelectionVisible();
152 RefreshRows();
153 }
154 }
155
159 public int VisibleItemCount => Math.Max(1, (Bounds.Height - ContentPadding * 2) / ItemHeight);
160
163 {
164 get => _verticalScrollBarVisibility;
165 set
166 {
167 if (!Enum.IsDefined(value))
168 throw new ArgumentOutOfRangeException(nameof(value));
169
170 if (_verticalScrollBarVisibility == value)
171 return;
172
173 _verticalScrollBarVisibility = value;
174 RefreshRows();
175 }
176 }
177
179 public bool IsVerticalScrollBarVisible => Visible && NeedsVerticalScrollBar;
180
181 private bool NeedsVerticalScrollBar => VerticalScrollBarVisibility != ScrollBarVisibility.Never
183
186 {
187 get => _mouseWheelScrollItems;
188 set => _mouseWheelScrollItems = value > 0
189 ? value
190 : throw new ArgumentOutOfRangeException(nameof(value), "Mouse-wheel scrolling must advance at least one item.");
191 }
192
194 public int VerticalScrollBarMinimumThumbHeight => MinimumScrollBarThumbHeight;
195
199 public int TopIndex
200 {
201 get => _topIndex;
202 set
203 {
204 int clamped = Math.Clamp(value, 0, GetMaximumTopIndex());
205 if (_topIndex == clamped)
206 return;
207
208 _topIndex = clamped;
209 RefreshRows();
210 }
211 }
212
216 public int SelectedIndex
217 {
218 get => _selectedIndex;
219 set => SetSelectedIndex(value);
220 }
221
225 public string? SelectedItem => _selectedIndex >= 0 && _selectedIndex < _items.Count
226 ? _items[_selectedIndex]
227 : null;
228
234 public ListBoxWidget SetItems(IEnumerable<string> items)
235 {
236 ArgumentNullException.ThrowIfNull(items);
237
238 bool hadSelection = _selectedIndex >= 0;
239 string[] replacement = items.Select(static item => item ?? string.Empty).ToArray();
240 _items.Clear();
241 _items.AddRange(replacement);
242 _selectedIndex = -1;
243 _topIndex = 0;
244 RefreshRows();
245
246 if (hadSelection)
247 SelectedIndexChanged?.Invoke(-1);
248
249 return this;
250 }
251
257 public ListBoxWidget AddItem(string item)
258 {
259 _items.Add(item ?? string.Empty);
260 ClampTopIndex();
261 RefreshRows();
262 return this;
263 }
264
270 public ListBoxWidget RemoveAt(int index)
271 {
272 if (index < 0 || index >= _items.Count)
273 throw new ArgumentOutOfRangeException(nameof(index));
274
275 int oldSelectedIndex = _selectedIndex;
276 bool selectedItemWasRemoved = index == oldSelectedIndex;
277
278 _items.RemoveAt(index);
279
280 if (_items.Count == 0)
281 {
282 _selectedIndex = -1;
283 }
284 else if (index < oldSelectedIndex)
285 {
286 _selectedIndex = oldSelectedIndex - 1;
287 }
288 else if (selectedItemWasRemoved)
289 {
290 _selectedIndex = Math.Min(index, _items.Count - 1);
291 }
292
293 ClampTopIndex();
294 EnsureSelectionVisible();
295 RefreshRows();
296
297 if (_selectedIndex != oldSelectedIndex || selectedItemWasRemoved)
298 SelectedIndexChanged?.Invoke(_selectedIndex);
299
300 return this;
301 }
302
308 {
309 bool hadSelection = _selectedIndex >= 0;
310 _items.Clear();
311 _selectedIndex = -1;
312 _topIndex = 0;
313 RefreshRows();
314
315 if (hadSelection)
316 SelectedIndexChanged?.Invoke(-1);
317
318 return this;
319 }
320
327 {
328 if (index < -1 || index >= _items.Count)
329 throw new ArgumentOutOfRangeException(nameof(index));
330
331 if (_selectedIndex == index)
332 {
333 EnsureSelectionVisible();
334 return this;
335 }
336
337 _selectedIndex = index;
338 EnsureSelectionVisible();
339 RefreshSelectionHighlight();
340 SelectedIndexChanged?.Invoke(index);
341 return this;
342 }
343
349 {
350 if (_items.Count == 0)
351 return this;
352
353 int next = _selectedIndex < 0 ? 0 : Math.Min(_selectedIndex + 1, _items.Count - 1);
354 return SetSelectedIndex(next);
355 }
356
362 {
363 if (_items.Count == 0)
364 return this;
365
366 int previous = _selectedIndex < 0 ? 0 : Math.Max(_selectedIndex - 1, 0);
367 return SetSelectedIndex(previous);
368 }
369
376 {
377 _baseZOrder = zOrder;
378 Background.ZOrder = zOrder;
379 SelectionHighlight.ZOrder = zOrder + 1;
380
381 foreach (TextBlock row in _rowTextBlocks)
382 row.ZOrder = zOrder + 2;
383
384 VerticalScrollBarTrack.ZOrder = zOrder + 3;
385 VerticalScrollBarThumb.ZOrder = zOrder + 4;
386
387 return this;
388 }
389
396 {
397 SelectionHighlight.SetColor(color);
398 Refresh(SelectionHighlight);
399 return this;
400 }
401
403 protected override void ProcessShown()
404 {
405 base.ProcessShown();
406 RefreshScrollBar();
407 RefreshSelectionHighlight();
408 }
409
411 protected override void ProcessHidden()
412 {
413 _isDraggingScrollBarThumb = false;
414 _scrollBarPointerId = null;
415 _suppressScrollBarClick = false;
416 base.ProcessHidden();
417 }
418
420 protected override void OnPointerClick(WidgetPointerEventArgs args)
421 {
422 base.OnPointerClick(args);
423
424 if (!args.IsPrimaryButton || _items.Count == 0)
425 return;
426
427 if (_suppressScrollBarClick || IsPointerOverScrollBar(args))
428 {
429 _suppressScrollBarClick = false;
430 args.Handled = true;
431 return;
432 }
433
434 RectangleF screenBounds = Background.GetDrawLocationScreen(args.View);
435 screenBounds.Offset(args.WrappedOffsetWorldPx.X * args.View.Viewport.Zoom, args.WrappedOffsetWorldPx.Y * args.View.Viewport.Zoom);
436 if (screenBounds.Height <= 0f || Bounds.Height <= 0)
437 return;
438
439 float scaleY = screenBounds.Height / Bounds.Height;
440 float localY = args.ScreenPositionPx.Y - screenBounds.Top - ContentPadding * scaleY;
441 float rowHeightScreen = ItemHeight * scaleY;
442
443 if (localY < 0f || rowHeightScreen <= 0f)
444 return;
445
446 int row = (int)(localY / rowHeightScreen);
447 int index = TopIndex + row;
448
449 if (row < 0 || row >= VisibleItemCount || index >= _items.Count)
450 return;
451
452 args.Handled = true;
453 SetSelectedIndex(index);
454 SelectionCommitted?.Invoke(index);
455 }
456
458 protected override void OnPointerDown(WidgetPointerEventArgs args)
459 {
460 base.OnPointerDown(args);
461
462 if (_scrollBarPointerId.HasValue)
463 return;
464
465 _suppressScrollBarClick = false;
466
468 return;
469
470 PointF local = GetLocalPointerPosition(args);
471 Rectangle thumbBounds = GetScrollBarThumbBounds(Bounds);
472 Rectangle trackBounds = GetScrollBarTrackBounds(Bounds);
473
474 if (!trackBounds.Contains(Point.Round(local)))
475 return;
476
477 args.Handled = true;
478 _scrollBarPointerId = args.PointerId;
479 _suppressScrollBarClick = true;
480 if (thumbBounds.Contains(Point.Round(local)))
481 {
482 _isDraggingScrollBarThumb = true;
483 _scrollBarDragOffset = local.Y - thumbBounds.Top;
484 return;
485 }
486
487 TopIndex += local.Y < thumbBounds.Top ? -VisibleItemCount : VisibleItemCount;
488 }
489
491 protected override void OnPointerMove(WidgetPointerEventArgs args)
492 {
493 base.OnPointerMove(args);
494
495 if (!_isDraggingScrollBarThumb || _scrollBarPointerId != args.PointerId || !IsVerticalScrollBarVisible)
496 return;
497
498 Rectangle trackBounds = GetScrollBarTrackBounds(Bounds);
499 Rectangle thumbBounds = GetScrollBarThumbBounds(Bounds);
500 float travel = trackBounds.Height - thumbBounds.Height;
501
502 if (travel <= 0f || GetMaximumTopIndex() == 0)
503 return;
504
505 float desiredTop = GetLocalPointerPosition(args).Y - _scrollBarDragOffset;
506 float fraction = Math.Clamp((desiredTop - trackBounds.Top) / travel, 0f, 1f);
507 TopIndex = (int)MathF.Round(fraction * GetMaximumTopIndex());
508 args.Handled = true;
509 }
510
512 protected override void OnPointerUp(WidgetPointerEventArgs args)
513 {
514 base.OnPointerUp(args);
515
516 if (_scrollBarPointerId == args.PointerId)
517 {
518 _isDraggingScrollBarThumb = false;
519 _scrollBarPointerId = null;
520 args.Handled = true;
521 }
522 }
523
525 protected override void OnMouseWheel(WidgetMouseWheelEventArgs args)
526 {
527 base.OnMouseWheel(args);
528
529 if (args.Delta == 0 || GetMaximumTopIndex() == 0)
530 return;
531
532 long notches = Math.Max(1, Math.Abs((long)args.Delta) / 120);
533 long change = notches * MouseWheelScrollItems * (args.Delta > 0 ? -1 : 1);
534 TopIndex = (int)Math.Clamp(TopIndex + change, 0, GetMaximumTopIndex());
535 args.Handled = true;
536 }
537
539 protected override void OnKeyboardInput(WidgetKeyboardEventArgs args)
540 {
541 base.OnKeyboardInput(args);
542
543 if (_items.Count == 0)
544 return;
545
546 if (args.Key == 13)
547 {
548 if (args.KeyAction != KeyAction.Pressed || _selectedIndex < 0)
549 return;
550
551 args.Handled = true;
552 SelectionCommitted?.Invoke(_selectedIndex);
553 return;
554 }
555
556 if (args.KeyAction is not KeyAction.Pressed and not KeyAction.Repeated)
557 return;
558
559 switch (args.Key)
560 {
561 case 38: // Up
563 break;
564 case 40: // Down
565 SelectNext();
566 break;
567 case 36: // Home
569 break;
570 case 35: // End
571 SetSelectedIndex(_items.Count - 1);
572 break;
573 case 33: // Page Up
574 SetSelectedIndex(Math.Max(0, (_selectedIndex < 0 ? 0 : _selectedIndex) - VisibleItemCount));
575 break;
576 case 34: // Page Down
577 SetSelectedIndex(Math.Min(_items.Count - 1, (_selectedIndex < 0 ? 0 : _selectedIndex) + VisibleItemCount));
578 break;
579 default:
580 return;
581 }
582
583 args.Handled = true;
584 }
585
586 private void CompleteInitialization(IEnumerable<string>? items)
587 {
588 SelectionHighlight.Visible = false;
589 VerticalScrollBarTrack.Visible = false;
590 VerticalScrollBarThumb.Visible = false;
591 CanReceiveFocus = true;
594
595 if (items is not null)
596 _items.AddRange(items.Select(static item => item ?? string.Empty));
597
598 RefreshRows();
599 }
600
601 private void EnsureSelectionVisible()
602 {
603 if (_selectedIndex < 0)
604 return;
605
606 if (_selectedIndex < _topIndex)
607 {
608 _topIndex = _selectedIndex;
609 RefreshRows();
610 return;
611 }
612
613 int lastVisible = _topIndex + VisibleItemCount - 1;
614 if (_selectedIndex > lastVisible)
615 {
616 _topIndex = Math.Min(GetMaximumTopIndex(), _selectedIndex - VisibleItemCount + 1);
617 RefreshRows();
618 }
619 }
620
621 private void ClampTopIndex()
622 {
623 _topIndex = Math.Clamp(_topIndex, 0, GetMaximumTopIndex());
624 }
625
626 private int GetMaximumTopIndex()
627 {
628 return Math.Max(0, _items.Count - VisibleItemCount);
629 }
630
631 private void RefreshRows()
632 {
633 bool wasVisible = Visible;
634
635 RefreshScrollBar();
636
637 foreach (TextBlock row in _rowTextBlocks)
638 {
639 Remove(row);
640 row.Dispose();
641 }
642
643 _rowTextBlocks.Clear();
644
645 Rectangle bounds = Bounds;
646 int count = Math.Min(VisibleItemCount, Math.Max(0, _items.Count - TopIndex));
647
648 for (int rowIndex = 0; rowIndex < count; rowIndex++)
649 {
650 int itemIndex = TopIndex + rowIndex;
651 Rectangle rowBounds = GetRowBounds(bounds, rowIndex);
652 TextBlock row = CreateRowText(rowBounds, _items[itemIndex]);
653 row.ZOrder = _baseZOrder + 2;
654 Add(row);
655 _rowTextBlocks.Add(row);
656 }
657
658 RefreshSelectionHighlight();
659
660 if (!wasVisible)
661 SetIsVisible(false);
662 }
663
664 private TextBlock CreateRowText(Rectangle bounds, string text)
665 {
666 TextBlock row = Mode == DirectDrawingMode.View
667 ? new TextBlock(RenderSurfaceHost, View!, bounds)
668 : new TextBlock(RenderSurfaceHost, SceneLayer!, view: null, worldBounds: bounds);
669
670 return row.SetText(text)
671 .SetFont(SKTypeface.Default, 15f, minSize: 9f)
672 .SetColors(SKColors.White, SKColors.Transparent)
673 .SetAlignment(SKTextAlign.Left, TextBlock.VerticalAlign.Center)
674 .SetPadding(6f, 0f)
675 .EnableWrapping(false);
676 }
677
678 private void RefreshSelectionHighlight()
679 {
680 if (!Visible || _selectedIndex < TopIndex || _selectedIndex >= TopIndex + VisibleItemCount || _selectedIndex >= _items.Count)
681 {
682 SelectionHighlight.Visible = false;
683 return;
684 }
685
686 int rowIndex = _selectedIndex - TopIndex;
687 Rectangle selectionBounds = GetRowBounds(Bounds, rowIndex);
688 Rectangle currentBounds = Mode == DirectDrawingMode.View
689 ? SelectionHighlight.ScreenBounds
690 : SelectionHighlight.WorldBounds;
691 Rectangle resizedBounds = new(currentBounds.Location, selectionBounds.Size);
692
693 if (Mode == DirectDrawingMode.View)
694 SelectionHighlight.ScreenBounds = resizedBounds;
695 else
696 SelectionHighlight.WorldBounds = resizedBounds;
697
700 new Vector2(ContentPadding, ContentPadding + rowIndex * ItemHeight));
701
702 SelectionHighlight.Visible = true;
703 }
704
705 private Rectangle GetRowBounds(Rectangle bounds, int rowIndex)
706 {
707 int y = bounds.Y + ContentPadding + rowIndex * ItemHeight;
708 return new Rectangle(bounds.X + ContentPadding,
709 y,
710 Math.Max(1, bounds.Width - ContentPadding * 2 - (NeedsVerticalScrollBar ? ScrollBarWidth + ScrollBarMargin * 2 : 0)),
711 Math.Min(ItemHeight, Math.Max(1, bounds.Bottom - ContentPadding - y)));
712 }
713
714 private void RefreshScrollBar()
715 {
716 bool visible = IsVerticalScrollBarVisible;
717 VerticalScrollBarTrack.Visible = visible;
718 VerticalScrollBarThumb.Visible = visible;
719
720 if (!visible)
721 return;
722
723 Rectangle trackBounds = GetScrollBarTrackBounds(Bounds);
724 Rectangle thumbBounds = GetScrollBarThumbBounds(Bounds);
725 SetRectangleBounds(VerticalScrollBarTrack, trackBounds);
726 SetRectangleBounds(VerticalScrollBarThumb, thumbBounds);
727 }
728
729 private Rectangle GetScrollBarTrackBounds(Rectangle bounds)
730 {
731 return new Rectangle(bounds.Right - ScrollBarMargin - ScrollBarWidth,
732 bounds.Top + ContentPadding + ScrollBarMargin,
733 ScrollBarWidth,
734 Math.Max(1, bounds.Height - (ContentPadding + ScrollBarMargin) * 2));
735 }
736
737 private Rectangle GetScrollBarThumbBounds(Rectangle bounds)
738 {
739 Rectangle trackBounds = GetScrollBarTrackBounds(bounds);
740 int thumbHeight = _items.Count == 0
741 ? trackBounds.Height
742 : Math.Clamp((int)MathF.Round(trackBounds.Height * Math.Min(1f, (float)VisibleItemCount / _items.Count)),
743 Math.Min(MinimumScrollBarThumbHeight, trackBounds.Height),
744 trackBounds.Height);
745 int travel = trackBounds.Height - thumbHeight;
746 int maximumTopIndex = GetMaximumTopIndex();
747 int offset = maximumTopIndex == 0 ? 0 : (int)MathF.Round(travel * ((float)TopIndex / maximumTopIndex));
748 return new Rectangle(trackBounds.Left, trackBounds.Top + offset, trackBounds.Width, thumbHeight);
749 }
750
751 private PointF GetLocalPointerPosition(WidgetPointerEventArgs args)
752 {
753 RectangleF screenBounds = Background.GetDrawLocationScreen(args.View);
754 screenBounds.Offset(args.WrappedOffsetWorldPx.X * args.View.Viewport.Zoom,
755 args.WrappedOffsetWorldPx.Y * args.View.Viewport.Zoom);
756 float scaleX = screenBounds.Width / Bounds.Width;
757 float scaleY = screenBounds.Height / Bounds.Height;
758 return new PointF(Bounds.Left + (args.ScreenPositionPx.X - screenBounds.Left) / scaleX,
759 Bounds.Top + (args.ScreenPositionPx.Y - screenBounds.Top) / scaleY);
760 }
761
762 private bool IsPointerOverScrollBar(WidgetPointerEventArgs args)
763 {
764 // The entire reserved gutter belongs to the scrollbar, including its margins.
765 return IsVerticalScrollBarVisible && GetLocalPointerPosition(args).X >= GetRowBounds(Bounds, 0).Right;
766 }
767
768 private void SetRectangleBounds(DirectRectangle rectangle, Rectangle bounds)
769 {
770 if (Mode == DirectDrawingMode.View)
771 rectangle.ScreenBounds = bounds;
772 else
773 rectangle.WorldBounds = bounds;
774
775 SetLocalOffset(rectangle, new Vector2(bounds.X, bounds.Y) - GetPosition());
776 }
777
778 private static DirectRectangle CreateBackground(RenderSurfaceHostBase host,
779 View view,
780 Rectangle bounds)
781 {
782 return ConfigureBackground(new DirectRectangle(Color.FromArgb(255, 36, 36, 44), host, view, bounds));
783 }
784
785 private static DirectRectangle CreateBackground(RenderSurfaceHostBase host,
786 SceneLayer layer,
787 Rectangle bounds)
788 {
789 return ConfigureBackground(new DirectRectangle(Color.FromArgb(255, 36, 36, 44), host, layer, bounds));
790 }
791
792 private static DirectRectangle ConfigureBackground(DirectRectangle background)
793 {
794 return background.SetFilled(true)
795 .SetBorderColor(Color.FromArgb(255, 140, 140, 155))
796 .SetStrokeWidth(1.5f)
797 .SetCornerRadius(4f);
798 }
799
800 private static DirectRectangle CreateSelectionHighlight(RenderSurfaceHostBase host,
801 View view,
802 Rectangle bounds)
803 {
804 return ConfigureSelectionHighlight(new DirectRectangle(Color.FromArgb(220, 63, 91, 138), host, view, bounds));
805 }
806
807 private static DirectRectangle CreateSelectionHighlight(RenderSurfaceHostBase host,
808 SceneLayer layer,
809 Rectangle bounds)
810 {
811 return ConfigureSelectionHighlight(new DirectRectangle(Color.FromArgb(220, 63, 91, 138), host, layer, bounds));
812 }
813
814 private static DirectRectangle ConfigureSelectionHighlight(DirectRectangle highlight)
815 {
816 return highlight.SetFilled(true)
817 .SetStrokeWidth(0f)
818 .SetCornerRadius(2f);
819 }
820
821 private static DirectRectangle CreateScrollBarTrack(RenderSurfaceHostBase host, View view, Rectangle bounds)
822 {
823 return ConfigureScrollBarTrack(new DirectRectangle(Color.FromArgb(255, 26, 26, 32), host, view, bounds));
824 }
825
826 private static DirectRectangle CreateScrollBarTrack(RenderSurfaceHostBase host, SceneLayer layer, Rectangle bounds)
827 {
828 return ConfigureScrollBarTrack(new DirectRectangle(Color.FromArgb(255, 26, 26, 32), host, layer, bounds));
829 }
830
831 private static DirectRectangle ConfigureScrollBarTrack(DirectRectangle track)
832 {
833 return track.SetFilled(true).SetStrokeWidth(0f).SetCornerRadius(3f);
834 }
835
836 private static DirectRectangle CreateScrollBarThumb(RenderSurfaceHostBase host, View view, Rectangle bounds)
837 {
838 return ConfigureScrollBarThumb(new DirectRectangle(Color.FromArgb(255, 126, 126, 142), host, view, bounds));
839 }
840
841 private static DirectRectangle CreateScrollBarThumb(RenderSurfaceHostBase host, SceneLayer layer, Rectangle bounds)
842 {
843 return ConfigureScrollBarThumb(new DirectRectangle(Color.FromArgb(255, 126, 126, 142), host, layer, bounds));
844 }
845
846 private static DirectRectangle ConfigureScrollBarThumb(DirectRectangle thumb)
847 {
848 return thumb.SetFilled(true).SetStrokeWidth(0f).SetCornerRadius(3f);
849 }
850
851 private static Rectangle GetInitialSelectionBounds(Rectangle bounds)
852 {
853 return new Rectangle(bounds.X + ContentPadding,
854 bounds.Y + ContentPadding,
855 Math.Max(1, bounds.Width - ContentPadding * 2),
856 DefaultItemHeight);
857 }
858
859 private static void Refresh(DirectRectangle rectangle)
860 {
861 rectangle.SetPosition(rectangle.GetPosition());
862 }
863
864 private static Rectangle ValidateBounds(Rectangle bounds)
865 {
866 if (bounds.Width < 24 || bounds.Height < DefaultItemHeight + ContentPadding * 2)
867 throw new ArgumentOutOfRangeException(nameof(bounds), bounds, "List-box bounds are too small.");
868
869 return bounds;
870 }
871}
bool Visible
Gets or sets the visibility of all descendants.
virtual DirectComposite Remove(IDirectCompositeChild child)
Removes a child without disposing it.
RenderSurfaceHostBase RenderSurfaceHost
Gets the render surface host shared by the composite and its children.
SceneLayer? SceneLayer
Gets the scene layer shared by all children in scene-layer mode.
DirectComposite SetLocalOffset(IDirectCompositeChild child, Vector2 newLocalOffsetPx)
Changes one child's local offset and reapplies its position.
DirectComposite SetIsVisible(bool visible)
Applies one visibility value to every descendant.
virtual DirectComposite Clear()
Removes every child without disposing them.
View? View
Gets the view shared by all children in view mode.
Vector2 GetPosition()
Gets the current composite anchor.
virtual DirectComposite Add(IDirectCompositeChild child, bool keepCurrentOffset=true, Vector2? explicitLocalOffsetPx=null)
Adds a composite-compatible direct drawable as a child.
DirectDrawingMode Mode
Gets the coordinate mode shared by the composite and its children.
Vector2 GetPosition()
Gets the current position of this direct drawing in pixel coordinates.
void SetPosition(Vector2 p)
Sets the position of this direct drawing to the specified pixel coordinates.
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.
A retained-mode text drawable with wrapping, multi-line ( ) support, horizontal/vertical alignment,...
Definition TextBlock.cs:44
TextBlock EnableWrapping(bool enable=true)
Enables or disables word wrapping when laying out paragraphs.
Definition TextBlock.cs:503
TextBlock SetAlignment(SKTextAlign h, VerticalAlign v)
Sets the horizontal and vertical alignment used when drawing the laid-out lines within the bounds.
Definition TextBlock.cs:433
TextBlock SetPadding(float horizontal, float vertical)
Sets symmetric horizontal and vertical padding and invalidates cached layout.
Definition TextBlock.cs:317
TextBlock SetColors(SKColor fg, SKColor bg)
Sets the foreground (text) and background colors using Skia colors.
Definition TextBlock.cs:410
override void Dispose(bool disposing)
Releases managed resources used by the text block.
TextBlock SetText(string text)
Sets the current text content and rebuilds layout as needed. If no reveal animation is active,...
Definition TextBlock.cs:358
Represents a base class for hosting a render surface, providing functionality for managing rendering ...
Viewport Viewport
Gets the viewport that defines the screen-space rectangle, zoom level, and other rendering parameters...
Definition View.cs:44
float Zoom
Zoom factor applied to the world when rendering this view. Values greater than 1 zoom in,...
Definition Viewport.cs:67
ListBoxWidget SetSelectedIndex(int index)
Selects the requested item index. Use -1 to clear selection.
override void ProcessHidden()
Performs processing when the widget is hidden before notification callbacks are invoked.
override void OnKeyboardInput(WidgetKeyboardEventArgs args)
Called when keyboard input is routed to the widget.
int VisibleItemCount
Gets the number of complete rows that can currently be displayed.
ListBoxWidget SetItems(IEnumerable< string > items)
Replaces all list items and clears selection.
ScrollBarVisibility VerticalScrollBarVisibility
Gets or sets when the vertical scrollbar is displayed.
Action< int >? SelectionCommitted
Occurs when the current selection is explicitly committed.
IReadOnlyList< string > Items
Gets the current items.
override void OnPointerClick(WidgetPointerEventArgs args)
Called when a pointer click is dispatched to the widget.
Action< int >? SelectedIndexChanged
Occurs when SelectedIndex changes.
DirectRectangle VerticalScrollBarThumb
Gets the vertical scrollbar thumb.
override void OnPointerDown(WidgetPointerEventArgs args)
Called when a pointer press begins on the widget.
Rectangle Bounds
Gets the list-box bounds in its native coordinate space.
ListBoxWidget SelectNext()
Selects the next item when possible.
DirectRectangle VerticalScrollBarTrack
Gets the vertical scrollbar track.
int SelectedIndex
Gets or sets the selected item index. Use -1 to clear selection.
string? SelectedItem
Gets the selected item text, or null when no item is selected.
DirectRectangle Background
Gets the list background.
override void OnPointerUp(WidgetPointerEventArgs args)
Called when a pointer press is released for the widget.
ListBoxWidget SelectPrevious()
Selects the previous item when possible.
DirectRectangle SelectionHighlight
Gets the rectangle used to highlight the selected row.
ListBoxWidget ClearItems()
Removes all items and clears selection.
ListBoxWidget SetSelectionColor(Color color)
Sets the selected-row highlight color.
ListBoxWidget SetListBoxZOrder(int zOrder)
Sets the base Z-order used by the list-box visuals.
int ItemHeight
Gets or sets the height of each row in native pixels.
ListBoxWidget RemoveAt(int index)
Removes an item by index.
ListBoxWidget(RenderSurfaceHostBase renderSurfaceHost, View view, Rectangle bounds, IEnumerable< string >? items=null, string? nickname=null)
Creates a view-level list box.
bool IsVerticalScrollBarVisible
Gets whether the vertical scrollbar is currently displayed.
int TopIndex
Gets or sets the index of the first visible item.
int VerticalScrollBarMinimumThumbHeight
Gets the minimum height used for a visible scrollbar thumb.
override void OnMouseWheel(WidgetMouseWheelEventArgs args)
Called when mouse-wheel input is routed to the widget.
override void ProcessShown()
Performs processing when the widget is shown before notification callbacks are invoked.
override void OnPointerMove(WidgetPointerEventArgs args)
Called when pointer movement is routed to the widget.
ListBoxWidget(RenderSurfaceHostBase renderSurfaceHost, SceneLayer sceneLayer, Rectangle bounds, IEnumerable< string >? items=null, string? nickname=null)
Creates a scene-layer list box.
int MouseWheelScrollItems
Gets or sets the number of items scrolled for one wheel notch.
ListBoxWidget AddItem(string item)
Adds an item to the end of the list.
bool CanReceiveFocus
Gets or sets a value indicating whether the widget can receive keyboard focus.
WidgetBase(RenderSurfaceHostBase renderSurfaceHost, DirectDrawingMode mode, PointF anchor=default, string? nickname=null)
Initializes a new instance of the WidgetBase class.
Definition WidgetBase.cs:95
bool IsKeyboardInputEnabled
Gets or sets a value indicating whether the widget can receive keyboard input.
Provides data for widget keyboard input events routed by WidgetInputRouter.
int Key
Gets the integer key identifier.
KeyAction KeyAction
Gets the action associated with this key event.
Event arguments for mouse-wheel input routed to a widget.
int Delta
Gets the wheel delta. Positive values scroll upward; negative values scroll downward.
Event arguments for pointer-based widget callbacks.
PointF WrappedOffsetWorldPx
World-space translation of the selected periodic widget instance. Retained throughout pointer capture...
bool IsPrimaryButton
Gets whether the pointer event represents a primary-button interaction.
View View
Gets the view through which the pointer interaction was routed.
int PointerId
Gets the identifier of the pointer that produced the event.
@ 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.
@ View
Performs direct drawing at the view level using screen-space coordinates.
KeyAction
Represents the type of action performed on a keyboard key, indicating whether the key was pressed,...
Definition KeyAction.cs:10
ScrollBarVisibility
Specifies when a scrollable widget displays its vertical scrollbar.
@ Always
Always displays the scrollbar while the widget is visible.