You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
427 lines
18 KiB
427 lines
18 KiB
using DotSDL.Events; |
|
using DotSDL.Input; |
|
using DotSDL.Interop.Core; |
|
using System; |
|
using System.Linq; |
|
|
|
namespace DotSDL.Graphics { |
|
/// <summary> |
|
/// Represents an SDL window. |
|
/// </summary> |
|
public class SdlWindow : IResourceObject { |
|
private readonly SdlInit _sdlInit = SdlInit.Instance; |
|
private readonly ResourceManager _resources = ResourceManager.Instance; |
|
|
|
private readonly IntPtr _window; |
|
private readonly IntPtr _renderer; |
|
private IntPtr _texture; |
|
private bool _hasTexture; |
|
|
|
public readonly Canvas Background; |
|
|
|
private bool _running; |
|
|
|
private uint _nextVideoUpdate; |
|
private uint _nextGameUpdate; |
|
|
|
private ScalingQuality _scalingQuality = ScalingQuality.Nearest; |
|
|
|
/// <summary><c>true</c> if this <see cref="SdlWindow"/> instance has been destroyed, othersize <c>false</c>.</summary> |
|
public bool IsDestroyed { get; set; } |
|
|
|
/// <summary><c>true</c> if this <see cref="SdlWindow"/> has been minimized, othersize <c>false</c>.</summary> |
|
public bool IsMinimized { get; set; } |
|
|
|
public ResourceType ResourceType => ResourceType.Window; |
|
|
|
/// <summary>Gets the width of this <see cref="SdlWindow"/>.</summary> |
|
public int WindowWidth { get; } |
|
/// <summary>Gets the height of this <see cref="SdlWindow"/>.</summary> |
|
public int WindowHeight { get; } |
|
|
|
/// <summary>Gets the width of the rendering target used by this <see cref="SdlWindow"/>.</summary> |
|
public int TextureWidth { get; } |
|
/// <summary>Gets the height of the rendering target used by this <see cref="SdlWindow"/>.</summary> |
|
public int TextureHeight { get; } |
|
|
|
/// <summary>The amount of time, in milliseconds, from when the application was started.</summary> |
|
public uint TicksElapsed => Timer.GetTicks(); |
|
/// <summary>Gets or sets the amount of time, in milliseconds, between video updates.</summary> |
|
public uint VideoUpdateTicks { get; set; } |
|
/// <summary>Gets or sets the amount of time, in milliseconds, between game (logic) updates.</summary> |
|
public uint GameUpdateTicks { get; set; } |
|
|
|
/// <summary>The list of active <see cref="Sprite"/> objects.</summary> |
|
public SpriteList Sprites { get; } |
|
|
|
/// <summary>Indicates that the window manager should position the window. To place the window on a specific display, use the <see cref="WindowPosCenteredDisplay"/> function.</summary> |
|
public const int WindowPosUndefined = 0x1FFF0000; |
|
|
|
/// <summary>Fired when tboolhe window's close button is clicked.</summary> |
|
public event EventHandler<WindowEvent> Closed; |
|
|
|
/// <summary>Fired when a key is pressed.</summary> |
|
public event EventHandler<KeyboardEvent> KeyPressed; |
|
|
|
/// <summary>Fired when a key is released.</summary> |
|
public event EventHandler<KeyboardEvent> KeyReleased; |
|
|
|
/// <summary>Fired when the window's minimize button is clicked.</summary> |
|
public event EventHandler<WindowEvent> Minimized; |
|
|
|
/// <summary>Fired when the window is restored.</summary> |
|
public event EventHandler<WindowEvent> Restored; |
|
|
|
public ScalingQuality ScalingQuality { |
|
get => _scalingQuality; |
|
set { |
|
_scalingQuality = value; |
|
|
|
if(_hasTexture) |
|
CreateTexture(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Calculates a value that allows the window to be placed on a specific display, with its exact position determined by the window manager. |
|
/// </summary> |
|
/// <param name="display">The index of the display to place the window on.</param> |
|
/// <returns>A coordinate value that should be passed to the <see cref="SdlWindow"/> constructor.</returns> |
|
public static int WindowPosUndefinedDisplay(uint display) { |
|
return (int)(WindowPosUndefined | display); |
|
} |
|
|
|
/// <summary> |
|
/// Indicates that the window should be in the center of the screen. To center the window on a specific display, use the <see cref="WindowPosCenteredDisplay"/> function. |
|
/// </summary> |
|
public const int WindowPosCentered = 0x2FFF0000; |
|
|
|
/// <summary> |
|
/// Calculates a value that allows the window to be placed in the center of a specified display. |
|
/// </summary> |
|
/// <param name="display">The index of the display to place the window on.</param> |
|
/// <returns>A coordinate value that should be passed to the <see cref="SdlWindow"/> constructor.</returns> |
|
public static int WindowPosCenteredDisplay(uint display) { |
|
return (int)(WindowPosCentered | display); |
|
} |
|
|
|
/// <summary> |
|
/// Creates a new <see cref="SdlWindow"/>.readonly |
|
/// </summary> |
|
/// <param name="title">The text that is displayed on the window's title bar.</param> |
|
/// <param name="position">A <see cref="Point"/> representing the starting position of the window. The X and Y coordinates of the Point can be set to <see cref="WindowPosUndefined"/> or <see cref="WindowPosCentered"/>.</param> |
|
/// <param name="windowWidth">The width of the window.</param> |
|
/// <param name="windowHeight">The height of the window.</param> |
|
public SdlWindow(string title, Point position, int windowWidth, int windowHeight) : this(title, position, windowWidth, windowHeight, windowWidth, windowHeight, ScalingQuality.Nearest) { } |
|
|
|
/// <summary> |
|
/// Creates a new <see cref="SdlWindow"/>. |
|
/// </summary> |
|
/// <param name="title">The text that is displayed on the window's title bar.</param> |
|
/// <param name="position">A <see cref="Point"/> representing the starting position of the window. The X and Y coordinates of the Point can be set to <see cref="WindowPosUndefined"/> or <see cref="WindowPosCentered"/>.</param> |
|
/// <param name="windowWidth">The width of the window.</param> |
|
/// <param name="windowHeight">The height of the window.</param> |
|
/// <param name="scalingQuality">The scaling (filtering) method to use for the background canvas texture.</param> |
|
public SdlWindow(string title, Point position, int windowWidth, int windowHeight, ScalingQuality scalingQuality) : this(title, position, windowWidth, windowHeight, windowWidth, windowHeight, scalingQuality) { } |
|
|
|
/// <summary> |
|
/// Creates a new <see cref="SdlWindow"/>. |
|
/// </summary> |
|
/// <param name="title">The text that is displayed on the window's title bar.</param> |
|
/// <param name="position">A <see cref="Point"/> representing the starting position of the window. The X and Y coordinates of the Point can be set to <see cref="WindowPosUndefined"/> or <see cref="WindowPosCentered"/>.</param> |
|
/// <param name="windowWidth">The width of the window.</param> |
|
/// <param name="windowHeight">The height of the window.</param> |
|
/// <param name="textureWidth">The width of the window's texture.</param> |
|
/// <param name="textureHeight">The height of the window's texture.</param> |
|
public SdlWindow(string title, Point position, int windowWidth, int windowHeight, int textureWidth, int textureHeight) : this(title, position, windowWidth, windowHeight, textureWidth, textureHeight, ScalingQuality.Nearest) { } |
|
|
|
/// <summary> |
|
/// Creates a new <see cref="SdlWindow"/>. |
|
/// </summary> |
|
/// <param name="title">The text that is displayed on the window's title bar.</param> |
|
/// <param name="position">A <see cref="Point"/> representing the starting position of the window. The X and Y coordinates of the Point can be set to <see cref="WindowPosUndefined"/> or <see cref="WindowPosCentered"/>.</param> |
|
/// <param name="windowWidth">The width of the window.</param> |
|
/// <param name="windowHeight">The height of the window.</param> |
|
/// <param name="textureWidth">The width of the window's texture.</param> |
|
/// <param name="textureHeight">The height of the window's texture.</param> |
|
/// <param name="scalingQuality">The scaling (filtering) method to use for the background canvas texture.</param> |
|
public SdlWindow(string title, Point position, int windowWidth, int windowHeight, int textureWidth, int textureHeight, ScalingQuality scalingQuality) { |
|
_sdlInit.InitSubsystem(Init.SubsystemFlags.Video); |
|
|
|
_window = Video.CreateWindow(title, position.X, position.Y, windowWidth, windowHeight, Video.WindowFlags.Hidden); |
|
_renderer = Render.CreateRenderer(_window, -1, Render.RendererFlags.Accelerated); |
|
|
|
WindowWidth = windowWidth; |
|
WindowHeight = windowHeight; |
|
|
|
TextureWidth = textureWidth; |
|
TextureHeight = textureHeight; |
|
|
|
ScalingQuality = scalingQuality; |
|
CreateTexture(); |
|
|
|
Background = new Canvas(textureWidth, textureHeight) { Renderer = _renderer }; |
|
Background.CreateTexture(); |
|
|
|
Sprites = new SpriteList(_renderer); |
|
|
|
IsDestroyed = false; |
|
_resources.RegisterResource(this); |
|
} |
|
|
|
/// <summary> |
|
/// Releases resources used by the <see cref="SdlWindow"/> instance. |
|
/// </summary> |
|
~SdlWindow() { |
|
DestroyObject(); |
|
_resources.UnregisterResource(this); |
|
} |
|
|
|
/// <summary> |
|
/// Handles calling the user draw function and passing the CLR objects to SDL2. |
|
/// </summary> |
|
private void BaseDraw() { |
|
if(IsDestroyed || IsMinimized) return; |
|
|
|
OnDraw(); |
|
|
|
Render.SetRenderTarget(_renderer, _texture); |
|
|
|
// Blit the Canvas to the target texture. |
|
Background.UpdateTexture(); |
|
unsafe { |
|
var canvasClippingRect = Background.Clipping.SdlRect; |
|
Render.RenderCopy(_renderer, Background.Texture, new IntPtr(&canvasClippingRect), IntPtr.Zero); |
|
} |
|
|
|
// Plot sprites on top of the background layer. |
|
if(Sprites.Count > 0) DrawSprites(); |
|
|
|
Render.SetRenderTarget(_renderer, IntPtr.Zero); |
|
Render.RenderCopy(_renderer, _texture, IntPtr.Zero, IntPtr.Zero); |
|
|
|
Render.RenderPresent(_renderer); |
|
} |
|
|
|
/// <summary> |
|
/// Handles setting up the <see cref="SdlWindow"/>. |
|
/// </summary> |
|
private void BaseLoad() { |
|
OnLoad(); // Call the overridden Load function. |
|
} |
|
|
|
/// <summary> |
|
/// Handles updating the application logic for the <see cref="SdlWindow"/>. |
|
/// </summary> |
|
private void BaseUpdate() { |
|
if(IsDestroyed) return; |
|
|
|
Events.EventHandler.ProcessEvents(); |
|
OnUpdate(); // Call the overridden Update function. |
|
} |
|
|
|
/// <summary> |
|
/// Creates the rendering target that all of the layers will be drawn to prior to rendering. |
|
/// </summary> |
|
private void CreateTexture() { |
|
DestroyTexture(); |
|
Hints.SetHint(Hints.RenderScaleQuality, ScalingQuality.ToString()); |
|
_texture = Render.CreateTexture(_renderer, Pixels.PixelFormatArgb8888, Render.TextureAccess.Target, TextureWidth, TextureHeight); |
|
_hasTexture = true; |
|
} |
|
|
|
/// <summary> |
|
/// Destroys this <see cref="SdlWindow"/>. |
|
/// </summary> |
|
public void DestroyObject() { |
|
Video.DestroyWindow(_window); |
|
IsDestroyed = true; |
|
} |
|
|
|
/// <summary> |
|
/// Destroys the render target associated with this <see cref="SdlWindow"/>. |
|
/// </summary> |
|
private void DestroyTexture() { |
|
if(!_hasTexture) return; |
|
|
|
Render.DestroyTexture(_texture); |
|
_hasTexture = false; |
|
} |
|
|
|
/// <summary> |
|
/// Plots the sprites stored in <see cref="Sprites"/> to the screen. Please note that this method is called by |
|
/// DotSDL's drawing routines and does not need to be called manually. Additionally, this method will not be |
|
/// called if there are no sprites defined. You usually do not need to override this method. |
|
/// </summary> |
|
public virtual void DrawSprites() { |
|
Render.SetRenderTarget(_renderer, _texture); |
|
foreach(var sprite in Sprites.Where(e => e.Shown).OrderBy(e => e.ZOrder)) { |
|
var srcRect = sprite.Clipping.SdlRect; |
|
var drawSize = new Point( |
|
(int)(srcRect.W * sprite.Scale.X), |
|
(int)(srcRect.H * sprite.Scale.Y) |
|
); |
|
var destRect = new Rectangle(sprite.Position, drawSize).SdlRect; |
|
|
|
unsafe { |
|
var srcRectPtr = new IntPtr(&srcRect); |
|
var destRectPtr = new IntPtr(&destRect); |
|
|
|
Render.RenderCopyEx( |
|
renderer: _renderer, |
|
texture: sprite.Texture, |
|
srcRect: srcRectPtr, |
|
dstRect: destRectPtr, |
|
angle: sprite.Rotation, |
|
center: sprite.RotationCenter.SdlPoint, |
|
flip: sprite.Flip |
|
); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Retrieves the SDL resource ID for this <see cref="SdlWindow"/>. |
|
/// </summary> |
|
/// <returns></returns> |
|
public uint GetResourceId() { |
|
return Video.GetWindowId(_window); |
|
} |
|
|
|
/// <summary> |
|
/// Triggers this window to handle a specified <see cref="KeyboardEvent"/>. |
|
/// </summary> |
|
/// <param name="ev">The <see cref="KeyboardEvent"/> to handle.</param> |
|
internal void HandleEvent(KeyboardEvent ev) { |
|
switch(ev.State) { |
|
case ButtonState.Pressed: |
|
KeyPressed?.Invoke(this, ev); |
|
break; |
|
case ButtonState.Released: |
|
KeyReleased?.Invoke(this, ev); |
|
break; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Triggers this window to handle a specified <see cref="WindowEvent"/>. |
|
/// </summary> |
|
/// <param name="ev">The <see cref="WindowEvent"/> to handle.</param> |
|
internal void HandleEvent(WindowEvent ev) { |
|
switch(ev.Event) { |
|
case WindowEventType.Close: |
|
OnClose(ev); |
|
break; |
|
case WindowEventType.Minimized: |
|
OnMinimize(ev); |
|
break; |
|
case WindowEventType.Restored: |
|
OnRestore(ev); |
|
break; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// A game loop that calls the <see cref="SdlWindow"/> update and draw functions. |
|
/// </summary> |
|
private void Loop() { |
|
_running = true; |
|
|
|
while(_running) { |
|
var ticks = TicksElapsed; |
|
|
|
if(ticks > _nextGameUpdate || GameUpdateTicks == 0) { |
|
_nextGameUpdate = ticks + GameUpdateTicks; |
|
BaseUpdate(); |
|
} |
|
|
|
if(ticks > _nextVideoUpdate || VideoUpdateTicks == 0) { |
|
_nextVideoUpdate = ticks + VideoUpdateTicks; |
|
BaseDraw(); |
|
} |
|
|
|
if(VideoUpdateTicks <= 0 && GameUpdateTicks <= 0) continue; // Cook the CPU! |
|
|
|
var updateTicks = (long)(_nextGameUpdate > _nextVideoUpdate ? _nextVideoUpdate : _nextGameUpdate) - TicksElapsed; |
|
if(updateTicks > 0) |
|
Timer.Delay((uint)updateTicks); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Called when the window's close button is clicked. |
|
/// </summary> |
|
private void OnClose(WindowEvent ev) { |
|
if(Closed is null) Stop(); |
|
else Closed(this, ev); |
|
} |
|
|
|
/// <summary> |
|
/// Called every time the window is drawn to. |
|
/// </summary> |
|
protected virtual void OnDraw() { } |
|
|
|
/// <summary> |
|
/// Called before the window is shown. |
|
/// </summary> |
|
protected virtual void OnLoad() { } |
|
|
|
/// <summary> |
|
/// Called when the window is minimized. |
|
/// </summary> |
|
private void OnMinimize(WindowEvent ev) { |
|
IsMinimized = true; |
|
Minimized?.Invoke(this, ev); |
|
} |
|
|
|
/// <summary> |
|
/// Called when the window is restored. |
|
/// </summary> |
|
private void OnRestore(WindowEvent ev) { |
|
IsMinimized = false; |
|
Restored?.Invoke(this, ev); |
|
} |
|
|
|
/// <summary> |
|
/// Called every time the application logic update runs. |
|
/// </summary> |
|
protected virtual void OnUpdate() { } |
|
|
|
/// <summary> |
|
/// Displays the window and begins executing code that's associated with it. |
|
/// </summary> |
|
public void Start() { |
|
Start(0, 0); |
|
} |
|
|
|
/// <summary> |
|
/// Displays the window and begins executing code that's associated with it. |
|
/// </summary> |
|
/// <param name="updateRate">The desired number of milliseconds between frames and game logic updates. 0 causes the display and game to be continuously updated.</param> |
|
public void Start(uint updateRate) { |
|
Start(updateRate, updateRate); |
|
} |
|
|
|
/// <summary> |
|
/// Displays the window and begins executing code that's associated with it. |
|
/// </summary> |
|
/// <param name="drawRate">The desired number of milliseconds between draw calls. 0 causes the display to be continuously updated.</param> |
|
/// <param name="updateRate">The desired number of milliseconds between game logic updates. 0 causes the game to be continuously updated.</param> |
|
public void Start(uint drawRate, uint updateRate) { |
|
VideoUpdateTicks = drawRate; |
|
GameUpdateTicks = updateRate; |
|
|
|
BaseLoad(); |
|
Video.ShowWindow(_window); |
|
Loop(); |
|
} |
|
|
|
/// <summary> |
|
/// Stops executing the game loop and destroys the window. |
|
/// </summary> |
|
public void Stop() { |
|
_running = false; |
|
DestroyObject(); |
|
} |
|
} |
|
}
|
|
|