feat: basic sdl3 basic windowing
This commit is contained in:
284
Platforms/Desktop/Engine.Integration.SDL3/SdlWindow.cs
Normal file
284
Platforms/Desktop/Engine.Integration.SDL3/SdlWindow.cs
Normal file
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
|
||||
using Engine.Core;
|
||||
|
||||
using SDL3;
|
||||
|
||||
namespace Engine.Integration.SDL3;
|
||||
|
||||
public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniverse, IExitUniverse
|
||||
{
|
||||
public Event<IWindow, IWindow.StateChangedArguments> OnStateChanged { get; } = new();
|
||||
public Event<IWindow, IWindow.FocusStateChangedArguments> OnFocusStateChanged { get; } = new();
|
||||
public Event<IWindow, IWindow.ModeChangedArguments> OnModeChanged { get; } = new();
|
||||
public Event<IWindow, IWindow.ShowStateChangedArguments> OnShowStateChanged { get; } = new();
|
||||
public Event<IWindow, IWindow.ResizeModeChangedArguments> OnResizeModeChanged { get; } = new();
|
||||
|
||||
public Event<IWindow, IWindow.TitleChangedArguments> OnTitleChanged { get; } = new();
|
||||
public Event<IWindow, IWindow.TargetFpsChangedArguments> OnTargetFpsChanged { get; } = new();
|
||||
public Event<IWindow, IWindow.SizeChangedArguments> OnSizeChanged { get; } = new();
|
||||
public Event<IWindow, IWindow.BackgroundColorChangedArguments> OnBackgroundColorChanged { get; } = new();
|
||||
|
||||
private IntPtr window = IntPtr.Zero;
|
||||
private IntPtr renderer = IntPtr.Zero;
|
||||
|
||||
private ulong nextRenderTick = 0;
|
||||
private ulong renderInterval = (ulong)(1f / 60f * 1000f);
|
||||
|
||||
public uint TargetFps
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value == field)
|
||||
return;
|
||||
|
||||
uint previousTargetFps = field;
|
||||
field = value;
|
||||
renderInterval = value == 0 ? 0 : (ulong)(1f / value * 1000f);
|
||||
OnTargetFpsChanged.Invoke(this, new(previousTargetFps));
|
||||
}
|
||||
} = 0;
|
||||
|
||||
public WindowState State
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value == field)
|
||||
return;
|
||||
|
||||
switch (value)
|
||||
{
|
||||
case WindowState.Open: OpenWindow(); break;
|
||||
case WindowState.Closed: default: CloseWindow(); break;
|
||||
}
|
||||
|
||||
WindowState previousState = field;
|
||||
field = value;
|
||||
OnStateChanged.Invoke(this, new(previousState));
|
||||
}
|
||||
} = WindowState.Open;
|
||||
|
||||
public WindowFocusState FocusState
|
||||
{
|
||||
get => field;
|
||||
private set
|
||||
{
|
||||
if (value == field)
|
||||
return;
|
||||
|
||||
WindowFocusState previousFocusState = field;
|
||||
field = value;
|
||||
OnFocusStateChanged.Invoke(this, new(previousFocusState));
|
||||
}
|
||||
} = WindowFocusState.Unfocused;
|
||||
|
||||
public WindowMode Mode
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value == field)
|
||||
return;
|
||||
|
||||
if (value is WindowMode.Windowed or WindowMode.BorderlessWindowed)
|
||||
{
|
||||
SDL.SetWindowFullscreen(window, false);
|
||||
SDL.SyncWindow(window);
|
||||
}
|
||||
|
||||
if (window != IntPtr.Zero)
|
||||
switch (value)
|
||||
{
|
||||
case WindowMode.Windowed: SDL.SetWindowBordered(window, true); break;
|
||||
case WindowMode.BorderlessWindowed: SDL.SetWindowBordered(window, false); break;
|
||||
case WindowMode.BorderlessFullscreen: SDL.SetWindowFullscreen(window, true); break;
|
||||
case WindowMode.Fullscreen: SDL.SetWindowFullscreen(window, true); break;
|
||||
}
|
||||
|
||||
SDL.SyncWindow(window);
|
||||
|
||||
WindowMode previousMode = field;
|
||||
field = value;
|
||||
OnModeChanged.Invoke(this, new(previousMode));
|
||||
}
|
||||
} = WindowMode.Fullscreen;
|
||||
|
||||
public WindowShowState ShowState
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value == field)
|
||||
return;
|
||||
|
||||
if (window != IntPtr.Zero)
|
||||
switch (value)
|
||||
{
|
||||
case WindowShowState.Normal: SDL.RestoreWindow(window); break;
|
||||
case WindowShowState.Minimized: SDL.MinimizeWindow(window); break;
|
||||
case WindowShowState.Maximized: SDL.MaximizeWindow(window); break;
|
||||
}
|
||||
|
||||
WindowShowState previousShowState = field;
|
||||
field = value;
|
||||
OnShowStateChanged.Invoke(this, new(previousShowState));
|
||||
}
|
||||
} = WindowShowState.Normal;
|
||||
|
||||
public WindowResizeMode ResizeMode
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value == field)
|
||||
return;
|
||||
|
||||
if (window != IntPtr.Zero)
|
||||
switch (value)
|
||||
{
|
||||
case WindowResizeMode.Fixed:
|
||||
SDL.SetWindowResizable(window, false);
|
||||
break;
|
||||
case WindowResizeMode.Resizable:
|
||||
case WindowResizeMode.AspectLocked:
|
||||
SDL.SetWindowResizable(window, true);
|
||||
break;
|
||||
}
|
||||
|
||||
WindowResizeMode previousResizeMode = field;
|
||||
field = value;
|
||||
OnResizeModeChanged.Invoke(this, new(previousResizeMode));
|
||||
}
|
||||
} = WindowResizeMode.Fixed;
|
||||
|
||||
public ColorRGB BackgroundColor
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value == field)
|
||||
return;
|
||||
|
||||
ColorRGB previousBackgroundColor = field;
|
||||
field = value;
|
||||
OnBackgroundColorChanged.Invoke(this, new(previousBackgroundColor));
|
||||
}
|
||||
} = new ColorRGB(35, 20, 35);
|
||||
|
||||
public string Title
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value == field)
|
||||
return;
|
||||
|
||||
string previousTitle = field;
|
||||
field = value;
|
||||
OnTitleChanged.Invoke(this, new(previousTitle));
|
||||
}
|
||||
} = nameof(Sdl3Window);
|
||||
|
||||
public Vector2DInt Size
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
if (value == field)
|
||||
return;
|
||||
|
||||
if (value.Max(Vector2DInt.One) == Vector2DInt.One)
|
||||
throw new Exception($"Window size can not be smaller than 1x1");
|
||||
|
||||
Vector2DInt previousSize = field;
|
||||
field = value;
|
||||
OnSizeChanged.Invoke(this, new(previousSize));
|
||||
}
|
||||
} = Vector2DInt.Zero;
|
||||
|
||||
public void Update()
|
||||
{
|
||||
while (SDL.PollEvent(out SDL.Event e))
|
||||
ProcessInternalEvents((SDL.EventType)e.Type);
|
||||
|
||||
ulong currentTick = SDL.GetTicks();
|
||||
|
||||
if (currentTick >= nextRenderTick)
|
||||
{
|
||||
nextRenderTick = currentTick + renderInterval;
|
||||
|
||||
SDL.SetRenderDrawColor(renderer, BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, 255);
|
||||
SDL.RenderClear(renderer);
|
||||
|
||||
// TODO Render
|
||||
|
||||
SDL.RenderPresent(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
public void ExitUniverse(IUniverse universe) => CloseWindow();
|
||||
public void EnterUniverse(IUniverse universe)
|
||||
{
|
||||
if (State == WindowState.Open)
|
||||
OpenWindow();
|
||||
}
|
||||
|
||||
private void OpenWindow()
|
||||
{
|
||||
if (!UniverseObject.IsInUniverse)
|
||||
throw new Exception($"{UniverseObject.Name} is not in a universe. The window can not be opened.");
|
||||
|
||||
SDL.WindowFlags windowFlags = 0;
|
||||
switch (Mode)
|
||||
{
|
||||
case WindowMode.Windowed: break;
|
||||
case WindowMode.Fullscreen: windowFlags |= SDL.WindowFlags.Fullscreen; break;
|
||||
case WindowMode.BorderlessWindowed: windowFlags |= SDL.WindowFlags.Borderless; break;
|
||||
case WindowMode.BorderlessFullscreen: windowFlags |= SDL.WindowFlags.Fullscreen | SDL.WindowFlags.Borderless; break;
|
||||
}
|
||||
|
||||
if (!SDL.Init(SDL.InitFlags.Video))
|
||||
throw new Exception($"SDL_Init failed: {SDL.GetError()}");
|
||||
|
||||
if (!SDL.CreateWindowAndRenderer(Title, Size.X, Size.Y, windowFlags, out window, out renderer))
|
||||
throw new Exception($"SDL_CreateWindowAndRenderer failed: {SDL.GetError()}");
|
||||
}
|
||||
|
||||
private void CloseWindow()
|
||||
{
|
||||
if (renderer != IntPtr.Zero) SDL.DestroyRenderer(renderer);
|
||||
if (window != IntPtr.Zero) SDL.DestroyWindow(window);
|
||||
|
||||
renderer = IntPtr.Zero;
|
||||
window = IntPtr.Zero;
|
||||
}
|
||||
|
||||
private void ProcessInternalEvents(SDL.EventType eventType)
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case SDL.EventType.WindowRestored: ShowState = WindowShowState.Normal; break;
|
||||
case SDL.EventType.WindowMaximized: ShowState = WindowShowState.Maximized; break;
|
||||
case SDL.EventType.WindowMinimized: ShowState = WindowShowState.Minimized; break;
|
||||
|
||||
case SDL.EventType.WindowFocusGained: FocusState = WindowFocusState.Focused; break;
|
||||
case SDL.EventType.WindowFocusLost: FocusState = WindowFocusState.Unfocused; break;
|
||||
|
||||
case SDL.EventType.Quit:
|
||||
State = WindowState.Closed;
|
||||
BehaviourController.RemoveBehaviour(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CloseWindow();
|
||||
|
||||
// SDL.Quit(); // TODO This possibly prevents multi window
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Engine.Core;
|
||||
|
||||
namespace Engine.Integration.SDL3;
|
||||
|
||||
public interface IWindow : IBehaviour
|
||||
{
|
||||
Event<IWindow, StateChangedArguments> OnStateChanged { get; }
|
||||
Event<IWindow, FocusStateChangedArguments> OnFocusStateChanged { get; }
|
||||
Event<IWindow, ModeChangedArguments> OnModeChanged { get; }
|
||||
Event<IWindow, ShowStateChangedArguments> OnShowStateChanged { get; }
|
||||
Event<IWindow, ResizeModeChangedArguments> OnResizeModeChanged { get; }
|
||||
|
||||
Event<IWindow, TitleChangedArguments> OnTitleChanged { get; }
|
||||
Event<IWindow, TargetFpsChangedArguments> OnTargetFpsChanged { get; }
|
||||
Event<IWindow, SizeChangedArguments> OnSizeChanged { get; }
|
||||
Event<IWindow, BackgroundColorChangedArguments> OnBackgroundColorChanged { get; }
|
||||
|
||||
WindowState State { get; set; }
|
||||
WindowFocusState FocusState { get; }
|
||||
WindowMode Mode { get; set; }
|
||||
WindowShowState ShowState { get; set; }
|
||||
WindowResizeMode ResizeMode { get; set; }
|
||||
|
||||
string Title { get; set; }
|
||||
uint TargetFps { get; set; }
|
||||
Vector2DInt Size { get; set; }
|
||||
ColorRGB BackgroundColor { get; set; }
|
||||
|
||||
readonly record struct StateChangedArguments(WindowState PreviousState);
|
||||
readonly record struct FocusStateChangedArguments(WindowFocusState PreviousFocusState);
|
||||
readonly record struct ModeChangedArguments(WindowMode PreviousMode);
|
||||
readonly record struct ShowStateChangedArguments(WindowShowState PreviousShowState);
|
||||
readonly record struct ResizeModeChangedArguments(WindowResizeMode PreviousResizeMode);
|
||||
readonly record struct TitleChangedArguments(string PreviousTitle);
|
||||
readonly record struct TargetFpsChangedArguments(uint PreviousTargetFps);
|
||||
readonly record struct SizeChangedArguments(Vector2DInt PreviousSize);
|
||||
readonly record struct BackgroundColorChangedArguments(ColorRGB PreviousColor);
|
||||
}
|
||||
|
||||
public enum WindowFocusState
|
||||
{
|
||||
Focused,
|
||||
Unfocused
|
||||
}
|
||||
|
||||
public enum WindowState
|
||||
{
|
||||
Open,
|
||||
Closed
|
||||
}
|
||||
|
||||
public enum WindowMode
|
||||
{
|
||||
Windowed,
|
||||
Fullscreen,
|
||||
BorderlessFullscreen,
|
||||
BorderlessWindowed
|
||||
}
|
||||
|
||||
public enum WindowShowState
|
||||
{
|
||||
Normal,
|
||||
Minimized,
|
||||
Maximized
|
||||
}
|
||||
|
||||
public enum WindowResizeMode
|
||||
{
|
||||
Fixed,
|
||||
Resizable,
|
||||
AspectLocked
|
||||
}
|
||||
Reference in New Issue
Block a user