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
ToastWidget.cs
1using System.Drawing;
2using System.Numerics;
8using SkiaSharp;
9
11
15public enum ToastTransition
16{
19
22}
23
28{
31
34
37
40}
41
45public enum ToastState
46{
49
52
55
58}
59
70public sealed class ToastWidget : WidgetBase
71{
72 private bool _disposed;
73 private float _transitionDurationSec = 0.3f;
74 private float? _holdDurationSec = 3f;
75 private float _phaseElapsedSec;
76 private long _lastAnimationTick;
77 private Vector2 _phaseStartPosition;
78 private Vector2 _phaseEndPosition;
79 private float _phaseStartOpacity;
80 private float _phaseEndOpacity;
81
85 public event Action? Dismissed;
86
96 RenderSurfaceHostBase renderSurfaceHost,
97 View view,
98 Rectangle targetBounds,
99 string text,
100 string? nickname = null)
101 : base(
102 renderSurfaceHost,
104 targetBounds.Location,
105 nickname)
106 {
107 ValidateBounds(targetBounds);
108
109 TargetBounds = targetBounds;
110
112 Color.FromArgb(232, 35, 38, 45),
113 renderSurfaceHost,
114 view,
115 targetBounds,
116 $"{Nickname}.background")
117 .SetFilled(true)
118 .SetBorderColor(Color.FromArgb(245, 215, 219, 225))
119 .SetStrokeWidth(1f)
121 .SetCornerRadius(6f);
122
123 Label = new TextBlock(
124 renderSurfaceHost,
125 view,
126 targetBounds,
127 $"{Nickname}.label")
128 .SetText(text)
129 .SetFont(SKTypeface.Default, 17f, minSize: 11f)
130 .SetColors(SKColors.White, SKColors.Transparent)
131 .SetAlignment(SKTextAlign.Center, TextBlock.VerticalAlign.Center)
132 .EnableWrapping(true);
133
134 Label.HorizontalPadding = 12f;
135 Label.VerticalPadding = 8f;
136
137 Add(
139 keepCurrentOffset: false,
140 explicitLocalOffsetPx: Vector2.Zero);
141
142 Add(
143 Label,
144 keepCurrentOffset: false,
145 explicitLocalOffsetPx: Vector2.Zero);
146
147 SetToastZOrder(int.MaxValue - 100);
148 SetIsVisible(false);
149 }
150
162 RenderSurfaceHostBase renderSurfaceHost,
163 View view,
164 Size size,
165 string text,
166 WidgetAnchor targetAnchor = WidgetAnchor.TopRight,
167 int marginPx = 16,
168 string? nickname = null)
169 : this(
170 renderSurfaceHost,
171 view,
172 GetAnchoredBounds(view, size, targetAnchor, marginPx),
173 text,
174 nickname)
175 {
176 }
177
182
186 public TextBlock Label { get; }
187
191 public ToastState CurrentState { get; private set; } = ToastState.Hidden;
192
196 public ToastTransition Transition { get; set; } = ToastTransition.Slide;
197
201 public ToastSlideOrigin SlideOrigin { get; set; } = ToastSlideOrigin.Right;
202
207 public Point? SourceLocationPx { get; set; }
208
212 public int SourceOffsetPx { get; set; } = 8;
213
218 {
219 get => _transitionDurationSec;
220 set => _transitionDurationSec = value >= 0f
221 ? value
222 : throw new ArgumentOutOfRangeException(nameof(value));
223 }
224
229 public float? HoldDurationSec
230 {
231 get => _holdDurationSec;
232 set => _holdDurationSec = value is null or >= 0f
233 ? value
234 : throw new ArgumentOutOfRangeException(nameof(value));
235 }
236
240 public EasingKind EntranceEasing { get; set; } = EasingKind.EaseOutCubic;
241
245 public EasingKind ExitEasing { get; set; } = EasingKind.EaseInCubic;
246
250 public bool AnimateDismissal { get; set; } = true;
251
255 public bool DismissOnClick { get; set; } = true;
256
260 public bool DisposeOnDismiss { get; set; } = true;
261
265 public Rectangle TargetBounds { get; private set; }
266
272 {
273 Show();
274 return this;
275 }
276
280 public void Dismiss()
281 {
282 if (_disposed || CurrentState is ToastState.Hidden or ToastState.Exiting)
283 return;
284
286 {
287 CompleteDismissal();
288 return;
289 }
290
291 CurrentState = ToastState.Exiting;
292 BeginPhase(
293 GetPosition(),
295 ? GetSourcePosition()
296 : GetPosition(),
297 GetCurrentOpacity(),
298 Transition == ToastTransition.Fade ? 0f : 1f);
299 }
300
306 public ToastWidget SetTargetBounds(Rectangle targetBounds)
307 {
308 ValidateBounds(targetBounds);
309
310 TargetBounds = targetBounds;
311 Background.ScreenBounds = targetBounds;
312 Label.ScreenBounds = targetBounds;
313 SetPosition(targetBounds.X, targetBounds.Y);
314
315 return this;
316 }
317
323 public ToastWidget SetText(string text)
324 {
325 Label.SetText(text);
326 return this;
327 }
328
334 public ToastWidget SetToastZOrder(int zOrder)
335 {
336 Background.ZOrder = zOrder;
337 Label.ZOrder = zOrder + 1;
338 return this;
339 }
340
342 public override void Update(long tick)
343 {
344 if (_disposed || tick <= _lastAnimationTick)
345 return;
346
347 float elapsedSec = HighResTimer.GetDuration(_lastAnimationTick, tick);
348 _lastAnimationTick = tick;
349
350 base.Update(tick);
351
352 switch (CurrentState)
353 {
354 case ToastState.Entering:
355 AdvanceTransition(elapsedSec, entering: true);
356 break;
357
358 case ToastState.Holding:
359 _phaseElapsedSec += elapsedSec;
360
361 if (HoldDurationSec is { } holdSec &&
362 _phaseElapsedSec >= holdSec)
363 {
364 Dismiss();
365 }
366 break;
367
368 case ToastState.Exiting:
369 AdvanceTransition(elapsedSec, entering: false);
370 break;
371 }
372 }
373
375 public override void Dispose()
376 {
377 if (_disposed)
378 return;
379
380 _disposed = true;
381 Dismissed = null;
382 base.Dispose();
383 }
384
386 protected override void ProcessShown()
387 {
388 base.ProcessShown();
389
390 CurrentState = ToastState.Entering;
391 _lastAnimationTick = HighResTimer.GetCurrentTick();
392
393 Vector2 target = new(TargetBounds.X, TargetBounds.Y);
394 Vector2 source = Transition == ToastTransition.Slide
395 ? GetSourcePosition()
396 : target;
397
398 BeginPhase(
399 source,
400 target,
401 Transition == ToastTransition.Fade ? 0f : 1f,
402 1f);
403
404 if (TransitionDurationSec <= 0f)
405 CompleteEntrance();
406 }
407
409 protected override void ProcessHidden()
410 {
411 CurrentState = ToastState.Hidden;
412 Movement.StopAllMovement();
413 base.ProcessHidden();
414 }
415
417 protected override void OnPointerClick(WidgetPointerEventArgs args)
418 {
419 base.OnPointerClick(args);
420
421 if (!DismissOnClick || !args.IsPrimaryButton)
422 return;
423
424 args.Handled = true;
425 Dismiss();
426 }
427
428 private void BeginPhase(
429 Vector2 startPosition,
430 Vector2 endPosition,
431 float startOpacity,
432 float endOpacity)
433 {
434 _phaseElapsedSec = 0f;
435 _phaseStartPosition = startPosition;
436 _phaseEndPosition = endPosition;
437 _phaseStartOpacity = startOpacity;
438 _phaseEndOpacity = endOpacity;
439
440 SetPosition(startPosition);
441 SetOpacity(startOpacity);
442 }
443
444 private void AdvanceTransition(float elapsedSec, bool entering)
445 {
446 _phaseElapsedSec += elapsedSec;
447
448 float progress = TransitionDurationSec <= 0f
449 ? 1f
450 : Math.Clamp(_phaseElapsedSec / TransitionDurationSec, 0f, 1f);
451
452 Func<float, float> easing = EasingFunctions.From(
453 entering ? EntranceEasing : ExitEasing);
454
455 float eased = Math.Clamp(easing(progress), 0f, 1f);
456
457 SetPosition(Vector2.Lerp(
458 _phaseStartPosition,
459 _phaseEndPosition,
460 eased));
461
463 _phaseStartOpacity
464 + (_phaseEndOpacity - _phaseStartOpacity) * eased);
465
466 if (progress < 1f)
467 return;
468
469 if (entering)
470 CompleteEntrance();
471 else
472 CompleteDismissal();
473 }
474
475 private void CompleteEntrance()
476 {
478 SetOpacity(1f);
479 CurrentState = ToastState.Holding;
480 _phaseElapsedSec = 0f;
481
482 if (HoldDurationSec == 0f)
483 Dismiss();
484 }
485
486 private void CompleteDismissal()
487 {
488 Hide();
489 Dismissed?.Invoke();
490
492 Dispose();
493 }
494
495 private Vector2 GetSourcePosition()
496 {
497 if (SourceLocationPx is { } explicitSource)
498 return new Vector2(explicitSource.X, explicitSource.Y);
499
500 Rectangle viewport = View!.Viewport.TargetRectPx;
501
502 return SlideOrigin switch
503 {
504 ToastSlideOrigin.Top => new Vector2(
505 TargetBounds.X,
506 viewport.Top - TargetBounds.Height - SourceOffsetPx),
507
508 ToastSlideOrigin.Right => new Vector2(
509 viewport.Right + SourceOffsetPx,
510 TargetBounds.Y),
511
512 ToastSlideOrigin.Bottom => new Vector2(
513 TargetBounds.X,
514 viewport.Bottom + SourceOffsetPx),
515
516 ToastSlideOrigin.Left => new Vector2(
517 viewport.Left - TargetBounds.Width - SourceOffsetPx,
518 TargetBounds.Y),
519
520 _ => throw new ArgumentOutOfRangeException()
521 };
522 }
523
524 private float GetCurrentOpacity()
525 {
526 return Children.Count == 0
527 ? 1f
528 : Children[0] is DirectDrawingBase drawing
529 ? drawing.Opacity
530 : 1f;
531 }
532
533 private static Rectangle GetAnchoredBounds(
534 View view,
535 Size size,
536 WidgetAnchor anchor,
537 int marginPx)
538 {
539 ArgumentNullException.ThrowIfNull(view);
540
541 if (size.Width <= 0 || size.Height <= 0)
542 throw new ArgumentOutOfRangeException(nameof(size));
543
544 if (marginPx < 0)
545 throw new ArgumentOutOfRangeException(nameof(marginPx));
546
547 Rectangle viewport = view.Viewport.TargetRectPx;
548
549 int x = anchor switch
550 {
551 WidgetAnchor.TopLeft or WidgetAnchor.CenterLeft or WidgetAnchor.BottomLeft => viewport.Left + marginPx,
552 WidgetAnchor.TopCenter or WidgetAnchor.Center or WidgetAnchor.BottomCenter => viewport.Left + (viewport.Width - size.Width) / 2,
553 WidgetAnchor.TopRight or WidgetAnchor.CenterRight or WidgetAnchor.BottomRight => viewport.Right - size.Width - marginPx,
554 _ => throw new ArgumentOutOfRangeException(nameof(anchor), anchor, null)
555 };
556
557 int y = anchor switch
558 {
559 WidgetAnchor.TopLeft or WidgetAnchor.TopCenter or WidgetAnchor.TopRight => viewport.Top + marginPx,
560 WidgetAnchor.CenterLeft or WidgetAnchor.Center or WidgetAnchor.CenterRight => viewport.Top + (viewport.Height - size.Height) / 2,
561 WidgetAnchor.BottomLeft or WidgetAnchor.BottomCenter or WidgetAnchor.BottomRight => viewport.Bottom - size.Height - marginPx,
562 _ => throw new ArgumentOutOfRangeException(nameof(anchor), anchor, null)
563 };
564
565 return new Rectangle(x, y, size.Width, size.Height);
566 }
567
568 private static void ValidateBounds(Rectangle bounds)
569 {
570 if (bounds.Width <= 0 || bounds.Height <= 0)
571 throw new ArgumentOutOfRangeException(nameof(bounds), "Toast bounds must have a positive width and height.");
572 }
573}
ReadOnlyCollection< IDirectCompositeChild > Children
Gets the children owned by the composite.
DirectComposite SetOpacity(float opacity)
Applies one opacity to every descendant.
DirectComposite SetIsVisible(bool visible)
Applies one visibility value to every descendant.
View? View
Gets the view shared by all children in view mode.
MovementController Movement
Gets the movement controller used to move the composite anchor.
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.
void SetPosition(Vector2 position)
Sets the composite anchor and repositions all children from their stored offsets.
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.
StrokeAlign
Defines how the stroke (outline) is positioned relative to the rectangle's boundary.
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.
DirectRectangle SetStrokeAlign(StrokeAlign align)
Sets how the rectangle's stroke is positioned relative to its bounds.
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
VerticalAlign
Vertical alignment options for multi-line text within the control's bounds.
Definition TextBlock.cs:49
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 ...
Viewport Viewport
Gets the viewport that defines the screen-space rectangle, zoom level, and other rendering parameters...
Definition View.cs:44
Rectangle TargetRectPx
Screen-space rectangle (in SCREEN / RenderSurface pixels) where this view is drawn....
Definition Viewport.cs:35
EasingKind EntranceEasing
Gets or sets the easing used by the entrance transition.
Action? Dismissed
Occurs after the toast completes dismissal and is hidden.
bool DisposeOnDismiss
Gets or sets whether the toast disposes itself after dismissal.
ToastWidget SetTargetBounds(Rectangle targetBounds)
Changes the target bounds used the next time the toast is shown.
float? HoldDurationSec
Gets or sets how long the toast remains at its target before automatic dismissal. Null disables autom...
bool AnimateDismissal
Gets or sets whether dismissal reverses the configured entrance transition.
bool DismissOnClick
Gets or sets whether a primary-pointer click dismisses the toast.
ToastWidget(RenderSurfaceHostBase renderSurfaceHost, View view, Rectangle targetBounds, string text, string? nickname=null)
Initializes a toast with explicit target bounds in absolute screen pixels.
void Dismiss()
Begins dismissal. Repeated calls while exiting or hidden have no effect.
Rectangle TargetBounds
Gets the target bounds at which the toast rests, in absolute screen pixels.
override void OnPointerClick(WidgetPointerEventArgs args)
Called when a pointer click is dispatched to the widget.
EasingKind ExitEasing
Gets or sets the easing used by the dismissal transition.
ToastWidget ShowToast()
Shows the toast and begins its entrance transition.
ToastSlideOrigin SlideOrigin
Gets or sets the edge used by slide transitions.
Point? SourceLocationPx
Gets or sets an optional explicit slide source top-left in absolute screen pixels....
ToastState CurrentState
Gets the current toast lifecycle state.
float TransitionDurationSec
Gets or sets the transition duration in seconds. Zero performs the transition immediately.
ToastWidget SetToastZOrder(int zOrder)
Sets the background Z-order and places the label immediately above it.
DirectRectangle Background
Gets the rectangle used for the toast background and border.
TextBlock Label
Gets the text drawing used for the toast message.
override void ProcessShown()
Performs processing when the widget is shown before notification callbacks are invoked.
ToastWidget(RenderSurfaceHostBase renderSurfaceHost, View view, Size size, string text, WidgetAnchor targetAnchor=WidgetAnchor.TopRight, int marginPx=16, string? nickname=null)
Initializes a toast positioned from a standard anchor inside the view.
override void Update(long tick)
Advances composite movement.This method uses the tick to calculate elapsed time and advance the movem...
override void ProcessHidden()
Performs processing when the widget is hidden before notification callbacks are invoked.
ToastWidget SetText(string text)
Sets the toast message.
ToastTransition Transition
Gets or sets the entrance and animated-dismissal transition.
int SourceOffsetPx
Gets or sets the gap between a calculated slide source and the view edge.
override void Dispose()
Disposes the composite and every child it owns.
WidgetBase Show()
Shows the widget and registers it for input routing.
WidgetBase(RenderSurfaceHostBase renderSurfaceHost, DirectDrawingMode mode, PointF anchor=default, string? nickname=null)
Initializes a new instance of the WidgetBase class.
Definition WidgetBase.cs:95
WidgetBase Hide()
Hides the widget and releases any routed input state.
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.
EasingKind
Enumeration of all supported easing functions for animation and movement interpolation....
Definition EasingKind.cs:9
ToastSlideOrigin
Defines the view edge from which a sliding toast enters.
@ Bottom
The toast enters from below the view.
@ Right
The toast enters from the right of the view.
@ Left
The toast enters from the left of the view.
@ Top
The toast enters from above the view.
ToastState
Represents the lifecycle state of a ToastWidget.
@ Exiting
The toast is moving or fading out of view.
@ Holding
The toast is resting at its target bounds.
@ Hidden
The toast is not being shown.
@ Entering
The toast is moving or fading into view.
ToastTransition
Defines how a toast enters its view.
@ Fade
The toast remains at its target bounds and fades from transparent.
@ Slide
The toast moves from outside the view to its target bounds.
WidgetAnchor
Identifies one of the nine common anchor points on a rectangle.