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
SplashScreen.cs
1using System.Drawing;
7using Microsoft.Extensions.Logging;
8using SkiaSharp;
10
12
32public sealed class SplashScreen : WidgetBase
33{
34 private static readonly ILogger<SplashScreen> Logger =
35 EngineLogger.GetLogger<SplashScreen>();
36
40 public enum State
41 {
46
51
56
61 }
62
66 public static readonly string SplashImageNickname = "__gondwana_splash__";
67
68 private readonly View _view;
69 private Timer? _holdTimer;
70 private TaskCompletionSource? _holdDurationCompletionSource;
71 private bool _disposed;
72
73 #region properties
74
78 public DirectImage Image { get; private set; }
79
83 public State CurrentState { get; private set; }
84
88 public float FadeInSec { get; private set; }
89
94 public float HoldSec { get; private set; }
95
99 public float FadeOutSec { get; private set; }
100
101 #endregion properties
102
103 #region factory / constructor
104
105 private SplashScreen(DirectImage image,
106 float fadeInSec,
107 float holdSec,
108 float fadeOutSec,
109 Action? onHoldingSync,
110 Func<Task>? onHoldingAsync,
111 Action? onSplashCompleted)
112 : base(image.RenderSurfaceHost,
114 PointF.Empty,
115 SplashImageNickname + Guid.NewGuid())
116 {
117 Image = image;
118 _view = image.View
119 ?? throw new InvalidOperationException("Splash image must be attached to a view.");
120 _view.Viewport.TargetRectChanged += OnViewportTargetRectChanged;
121 CurrentState = State.Hidden;
122
123 FadeInSec = fadeInSec;
124 HoldSec = holdSec;
125 FadeOutSec = fadeOutSec;
126
127 Add(Image);
128
129 StartFadeIn(
130 onHoldingSync,
131 onHoldingAsync,
132 onSplashCompleted);
133 }
134
197 public static SplashScreen? TryCreate(Stream imageStream,
199 View view,
200 float fadeInSec = 0.45f,
201 float holdSec = 3f,
202 float fadeOutSec = 0.45f,
203 Action? onHoldingSync = null,
204 Func<Task>? onHoldingAsync = null,
205 Action? onSplashCompleted = null)
206 {
207 if (host.ViewManager.Views.Count == 0)
208 return null;
209
210 using var bitmap = SKBitmap.Decode(imageStream);
211
212 var sourceImage = bitmap is null
213 ? null
214 : SKImage.FromBitmap(bitmap);
215
216 if (sourceImage is null)
217 return null;
218
219 var viewport = view.Viewport.TargetRectPx;
220
221 var screenBounds = viewport;
222
223 var image = new DirectImage(
224 sourceImage,
225 host,
226 view,
227 screenBounds,
230
231 // DirectImage does not own the SKImage lifetime.
232 image.Disposing += (_, _) => sourceImage.Dispose();
233
234 image.ZOrder = int.MaxValue;
235 image.Opacity = 0f;
236
237 return new SplashScreen(
238 image,
239 fadeInSec,
240 holdSec,
241 fadeOutSec,
242 onHoldingSync,
243 onHoldingAsync,
244 onSplashCompleted);
245 }
246
247 #endregion factory / constructor
248
252 public override void Dispose()
253 {
254 if (_disposed)
255 return;
256
257 _disposed = true;
258
259 _view.Viewport.TargetRectChanged -= OnViewportTargetRectChanged;
260
261 _holdTimer?.Dispose();
262 _holdTimer = null;
263
264 _holdDurationCompletionSource?.TrySetCanceled();
265 _holdDurationCompletionSource = null;
266
267 base.Dispose();
268 }
269
270 private void OnViewportTargetRectChanged(ViewportResizedEventArgs args)
271 {
272 if (_disposed)
273 return;
274
275 Image.ScreenBounds = args.NewRect;
276 }
277
278 #region splash control
279
280 private void StartFadeIn(Action? onHoldingSync, Func<Task>? onHoldingAsync, Action? onSplashCompleted)
281 {
282 Logger.LogDebug("Starting splash screen fade-in");
283
284 CurrentState = State.FadingIn;
285
286 EventHandler<DirectDrawingBase>? fadeInHandler = null;
287
288 fadeInHandler = (_, _) =>
289 {
290 Image.FadeToCompleted -= fadeInHandler;
291
292 if (_disposed)
293 return;
294
295 CurrentState = State.Holding;
296
297 _ = RunHoldPhaseAsync(
298 onHoldingSync,
299 onHoldingAsync,
300 onSplashCompleted);
301 };
302
303 Image.FadeToCompleted += fadeInHandler;
304
306 }
307
308 private async Task RunHoldPhaseAsync(
309 Action? onHoldingSync,
310 Func<Task>? onHoldingAsync,
311 Action? onSplashCompleted)
312 {
313 Logger.LogDebug("Starting splash screen hold phase");
314
315 try
316 {
317 var holdDurationTask = WaitForHoldDurationAsync();
318
319 var delegateTask = ExecuteHoldDelegatesAsync(onHoldingSync, onHoldingAsync);
320
321 await Task.WhenAll(
322 holdDurationTask,
323 delegateTask);
324 }
325 catch (OperationCanceledException)
326 when (_disposed)
327 {
328 return;
329 }
330 catch (Exception ex)
331 {
332 Logger.LogError(ex, "Unexpected error during SplashScreen hold phase");
333 }
334 finally
335 {
336 _holdTimer?.Dispose();
337 _holdTimer = null;
338
339 _holdDurationCompletionSource = null;
340 }
341
342 if (_disposed)
343 return;
344
345 Engine.Instance.EngineDispatcher.Post(() =>
346 {
347 if (_disposed)
348 return;
349
350 if (CurrentState != State.Holding)
351 return;
352
353 StartFadeOut(onSplashCompleted);
354 });
355 }
356
357 private Task WaitForHoldDurationAsync()
358 {
359 var completionSource = new TaskCompletionSource(
360 TaskCreationOptions.RunContinuationsAsynchronously);
361
362 _holdDurationCompletionSource =
363 completionSource;
364
365 var timer = Timer.Add(
366 TimerType.PostCycle,
367 TimerCycles.Once,
368 HoldSec);
369
370 _holdTimer = timer;
371
372 void OnHoldTimerElapsed()
373 {
374 timer.Tick -= OnHoldTimerElapsed;
375
376 completionSource.TrySetResult();
377 }
378
379 timer.Tick += OnHoldTimerElapsed;
380
381 return completionSource.Task;
382 }
383
384 private static async Task ExecuteHoldDelegatesAsync(
385 Action? onHoldingSync,
386 Func<Task>? onHoldingAsync)
387 {
388 if (onHoldingSync is null &&
389 onHoldingAsync is null)
390 {
391 return;
392 }
393
394 var dispatcher =
395 Engine.Instance.EngineDispatcher;
396
397 try
398 {
399 await dispatcher.PostAsync(async () =>
400 {
401 Task asyncTask =
402 Task.CompletedTask;
403
404 Task syncTask =
405 Task.CompletedTask;
406
407 if (onHoldingAsync is not null)
408 {
409 Logger.LogDebug("Calling onHoldingAsync in SplashScreen");
410
411 try
412 {
413 asyncTask = onHoldingAsync() ?? Task.CompletedTask;
414 }
415 catch (Exception ex)
416 {
417 asyncTask = Task.FromException(ex);
418 }
419 }
420
421 if (onHoldingSync is not null)
422 {
423 Logger.LogDebug("Calling onHoldingSync in SplashScreen");
424
425 try
426 {
427 onHoldingSync();
428 }
429 catch (Exception ex)
430 {
431 syncTask = Task.FromException(ex);
432 }
433 }
434
435 await Task.WhenAll(asyncTask, syncTask);
436 });
437 }
438 catch (Exception ex)
439 {
440 Logger.LogError(ex, "Error executing SplashScreen hold delegates");
441 }
442 }
443
444 private void StartFadeOut(Action? onSplashCompleted)
445 {
446 Logger.LogDebug("Starting splash screen fade-out");
447
448 CurrentState = State.FadingOut;
449
450 EventHandler<DirectDrawingBase>? fadeOutHandler = null;
451
452 fadeOutHandler = (_, _) =>
453 {
454 Image.FadeToCompleted -= fadeOutHandler;
455 CurrentState = State.Hidden;
456 Dispose();
457 onSplashCompleted?.Invoke();
458 };
459
460 Image.FadeToCompleted += fadeOutHandler;
461 Image.FadeOut(FadeOutSec);
462 }
463
464 #endregion splash control
465}
RenderSurfaceHostBase RenderSurfaceHost
Gets the render surface host shared by the composite and its children.
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.
void Dispose()
Releases all resources used by this DirectDrawingBase instance.
DirectDrawingBase FadeIn(float durationSec)
Fades this direct drawing from its current opacity to fully opaque over the specified duration.
Renders a bitmap or image to a rectangular region with support for scaling modes, source clipping,...
DirectImage SetScaleMode(ScaleMode mode)
Sets how the image is scaled to fit within the destination rectangle.
ScaleMode
Defines how an image is scaled to fit within the destination rectangle.
Represents a base class for hosting a render surface, providing functionality for managing rendering ...
ViewManager ViewManager
Gets the view manager that controls camera positions, viewports, and multi-view rendering for this re...
ReadOnlyCollection< View > Views
Gets a read-only collection of all views currently managed by this view manager. Views are sorted by ...
Represents a rendered view of a scene, combining a camera position with a viewport configuration to c...
Definition View.cs:14
Viewport Viewport
Gets the viewport that defines the screen-space rectangle, zoom level, and other rendering parameters...
Definition View.cs:44
Provides data for the Viewport.TargetRectChanged event, which is raised when a viewport's target rect...
Rectangle NewRect
Gets the new target rectangle after the resize or reposition operation.
Rectangle TargetRectPx
Screen-space rectangle (in SCREEN / RenderSurface pixels) where this view is drawn....
Definition Viewport.cs:35
Represents a high-resolution timer that can trigger events at specified intervals and supports variou...
Definition Timer.cs:16
static Timer Add(string timerID, TimerType type, TimerCycles cycles, double length)
Creates a new timer with the specified parameters and adds it to the collection of active timers.
Definition Timer.cs:137
Represents a full-screen splash screen widget that fades in, holds for a duration,...
float FadeOutSec
Gets the duration of the fade-out animation in seconds.
State
Represents the current state of the splash screen animation.
@ Holding
The splash screen is held at full opacity.
@ FadingOut
The splash screen is fading out from opaque to transparent.
@ FadingIn
The splash screen is fading in from transparent to opaque.
@ Hidden
The splash screen is not visible.
State CurrentState
Gets the current state of the splash screen.
static readonly string SplashImageNickname
The nickname used for the DirectImage overlay that displays the splash.
float HoldSec
Gets the minimum duration for which the splash remains at full opacity, in seconds.
override void Dispose()
Releases the splash overlay and associated resources.
DirectImage Image
Gets the DirectImage overlay that displays the splash image.
static ? SplashScreen TryCreate(Stream imageStream, RenderSurfaceHostBase host, View view, float fadeInSec=0.45f, float holdSec=3f, float fadeOutSec=0.45f, Action? onHoldingSync=null, Func< Task >? onHoldingAsync=null, Action? onSplashCompleted=null)
Attempts to create a new SplashScreen instance from the provided image stream.
float FadeInSec
Gets the duration of the fade-in animation in seconds.
WidgetBase(RenderSurfaceHostBase renderSurfaceHost, DirectDrawingMode mode, PointF anchor=default, string? nickname=null)
Initializes a new instance of the WidgetBase class.
Definition WidgetBase.cs:95
DirectDrawingMode
Specifies where direct drawing operations are applied within the rendering pipeline.
TimerType
Specifies when a Timer should have its Timer.Tick event raised relative to the engine's main update a...
Definition TimerType.cs:22
TimerCycles
Specifies the cycle behavior of a Timer, determining whether it executes once or repeatedly at its co...