diff --git a/Platforms/Desktop/Engine.Integration.SDL3/SdlWindow.cs b/Platforms/Desktop/Engine.Integration.SDL3/SdlWindow.cs new file mode 100644 index 0000000..beeb3e0 --- /dev/null +++ b/Platforms/Desktop/Engine.Integration.SDL3/SdlWindow.cs @@ -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 OnStateChanged { get; } = new(); + public Event OnFocusStateChanged { get; } = new(); + public Event OnModeChanged { get; } = new(); + public Event OnShowStateChanged { get; } = new(); + public Event OnResizeModeChanged { get; } = new(); + + public Event OnTitleChanged { get; } = new(); + public Event OnTargetFpsChanged { get; } = new(); + public Event OnSizeChanged { get; } = new(); + public Event 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); + } +} diff --git a/Platforms/Desktop/Engine.Integration.SDL3/Windowing/IWindow.cs b/Platforms/Desktop/Engine.Integration.SDL3/Windowing/IWindow.cs new file mode 100644 index 0000000..f6b54e5 --- /dev/null +++ b/Platforms/Desktop/Engine.Integration.SDL3/Windowing/IWindow.cs @@ -0,0 +1,72 @@ +using Engine.Core; + +namespace Engine.Integration.SDL3; + +public interface IWindow : IBehaviour +{ + Event OnStateChanged { get; } + Event OnFocusStateChanged { get; } + Event OnModeChanged { get; } + Event OnShowStateChanged { get; } + Event OnResizeModeChanged { get; } + + Event OnTitleChanged { get; } + Event OnTargetFpsChanged { get; } + Event OnSizeChanged { get; } + Event 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 +} diff --git a/Platforms/Desktop/Program.cs b/Platforms/Desktop/Program.cs index 56db350..fcad1d1 100644 --- a/Platforms/Desktop/Program.cs +++ b/Platforms/Desktop/Program.cs @@ -3,6 +3,7 @@ using Engine.Core; using Engine.Core.Debug; using Engine.Core.Serialization; +using Engine.Integration.SDL3; using Engine.Serializers.Yaml; using Engine.Systems.Network; using Engine.Systems.Time; @@ -22,6 +23,9 @@ universe.InstantiateUniverseObject().SetUniverseObject("Logger") IUniverseObject desktopParent = universe.InstantiateUniverseObject().SetUniverseObject("Desktop"); +// universe.InstantiateUniverseObject().SetUniverseObject("Desktop Inputs", desktopParent) +// .BehaviourController.AddBehaviour(); + universe.InstantiateUniverseObject().SetUniverseObject("Visual Managers", desktopParent) .BehaviourController.AddBehaviour(); @@ -36,8 +40,38 @@ MyUniverse.Shared.UniverseSource.ApplyUniverse(universe); desktopParent.AddChild(universe.InstantiateUniverseObject().SetUniverseObject("Triangle Batcher").BehaviourController.AddBehaviour().UniverseObject); +Sdl3Window window = Engine.Core.Factory.BehaviourFactory.Instantiate(); +window.Title = "MyUniverse"; +window.Size = new(800, 600); +window.Mode = WindowMode.BorderlessWindowed; + +IUniverseObject windowUO = universe.InstantiateUniverseObject().SetUniverseObject("SDL3 Window", desktopParent); +windowUO.BehaviourController.AddBehaviour(window); + +// Testing Start +TickerStopwatch tickerStopwatch = windowUO.BehaviourController.AddBehaviour(); +tickerStopwatch.Period = 5f; +tickerStopwatch.OnTick.AddListener(sender => +{ + if (window.IsInitialized) + windowUO.BehaviourController.RemoveBehaviour(window); + else + windowUO.BehaviourController.AddBehaviour(window); +}); +tickerStopwatch.Start(); +// Testing End + universe.Initialize(); +// window.OnRender.AddListener((sender, args) => universe.Draw()); +// window.OnUpdate.AddListener((sender, args) => +// { +// timeSinceStart += args.TimeSpan; +// universe.Update(new(timeSinceStart, args.TimeSpan)); +// }); + +// window.Run(); + DateTime lastRun = DateTime.UtcNow; TimeSpan timeSinceStart = new(0); @@ -52,3 +86,21 @@ while (true) lastRun = now; System.Threading.Thread.Sleep(1); } + +// DateTime lastRun = DateTime.UtcNow; +// TimeSpan interval = new(0, 0, 0, 0, 16); +// TimeSpan timeSinceStart = new(0); + +// universe.Initialize(); + +// while (true) +// { +// if (lastRun + interval <= DateTime.UtcNow) +// { +// lastRun += interval; +// timeSinceStart += interval; +// universe.Update(new(timeSinceStart, interval)); +// } + +// Thread.Sleep(1); +// }