using System; using System.Runtime.InteropServices; using SDL2; using SlatedGameToolkit.Framework.DataTypes; using SlatedGameToolkit.Framework.Exceptions; using SlatedGameToolkit.Framework.Graphics.OpenGL; namespace SlatedGameToolkit.Framework.Graphics.Window { /// /// A delegate to determine the type of interaction with a window. /// The method should be extremely lightweight as it can be called many times. /// /// The window coordinates that are being interacted with. /// The region type. public delegate WindowRegion WindowRegionHit(FloatVector2 hitPoint); /// /// A delegate that represents a function to be called on a window resize. /// /// The new width. /// The new height. public delegate void WindowResize(int width, int height); /// /// A delegate that represents a function to be called when a window event occurs. /// public delegate void WindowOperation(); /// /// A handle for a window. /// This object itself is not thread safe. /// public sealed class WindowContext : IDisposable { /// /// The pointer referencing the SDL window. /// private readonly IntPtr window; /// /// The pointer referencing the OpenGL context. /// private readonly IntPtr glContext; public event WindowOperation focusGainedEvent, focusLostEvent; /// /// Invoked when this window resizes. /// public event WindowResize resizeEvent; /// /// Event for when the window is being interacted with. /// public event WindowRegionHit windowRegionHitEvent; internal readonly GLClearColour clearColour; internal readonly GLClear clear; internal readonly GLViewport viewport; /// /// Whether or not to show this window. /// /// True for showing. public bool Shown { set { if (value) { SDL.SDL_ShowWindow(window); } else { SDL.SDL_HideWindow(window); } } get { return ((SDL.SDL_WindowFlags) Enum.Parse(typeof(SDL.SDL_WindowFlags), SDL.SDL_GetWindowFlags(window).ToString())).HasFlag(SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN); } } /// /// Whether or not to attempt to perform vertical synchronization. /// Will use adaptive sync if supported. /// If this window is not the current active window, retrieving and setting requires a context switch. /// If a context switch was nessecary, will automatically switch back to previous once operation is complete. /// If neither normal VSync or adaptive sync are available, enabling will throw an OptionalSDLException. /// /// public bool VSync { get { IntPtr currentContext = SDL.SDL_GL_GetCurrentContext(); IntPtr currentWindow = SDL.SDL_GL_GetCurrentWindow(); bool diff = false; if (currentContext != glContext || currentWindow != window) { MakeCurrent(); diff = true; } bool vSync = SDL.SDL_GL_GetSwapInterval() != 0; if (diff) SDL.SDL_GL_MakeCurrent(currentWindow, currentContext); return vSync; } set { IntPtr currentContext = SDL.SDL_GL_GetCurrentContext(); IntPtr currentWindow = SDL.SDL_GL_GetCurrentWindow(); bool diff = false; if (currentContext != glContext || currentWindow != window) { MakeCurrent(); diff = true; } if (SDL.SDL_GL_SetSwapInterval(value ? -1 : 0) < 0) { if (SDL.SDL_GL_SetSwapInterval(value ? 1 : 0) < 0) { throw new OptionalSDLException(); } } if (diff) SDL.SDL_GL_MakeCurrent(currentWindow, currentContext); } } /// /// The title displayed on the window. /// /// A string that represents whats to be displayed as the title of the window. public string WindowTitle { set { SDL.SDL_SetWindowTitle(window, value); } get { return SDL.SDL_GetWindowTitle(window); } } /// /// Whether or not to display the default window border. /// /// True if displaying borders. public bool WindowBordered { set { SDL.SDL_SetWindowBordered(window, value ? SDL.SDL_bool.SDL_TRUE : SDL.SDL_bool.SDL_FALSE); } get { int top, bottom, left, right; int errorCode = SDL.SDL_GetWindowBordersSize(window, out top, out left, out bottom, out right); if (errorCode < 0) throw new OptionalSDLException(); return top > 0 || bottom > 0 || left > 0 || right > 0; } } /// /// Whether or not this window should be resizeable. /// /// True if resizeable. public bool WindowResizable { set { SDL.SDL_SetWindowResizable(window, value ? SDL.SDL_bool.SDL_TRUE : SDL.SDL_bool.SDL_FALSE); } get { return ((SDL.SDL_WindowFlags) Enum.Parse(typeof(SDL.SDL_WindowFlags), SDL.SDL_GetWindowFlags(window).ToString())).HasFlag(SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE); } } /// /// The boundaries of the window represent both the bottom left corner relative to the screen, /// as well as the current width and height of this window. /// /// Rectangle representing the boundaries of the window. public FloatRectangle WindowBoundaries { set { SDL.SDL_SetWindowPosition(window, (int)value.X1, (int)value.Y1); SDL.SDL_SetWindowSize(window, (int)value.Width, (int)value.Height); } get { int x, y, width, height; SDL.SDL_GetWindowSize(window, out x, out y); SDL.SDL_GetWindowPosition(window, out width, out height); FloatRectangle rectangle = new FloatRectangle(); rectangle.X1 = x; rectangle.Y2 = y; rectangle.Width = width; rectangle.Height = height; return rectangle; } } /// /// The maximum size the window can be at any given time. /// /// A vector that represents the maximum width and height of the window with the x and y components respectively. public FloatVector2 MaximumSize { set { SDL.SDL_SetWindowMaximumSize(window, (int)value.x, (int)value.y); } get { int width, height; SDL.SDL_GetWindowMaximumSize(window, out width, out height); FloatVector2 maxSize = new FloatVector2(); maxSize.x = width; maxSize.y = height; return maxSize; } } /// /// The minimum size the window can be at any given time. /// /// A vector that represents the minimum width and height of the window with the x and y components respectively. public FloatVector2 MinimumSize { set { SDL.SDL_SetWindowMinimumSize(window, (int)value.x, (int)value.y); } get { int width, height; SDL.SDL_GetWindowMinimumSize(window, out width, out height); FloatVector2 maxSize = new FloatVector2(); maxSize.x = width; maxSize.y = height; return maxSize; } } /// /// The opacity of the window itself. /// This is not supported on all targetted platforms, and therefore, can throw an OptionalSDLException for when this is the case. /// /// A float with bounds of [0, 1] representing the opacity of the window. public float Opacity { set { int errorCode = SDL.SDL_SetWindowOpacity(window, value); if (errorCode < 0) throw new OptionalSDLException(); } get { float value; int errorCode = SDL.SDL_GetWindowOpacity(window, out value); if (errorCode < 0) throw new OptionalSDLException(); return value; } } /// /// Whether or not this window is grabbing the users input by making sure the mouse stays within this windows boundaries. /// /// True if grabbing. public bool GrabbingInput { set { SDL.SDL_SetWindowGrab(window, value ? SDL.SDL_bool.SDL_TRUE : SDL.SDL_bool.SDL_FALSE); } get { return SDL.SDL_GetWindowGrab(window) == SDL.SDL_bool.SDL_TRUE ? true : false; } } internal uint WindowID { get { uint id = SDL.SDL_GetWindowID(window); if (id == 0) throw new FrameworkSDLException(); return id; } } /// /// Instantiates a window handle. /// public WindowContext(string title, int x = -1, int y = -1, int width = 640, int height = 480, bool specialRegions = false, WindowOption options = default(WindowOption)) { GameEngine.Logger.Information(String.Format("Starting openGL window with title \"{0}\"", title)); window = SDL.SDL_CreateWindow(title, x < 0 ? SDL.SDL_WINDOWPOS_CENTERED : x, y < 0 ? SDL.SDL_WINDOWPOS_CENTERED : y, width, height, SDL.SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS | SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL.SDL_WindowFlags.SDL_WINDOW_MOUSE_FOCUS | (SDL.SDL_WindowFlags) options); if (window == null) { throw new FrameworkSDLException(); } glContext = SDL.SDL_GL_CreateContext(window); if (glContext == null) { throw new FrameworkSDLException(); } clear = Marshal.GetDelegateForFunctionPointer(SDL.SDL_GL_GetProcAddress("glClear")); if (clear == null) throw new FrameworkSDLException(); clearColour = Marshal.GetDelegateForFunctionPointer(SDL.SDL_GL_GetProcAddress("glClearColor")); if (clearColour == null) throw new FrameworkSDLException(); viewport = Marshal.GetDelegateForFunctionPointer(SDL.SDL_GL_GetProcAddress("glViewport")); if (viewport == null) throw new FrameworkSDLException(); if (specialRegions) { if (SDL.SDL_SetWindowHitTest(window, SpecialRegionHit, IntPtr.Zero) < 0) { throw new OptionalSDLException(); } } WindowContextsManager.RegisterWindow(this); } /// /// Makes this window the window that is currently being drawn to. /// More specifically, it sets the OpenGL Context associated with this window to be the one /// that is actively receiving all OpenGL calls. /// /// If the current context is already of this window, nothing happens. /// internal void MakeCurrent() { if (SDL.SDL_GL_GetCurrentContext() != glContext) { SDL.SDL_GL_MakeCurrent(window, glContext); } } internal void SwapBuffer() { SDL.SDL_GL_SwapWindow(window); } /// /// Attempts to raise the window to the top. /// public void RaiseToTop() { SDL.SDL_RestoreWindow(window); SDL.SDL_RaiseWindow(window); } /// /// Gets the index of the display that this window resides within. /// /// An integer representing the display this window resides within. public int GetDisplayIndex() { int index = SDL.SDL_GetWindowDisplayIndex(window); if (index < 0) throw new FrameworkSDLException(); return index; } private SDL.SDL_HitTestResult SpecialRegionHit(IntPtr window, IntPtr hitPtr, IntPtr data) { SDL.SDL_Point SDLPoint = Marshal.PtrToStructure(hitPtr); FloatVector2 point = new FloatVector2(SDLPoint.x, SDLPoint.y); WindowRegion region = windowRegionHitEvent.Invoke(point); return (SDL.SDL_HitTestResult) region; } /// /// Disposes of this window and it's associated handles. /// public void Dispose() { WindowContextsManager.DeregisterWindow(this); SDL.SDL_GL_DeleteContext(glContext); SDL.SDL_DestroyWindow(window); } internal void OnResize(int width, int height) { resizeEvent?.Invoke(width, height); } internal void OnFocusLost() { focusLostEvent?.Invoke(); } internal void OnFocusGained() { focusGainedEvent?.Invoke(); } ~WindowContext() { Dispose(); } } }