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 ( ) ;
}
}
}