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
RadioButtonWidget.cs
1using System.Drawing;
7using SkiaSharp;
8
10
14public sealed class RadioButtonGroup
15{
16 private readonly HashSet<RadioButtonWidget> _buttons = [];
17
21 public RadioButtonWidget? SelectedButton => _buttons.FirstOrDefault(static button => button.IsSelected);
22
23 internal void Register(RadioButtonWidget button)
24 {
25 ArgumentNullException.ThrowIfNull(button);
26 _buttons.Add(button);
27
28 if (button.IsSelected)
29 Select(button);
30 }
31
32 internal void Unregister(RadioButtonWidget button)
33 {
34 ArgumentNullException.ThrowIfNull(button);
35 _buttons.Remove(button);
36 }
37
38 internal void Select(RadioButtonWidget selected)
39 {
40 ArgumentNullException.ThrowIfNull(selected);
41
42 foreach (RadioButtonWidget button in _buttons.ToArray())
43 button.SetSelectedFromGroup(ReferenceEquals(button, selected));
44 }
45}
46
50public sealed class RadioButtonWidget : WidgetBase
51{
52 private const int DefaultIndicatorSize = 20;
53 private const int DefaultLabelGap = 8;
54 private const int DotInset = 6;
55
56 private Color _normalColor = Color.FromArgb(255, 48, 48, 56);
57 private Color _hoverColor = Color.FromArgb(255, 66, 66, 76);
58 private Color _dotColor = Color.FromArgb(255, 235, 235, 242);
59 private RadioButtonGroup? _group;
60 private bool _isSelected;
61 private bool _disposed;
62
66 public event Action<bool>? SelectionChanged;
67
78 public RadioButtonWidget(RenderSurfaceHostBase renderSurfaceHost,
79 View view,
80 Rectangle bounds,
81 string text,
82 RadioButtonGroup? group = null,
83 bool isSelected = false,
84 string? nickname = null)
85 : base(renderSurfaceHost, DirectDrawingMode.View, ValidateBounds(bounds).Location, nickname)
86 {
87 ArgumentNullException.ThrowIfNull(view);
88
89 Rectangle indicatorBounds = GetIndicatorBounds(bounds);
90 Rectangle dotBounds = GetDotBounds(indicatorBounds);
91 Rectangle labelBounds = GetLabelBounds(bounds, indicatorBounds.Width);
92
93 Ring = CreateRing(renderSurfaceHost, view, indicatorBounds);
94 Dot = CreateDot(renderSurfaceHost, view, dotBounds);
95 Label = CreateLabel(renderSurfaceHost, view, labelBounds, text);
96
97 Add(Ring);
98 Add(Dot);
99 Add(Label);
100
101 CompleteInitialization(group, isSelected);
102 }
103
114 public RadioButtonWidget(RenderSurfaceHostBase renderSurfaceHost,
115 SceneLayer sceneLayer,
116 Rectangle bounds,
117 string text,
118 RadioButtonGroup? group = null,
119 bool isSelected = false,
120 string? nickname = null)
121 : base(renderSurfaceHost, DirectDrawingMode.SceneLayer, ValidateBounds(bounds).Location, nickname)
122 {
123 ArgumentNullException.ThrowIfNull(sceneLayer);
124
125 Rectangle indicatorBounds = GetIndicatorBounds(bounds);
126 Rectangle dotBounds = GetDotBounds(indicatorBounds);
127 Rectangle labelBounds = GetLabelBounds(bounds, indicatorBounds.Width);
128
129 Ring = CreateRing(renderSurfaceHost, sceneLayer, indicatorBounds);
130 Dot = CreateDot(renderSurfaceHost, sceneLayer, dotBounds);
131 Label = CreateLabel(renderSurfaceHost, sceneLayer, labelBounds, text);
132
133 Add(Ring);
134 Add(Dot);
135 Add(Label);
136
137 CompleteInitialization(group, isSelected);
138 }
139
143 public DirectRectangle Ring { get; }
144
148 public DirectRectangle Dot { get; }
149
153 public TextBlock Label { get; }
154
159 {
160 get => _group;
161 set
162 {
163 if (ReferenceEquals(_group, value))
164 return;
165
166 _group?.Unregister(this);
167 _group = value;
168 _group?.Register(this);
169 }
170 }
171
175 public bool IsSelected
176 {
177 get => _isSelected;
178 set => SetSelected(value);
179 }
180
186 public RadioButtonWidget SetSelected(bool isSelected)
187 {
188 if (isSelected && Group is not null)
189 {
190 Group.Select(this);
191 return this;
192 }
193
194 SetSelectedFromGroup(isSelected);
195 return this;
196 }
197
203 {
204 return SetSelected(true);
205 }
206
212 public RadioButtonWidget SetText(string text)
213 {
214 Label.SetText(text ?? string.Empty);
215 return this;
216 }
217
225 public RadioButtonWidget SetColors(Color normal,
226 Color hover,
227 Color dot)
228 {
229 _normalColor = normal;
230 _hoverColor = hover;
231 _dotColor = dot;
232
233 UpdateRingColor(_normalColor);
234 Dot.SetColor(_dotColor);
235 Refresh(Dot);
236 return this;
237 }
238
244 public RadioButtonWidget SetTextColor(SKColor color)
245 {
246 Label.SetColors(color, SKColors.Transparent);
247 return this;
248 }
249
256 {
257 Ring.ZOrder = zOrder;
258 Dot.ZOrder = zOrder + 1;
259 Label.ZOrder = zOrder + 1;
260 return this;
261 }
262
264 public override void Dispose()
265 {
266 if (_disposed)
267 return;
268
269 _disposed = true;
270 Group = null;
271 base.Dispose();
272 }
273
275 protected override void ProcessShown()
276 {
277 base.ProcessShown();
278 Dot.Visible = IsSelected;
279 }
280
282 protected override void OnPointerEnter(WidgetPointerEventArgs args)
283 {
284 base.OnPointerEnter(args);
285 UpdateRingColor(_hoverColor);
286 }
287
289 protected override void OnPointerLeave(WidgetPointerEventArgs args)
290 {
291 base.OnPointerLeave(args);
292 UpdateRingColor(_normalColor);
293 }
294
296 protected override void OnPointerClick(WidgetPointerEventArgs args)
297 {
298 base.OnPointerClick(args);
299
300 if (!args.IsPrimaryButton)
301 return;
302
303 args.Handled = true;
304 Select();
305 }
306
308 protected override void OnKeyboardInput(WidgetKeyboardEventArgs args)
309 {
310 base.OnKeyboardInput(args);
311
312 if (args.KeyAction != KeyAction.Pressed || args.Key != 32)
313 return;
314
315 args.Handled = true;
316 Select();
317 }
318
319 internal void SetSelectedFromGroup(bool isSelected)
320 {
321 if (_isSelected == isSelected)
322 return;
323
324 _isSelected = isSelected;
325 Dot.Visible = isSelected;
326 SelectionChanged?.Invoke(isSelected);
327 }
328
329 private void CompleteInitialization(RadioButtonGroup? group, bool isSelected)
330 {
331 _isSelected = isSelected;
332 Dot.Visible = isSelected;
334
335 CanReceiveFocus = true;
337
338 Group = group;
339 }
340
341 private void UpdateRingColor(Color color)
342 {
343 Ring.SetColor(color);
344 Refresh(Ring);
345 }
346
347 private static void Refresh(DirectRectangle rectangle)
348 {
349 rectangle.SetPosition(rectangle.GetPosition());
350 }
351
352 private DirectRectangle CreateRing(RenderSurfaceHostBase host,
353 View view,
354 Rectangle bounds)
355 {
356 return ConfigureRing(new DirectRectangle(_normalColor, host, view, bounds, $"{Nickname}.ring"), bounds.Width);
357 }
358
359 private DirectRectangle CreateRing(RenderSurfaceHostBase host,
360 SceneLayer layer,
361 Rectangle bounds)
362 {
363 return ConfigureRing(new DirectRectangle(_normalColor, host, layer, bounds, $"{Nickname}.ring"), bounds.Width);
364 }
365
366 private static DirectRectangle ConfigureRing(DirectRectangle ring, int size)
367 {
368 return ring.SetFilled(true)
369 .SetBorderColor(Color.FromArgb(255, 150, 150, 160))
370 .SetStrokeWidth(1.5f)
371 .SetCornerRadius(size / 2f);
372 }
373
374 private DirectRectangle CreateDot(RenderSurfaceHostBase host,
375 View view,
376 Rectangle bounds)
377 {
378 return ConfigureDot(new DirectRectangle(_dotColor, host, view, bounds, $"{Nickname}.dot"), bounds.Width);
379 }
380
381 private DirectRectangle CreateDot(RenderSurfaceHostBase host,
382 SceneLayer layer,
383 Rectangle bounds)
384 {
385 return ConfigureDot(new DirectRectangle(_dotColor, host, layer, bounds, $"{Nickname}.dot"), bounds.Width);
386 }
387
388 private static DirectRectangle ConfigureDot(DirectRectangle dot, int size)
389 {
390 return dot.SetFilled(true)
391 .SetStrokeWidth(0f)
392 .SetCornerRadius(size / 2f);
393 }
394
395 private static TextBlock CreateLabel(RenderSurfaceHostBase host,
396 View view,
397 Rectangle bounds,
398 string text)
399 {
400 return ConfigureLabel(new TextBlock(host, view, bounds), text);
401 }
402
403 private static TextBlock CreateLabel(RenderSurfaceHostBase host,
404 SceneLayer layer,
405 Rectangle bounds,
406 string text)
407 {
408 return ConfigureLabel(new TextBlock(host, layer, view: null, worldBounds: bounds), text);
409 }
410
411 private static TextBlock ConfigureLabel(TextBlock label, string text)
412 {
413 return label.SetText(text ?? string.Empty)
414 .SetFont(SKTypeface.Default, 16f, minSize: 10f)
415 .SetColors(SKColors.White, SKColors.Transparent)
416 .SetAlignment(SKTextAlign.Left, TextBlock.VerticalAlign.Center)
417 .EnableWrapping(false);
418 }
419
420 private static Rectangle GetIndicatorBounds(Rectangle bounds)
421 {
422 int size = Math.Min(DefaultIndicatorSize, Math.Max(10, bounds.Height - 2));
423 return new Rectangle(bounds.X,
424 bounds.Y + (bounds.Height - size) / 2,
425 size,
426 size);
427 }
428
429 private static Rectangle GetDotBounds(Rectangle indicatorBounds)
430 {
431 int inset = Math.Min(DotInset, Math.Max(2, indicatorBounds.Width / 3));
432 return Rectangle.Inflate(indicatorBounds, -inset, -inset);
433 }
434
435 private static Rectangle GetLabelBounds(Rectangle bounds, int indicatorWidth)
436 {
437 int left = bounds.X + indicatorWidth + DefaultLabelGap;
438 return new Rectangle(left,
439 bounds.Y,
440 Math.Max(1, bounds.Right - left),
441 bounds.Height);
442 }
443
444 private static Rectangle ValidateBounds(Rectangle bounds)
445 {
446 if (bounds.Width < 40 || bounds.Height < 12)
447 throw new ArgumentOutOfRangeException(nameof(bounds), bounds, "Radio-button bounds are too small.");
448
449 return bounds;
450 }
451}
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.
virtual DirectComposite Add(IDirectCompositeChild child, bool keepCurrentOffset=true, Vector2? explicitLocalOffsetPx=null)
Adds a composite-compatible direct drawable as a child.
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 SetColor(Color color)
Sets the base color used for both fill and stroke (unless a distinct border color is specified).
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 SetFont(SKTypeface typeface, float size, float? minSize=null)
Sets the font face and size (and optional minimum size for auto-shrink).
Definition TextBlock.cs:394
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 SetColors(SKColor fg, SKColor bg)
Sets the foreground (text) and background colors using Skia colors.
Definition TextBlock.cs:410
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 ...
Coordinates mutually exclusive RadioButtonWidget instances.
RadioButtonWidget? SelectedButton
Gets the currently selected radio button, or null.
Provides a radio-button control with optional mutually exclusive grouping.
RadioButtonWidget(RenderSurfaceHostBase renderSurfaceHost, SceneLayer sceneLayer, Rectangle bounds, string text, RadioButtonGroup? group=null, bool isSelected=false, string? nickname=null)
Creates a scene-layer radio button.
RadioButtonWidget SetText(string text)
Changes the label text.
override void OnPointerClick(WidgetPointerEventArgs args)
Called when a pointer click is dispatched to the widget.
RadioButtonWidget(RenderSurfaceHostBase renderSurfaceHost, View view, Rectangle bounds, string text, RadioButtonGroup? group=null, bool isSelected=false, string? nickname=null)
Creates a view-level radio button.
override void OnPointerEnter(WidgetPointerEventArgs args)
Called when a pointer enters the widget bounds.
TextBlock Label
Gets the label displayed beside the radio button.
override void ProcessShown()
Performs processing when the widget is shown before notification callbacks are invoked.
RadioButtonWidget SetRadioButtonZOrder(int zOrder)
Sets the base Z-order used by the radio-button visuals.
override void OnKeyboardInput(WidgetKeyboardEventArgs args)
Called when keyboard input is routed to the widget.
Action< bool >? SelectionChanged
Occurs when IsSelected changes.
RadioButtonGroup? Group
Gets or sets the group that coordinates this radio button.
override void Dispose()
Disposes the composite and every child it owns.
bool IsSelected
Gets or sets whether the radio button is selected.
override void OnPointerLeave(WidgetPointerEventArgs args)
Called when a pointer leaves the widget bounds.
DirectRectangle Ring
Gets the outer radio-button ring.
RadioButtonWidget SetTextColor(SKColor color)
Sets the label text color.
DirectRectangle Dot
Gets the inner selection dot.
RadioButtonWidget SetColors(Color normal, Color hover, Color dot)
Sets the normal, hover, and selected-dot colors.
RadioButtonWidget SetSelected(bool isSelected)
Sets the selection state.
RadioButtonWidget Select()
Selects this button and clears any other selection in its group.
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 pointer-based widget callbacks.
bool IsPrimaryButton
Gets whether the pointer event represents a primary-button interaction.
@ 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.
KeyAction
Represents the type of action performed on a keyboard key, indicating whether the key was pressed,...
Definition KeyAction.cs:10