|
|
#region License
|
|
|
/* FNA - XNA4 Reimplementation for Desktop Platforms
|
|
|
* Copyright 2009-2020 Ethan Lee and the MonoGame Team
|
|
|
*
|
|
|
* Released under the Microsoft Public License.
|
|
|
* See LICENSE for details.
|
|
|
*/
|
|
|
#endregion
|
|
|
|
|
|
#region Using Statements
|
|
|
using System;
|
|
|
using System.IO;
|
|
|
using System.Text;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
|
using SDL2;
|
|
|
|
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
|
using Microsoft.Xna.Framework.Audio;
|
|
|
using Microsoft.Xna.Framework.Input;
|
|
|
using Microsoft.Xna.Framework.Input.Touch;
|
|
|
#endregion
|
|
|
|
|
|
namespace Microsoft.Xna.Framework
|
|
|
{
|
|
|
internal static class SDL2_FNAPlatform
|
|
|
{
|
|
|
#region Static Constants
|
|
|
|
|
|
private static string OSVersion;
|
|
|
|
|
|
private static readonly bool UseScancodes = Environment.GetEnvironmentVariable(
|
|
|
"FNA_KEYBOARD_USE_SCANCODES"
|
|
|
) == "1";
|
|
|
|
|
|
private static bool SupportsGlobalMouse;
|
|
|
private static string ForcedGLDevice;
|
|
|
private static string ActualGLDevice;
|
|
|
|
|
|
// For iOS high dpi support
|
|
|
private static int RetinaWidth;
|
|
|
private static int RetinaHeight;
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Graphics Backend String Constants
|
|
|
|
|
|
private const string OPENGL = "OpenGLDevice";
|
|
|
private const string MODERNGL = "ModernGLDevice";
|
|
|
private const string THREADEDGL = "ThreadedGLDevice";
|
|
|
private const string METAL = "MetalDevice";
|
|
|
private const string VULKAN = "VulkanDevice";
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Game Objects
|
|
|
|
|
|
/* This is needed for asynchronous window events */
|
|
|
private static List<Game> activeGames = new List<Game>();
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Init/Exit Methods
|
|
|
|
|
|
public static string ProgramInit(LaunchParameters args)
|
|
|
{
|
|
|
// This is how we can weed out cases where fnalibs is missing
|
|
|
try
|
|
|
{
|
|
|
OSVersion = SDL.SDL_GetPlatform();
|
|
|
}
|
|
|
catch(Exception e)
|
|
|
{
|
|
|
FNALoggerEXT.LogError(
|
|
|
"SDL2 was not found! Do you have fnalibs?"
|
|
|
);
|
|
|
throw e;
|
|
|
}
|
|
|
|
|
|
/* SDL2 might complain if an OS that uses SDL_main has not actually
|
|
|
* used SDL_main by the time you initialize SDL2.
|
|
|
* The only platform that is affected is Windows, but we can skip
|
|
|
* their WinMain. This was only added to prevent iOS from exploding.
|
|
|
* -flibit
|
|
|
*/
|
|
|
SDL.SDL_SetMainReady();
|
|
|
|
|
|
/* A number of platforms don't support global mouse, but
|
|
|
* this really only matters on desktop where the game
|
|
|
* screen may not be covering the whole display.
|
|
|
*/
|
|
|
if ( OSVersion.Equals("Windows") ||
|
|
|
OSVersion.Equals("Mac OS X") ||
|
|
|
OSVersion.Equals("Linux") ||
|
|
|
OSVersion.Equals("FreeBSD") ||
|
|
|
OSVersion.Equals("OpenBSD") ||
|
|
|
OSVersion.Equals("NetBSD") )
|
|
|
{
|
|
|
SupportsGlobalMouse = true;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
SupportsGlobalMouse = false;
|
|
|
}
|
|
|
|
|
|
// Also, Windows is an idiot. -flibit
|
|
|
if ( OSVersion.Equals("Windows") ||
|
|
|
OSVersion.Equals("WinRT") )
|
|
|
{
|
|
|
// Visual Studio is an idiot.
|
|
|
if (System.Diagnostics.Debugger.IsAttached)
|
|
|
{
|
|
|
SDL.SDL_SetHint(
|
|
|
SDL.SDL_HINT_WINDOWS_DISABLE_THREAD_NAMING,
|
|
|
"1"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* Windows has terrible event pumping and doesn't give us
|
|
|
* WM_PAINT events correctly. So we get to do this!
|
|
|
* -flibit
|
|
|
*/
|
|
|
SDL.SDL_SetEventFilter(
|
|
|
win32OnPaint,
|
|
|
IntPtr.Zero
|
|
|
);
|
|
|
}
|
|
|
|
|
|
/* Mount TitleLocation.Path */
|
|
|
string titleLocation = GetBaseDirectory();
|
|
|
|
|
|
// If available, load the SDL_GameControllerDB
|
|
|
string mappingsDB = Path.Combine(
|
|
|
titleLocation,
|
|
|
"gamecontrollerdb.txt"
|
|
|
);
|
|
|
if (File.Exists(mappingsDB))
|
|
|
{
|
|
|
SDL.SDL_GameControllerAddMappingsFromFile(
|
|
|
mappingsDB
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Built-in SDL2 command line arguments
|
|
|
string arg;
|
|
|
if (args.TryGetValue("disablelateswaptear", out arg) && arg == "1")
|
|
|
{
|
|
|
Environment.SetEnvironmentVariable(
|
|
|
"FNA_OPENGL_DISABLE_LATESWAPTEAR",
|
|
|
"1"
|
|
|
);
|
|
|
}
|
|
|
if (args.TryGetValue("glprofile", out arg))
|
|
|
{
|
|
|
if (arg == "es3")
|
|
|
{
|
|
|
Environment.SetEnvironmentVariable(
|
|
|
"FNA_OPENGL_FORCE_ES3",
|
|
|
"1"
|
|
|
);
|
|
|
}
|
|
|
else if (arg == "core")
|
|
|
{
|
|
|
Environment.SetEnvironmentVariable(
|
|
|
"FNA_OPENGL_FORCE_CORE_PROFILE",
|
|
|
"1"
|
|
|
);
|
|
|
}
|
|
|
else if (arg == "compatibility")
|
|
|
{
|
|
|
Environment.SetEnvironmentVariable(
|
|
|
"FNA_OPENGL_FORCE_COMPATIBILITY_PROFILE",
|
|
|
"1"
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
if (args.TryGetValue("angle", out arg) && arg == "1")
|
|
|
{
|
|
|
Environment.SetEnvironmentVariable(
|
|
|
"FNA_OPENGL_FORCE_ES3",
|
|
|
"1"
|
|
|
);
|
|
|
Environment.SetEnvironmentVariable(
|
|
|
"SDL_OPENGL_ES_DRIVER",
|
|
|
"1"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// This _should_ be the first real SDL call we make...
|
|
|
SDL.SDL_Init(
|
|
|
SDL.SDL_INIT_VIDEO |
|
|
|
SDL.SDL_INIT_JOYSTICK |
|
|
|
SDL.SDL_INIT_GAMECONTROLLER |
|
|
|
SDL.SDL_INIT_HAPTIC
|
|
|
);
|
|
|
|
|
|
// Set any hints to match XNA4 behavior...
|
|
|
string hint = SDL.SDL_GetHint(SDL.SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS);
|
|
|
if (String.IsNullOrEmpty(hint))
|
|
|
{
|
|
|
SDL.SDL_SetHint(
|
|
|
SDL.SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS,
|
|
|
"1"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// By default, assume physical layout, since XNA games probably assume XInput
|
|
|
hint = SDL.SDL_GetHint(SDL.SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS);
|
|
|
if (String.IsNullOrEmpty(hint))
|
|
|
{
|
|
|
SDL.SDL_SetHint(
|
|
|
SDL.SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
|
|
|
"0"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
SDL.SDL_SetHint(
|
|
|
SDL.SDL_HINT_ORIENTATIONS,
|
|
|
"LandscapeLeft LandscapeRight Portrait"
|
|
|
);
|
|
|
|
|
|
// We want to initialize the controllers ASAP!
|
|
|
SDL.SDL_Event[] evt = new SDL.SDL_Event[1];
|
|
|
SDL.SDL_PumpEvents();
|
|
|
while (SDL.SDL_PeepEvents(
|
|
|
evt,
|
|
|
1,
|
|
|
SDL.SDL_eventaction.SDL_GETEVENT,
|
|
|
SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED,
|
|
|
SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED
|
|
|
) == 1) {
|
|
|
INTERNAL_AddInstance(evt[0].cdevice.which);
|
|
|
}
|
|
|
|
|
|
return titleLocation;
|
|
|
}
|
|
|
|
|
|
public static void ProgramExit(object sender, EventArgs e)
|
|
|
{
|
|
|
AudioEngine.ProgramExiting = true;
|
|
|
|
|
|
if (SoundEffect.FAudioContext.Context != null)
|
|
|
{
|
|
|
SoundEffect.FAudioContext.Context.Dispose();
|
|
|
}
|
|
|
Media.MediaPlayer.DisposeIfNecessary();
|
|
|
|
|
|
// This _should_ be the last SDL call we make...
|
|
|
SDL.SDL_Quit();
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Window Methods
|
|
|
|
|
|
private static bool PrepareVKAttributes()
|
|
|
{
|
|
|
// Who will write the VulkanDevice.. will it be YOU?
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
private static bool PrepareMTLAttributes()
|
|
|
{
|
|
|
if ( String.IsNullOrEmpty(ForcedGLDevice) ||
|
|
|
!ForcedGLDevice.Equals(METAL) )
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
#if DEBUG
|
|
|
// Always enable the validation layer in debug mode
|
|
|
Environment.SetEnvironmentVariable(
|
|
|
"METAL_DEVICE_WRAPPER_TYPE",
|
|
|
"1"
|
|
|
);
|
|
|
#endif
|
|
|
|
|
|
if (OSVersion.Equals("Mac OS X"))
|
|
|
{
|
|
|
// Let's find out if the OS supports Metal...
|
|
|
try
|
|
|
{
|
|
|
if (MetalDevice.MTLCreateSystemDefaultDevice() != IntPtr.Zero)
|
|
|
{
|
|
|
// We're good to go!
|
|
|
return true;
|
|
|
}
|
|
|
}
|
|
|
catch
|
|
|
{
|
|
|
// The OS is too old for Metal!
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
else if (OSVersion.Equals("iOS") || OSVersion.Equals("tvOS"))
|
|
|
{
|
|
|
/* We only support iOS/tvOS 11.0+ so
|
|
|
* Metal is guaranteed to be supported.
|
|
|
*/
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// Oh well, to OpenGL we go!
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
private static bool PrepareGLAttributes()
|
|
|
{
|
|
|
if ( !String.IsNullOrEmpty(ForcedGLDevice) &&
|
|
|
!ForcedGLDevice.Equals(OPENGL) &&
|
|
|
!ForcedGLDevice.Equals(MODERNGL) &&
|
|
|
!ForcedGLDevice.Equals(THREADEDGL) )
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// GLContext environment variables
|
|
|
bool forceES3 = Environment.GetEnvironmentVariable(
|
|
|
"FNA_OPENGL_FORCE_ES3"
|
|
|
) == "1";
|
|
|
bool forceCoreProfile = Environment.GetEnvironmentVariable(
|
|
|
"FNA_OPENGL_FORCE_CORE_PROFILE"
|
|
|
) == "1";
|
|
|
bool forceCompatProfile = Environment.GetEnvironmentVariable(
|
|
|
"FNA_OPENGL_FORCE_COMPATIBILITY_PROFILE"
|
|
|
) == "1";
|
|
|
|
|
|
// Some platforms are GLES only
|
|
|
forceES3 |= (
|
|
|
OSVersion.Equals("WinRT") ||
|
|
|
OSVersion.Equals("iOS") ||
|
|
|
OSVersion.Equals("tvOS") ||
|
|
|
OSVersion.Equals("Stadia") ||
|
|
|
OSVersion.Equals("Android") ||
|
|
|
OSVersion.Equals("Emscripten")
|
|
|
);
|
|
|
|
|
|
int depthSize = 24;
|
|
|
int stencilSize = 8;
|
|
|
DepthFormat windowDepthFormat;
|
|
|
if (Enum.TryParse(
|
|
|
Environment.GetEnvironmentVariable("FNA_OPENGL_WINDOW_DEPTHSTENCILFORMAT"),
|
|
|
true,
|
|
|
out windowDepthFormat
|
|
|
)) {
|
|
|
if (windowDepthFormat == DepthFormat.None)
|
|
|
{
|
|
|
depthSize = 0;
|
|
|
stencilSize = 0;
|
|
|
}
|
|
|
else if (windowDepthFormat == DepthFormat.Depth16)
|
|
|
{
|
|
|
depthSize = 16;
|
|
|
stencilSize = 0;
|
|
|
}
|
|
|
else if (windowDepthFormat == DepthFormat.Depth24)
|
|
|
{
|
|
|
depthSize = 24;
|
|
|
stencilSize = 0;
|
|
|
}
|
|
|
else if (windowDepthFormat == DepthFormat.Depth24Stencil8)
|
|
|
{
|
|
|
depthSize = 24;
|
|
|
stencilSize = 8;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_RED_SIZE, 8);
|
|
|
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_GREEN_SIZE, 8);
|
|
|
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_BLUE_SIZE, 8);
|
|
|
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_ALPHA_SIZE, 8);
|
|
|
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DEPTH_SIZE, depthSize);
|
|
|
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_STENCIL_SIZE, stencilSize);
|
|
|
SDL.SDL_GL_SetAttribute(SDL.SDL_GLattr.SDL_GL_DOUBLEBUFFER, 1);
|
|
|
if (forceES3)
|
|
|
{
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_RETAINED_BACKING,
|
|
|
0
|
|
|
);
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_ACCELERATED_VISUAL,
|
|
|
1
|
|
|
);
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION,
|
|
|
3
|
|
|
);
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION,
|
|
|
0
|
|
|
);
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK,
|
|
|
(int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_ES
|
|
|
);
|
|
|
}
|
|
|
else if (forceCoreProfile)
|
|
|
{
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION,
|
|
|
4
|
|
|
);
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION,
|
|
|
6
|
|
|
);
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK,
|
|
|
(int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_CORE
|
|
|
);
|
|
|
}
|
|
|
else if (forceCompatProfile)
|
|
|
{
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_MAJOR_VERSION,
|
|
|
2
|
|
|
);
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_MINOR_VERSION,
|
|
|
1
|
|
|
);
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_PROFILE_MASK,
|
|
|
(int) SDL.SDL_GLprofile.SDL_GL_CONTEXT_PROFILE_COMPATIBILITY
|
|
|
);
|
|
|
}
|
|
|
#if DEBUG
|
|
|
SDL.SDL_GL_SetAttribute(
|
|
|
SDL.SDL_GLattr.SDL_GL_CONTEXT_FLAGS,
|
|
|
(int) SDL.SDL_GLcontext.SDL_GL_CONTEXT_DEBUG_FLAG
|
|
|
);
|
|
|
#endif
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public static GameWindow CreateWindow()
|
|
|
{
|
|
|
// Set and initialize the SDL2 window
|
|
|
SDL.SDL_WindowFlags initFlags = (
|
|
|
SDL.SDL_WindowFlags.SDL_WINDOW_HIDDEN |
|
|
|
SDL.SDL_WindowFlags.SDL_WINDOW_INPUT_FOCUS |
|
|
|
SDL.SDL_WindowFlags.SDL_WINDOW_MOUSE_FOCUS
|
|
|
);
|
|
|
|
|
|
// Did the user force a particular GLDevice?
|
|
|
ForcedGLDevice = Environment.GetEnvironmentVariable(
|
|
|
"FNA_GRAPHICS_FORCE_GLDEVICE"
|
|
|
);
|
|
|
|
|
|
bool vulkan = false, metal = false, opengl = false;
|
|
|
if (vulkan = PrepareVKAttributes())
|
|
|
{
|
|
|
initFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN;
|
|
|
ActualGLDevice = VULKAN;
|
|
|
}
|
|
|
else if (metal = PrepareMTLAttributes())
|
|
|
{
|
|
|
SDL.SDL_SetHint(SDL.SDL_HINT_VIDEO_EXTERNAL_CONTEXT, "1");
|
|
|
|
|
|
// Metal doesn't require a window flag
|
|
|
ActualGLDevice = METAL;
|
|
|
}
|
|
|
else if (opengl = PrepareGLAttributes())
|
|
|
{
|
|
|
initFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL;
|
|
|
if ( ForcedGLDevice == MODERNGL ||
|
|
|
ForcedGLDevice == THREADEDGL )
|
|
|
{
|
|
|
ActualGLDevice = ForcedGLDevice;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
ActualGLDevice = OPENGL;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (Environment.GetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI") == "1")
|
|
|
{
|
|
|
initFlags |= SDL.SDL_WindowFlags.SDL_WINDOW_ALLOW_HIGHDPI;
|
|
|
}
|
|
|
|
|
|
string title = MonoGame.Utilities.AssemblyHelper.GetDefaultWindowTitle();
|
|
|
IntPtr window = SDL.SDL_CreateWindow(
|
|
|
title,
|
|
|
SDL.SDL_WINDOWPOS_CENTERED,
|
|
|
SDL.SDL_WINDOWPOS_CENTERED,
|
|
|
GraphicsDeviceManager.DefaultBackBufferWidth,
|
|
|
GraphicsDeviceManager.DefaultBackBufferHeight,
|
|
|
initFlags
|
|
|
);
|
|
|
if (window == IntPtr.Zero)
|
|
|
{
|
|
|
/* If this happens, the GL attributes were
|
|
|
* rejected by the platform. This is EXTREMELY
|
|
|
* rare (unless you're on Android, of course).
|
|
|
*/
|
|
|
throw new NoSuitableGraphicsDeviceException(
|
|
|
SDL.SDL_GetError()
|
|
|
);
|
|
|
}
|
|
|
INTERNAL_SetIcon(window, title);
|
|
|
|
|
|
// Disable the screensaver.
|
|
|
SDL.SDL_DisableScreenSaver();
|
|
|
|
|
|
// We hide the mouse cursor by default.
|
|
|
OnIsMouseVisibleChanged(false);
|
|
|
|
|
|
/* When using OpenGL, iOS and tvOS require
|
|
|
* an active GL context to get the drawable
|
|
|
* size of the screen.
|
|
|
*
|
|
|
* When using Metal, all Apple platforms
|
|
|
* require a view to get the drawable size.
|
|
|
*/
|
|
|
IntPtr tempContext = IntPtr.Zero;
|
|
|
if (opengl && (OSVersion.Equals("iOS") || OSVersion.Equals("tvOS")))
|
|
|
{
|
|
|
tempContext = SDL.SDL_GL_CreateContext(window);
|
|
|
}
|
|
|
else if (metal)
|
|
|
{
|
|
|
tempContext = SDL.SDL_Metal_CreateView(window);
|
|
|
}
|
|
|
|
|
|
/* If high DPI is not found, unset the HIGHDPI var.
|
|
|
* This is our way to communicate that it failed...
|
|
|
* -flibit
|
|
|
*/
|
|
|
int drawX, drawY;
|
|
|
if (vulkan)
|
|
|
{
|
|
|
SDL.SDL_Vulkan_GetDrawableSize(window, out drawX, out drawY);
|
|
|
}
|
|
|
else if (metal)
|
|
|
{
|
|
|
MetalDevice.GetDrawableSizeFromView(tempContext, out drawX, out drawY);
|
|
|
}
|
|
|
else if (opengl)
|
|
|
{
|
|
|
SDL.SDL_GL_GetDrawableSize(window, out drawX, out drawY);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
throw new InvalidOperationException("DirectX? Glide? What?");
|
|
|
}
|
|
|
if ( drawX == GraphicsDeviceManager.DefaultBackBufferWidth &&
|
|
|
drawY == GraphicsDeviceManager.DefaultBackBufferHeight )
|
|
|
{
|
|
|
Environment.SetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI", "0");
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// Store the full retina resolution of the display
|
|
|
RetinaWidth = drawX;
|
|
|
RetinaHeight = drawY;
|
|
|
}
|
|
|
|
|
|
// We're done with that temporary context.
|
|
|
if (tempContext != IntPtr.Zero)
|
|
|
{
|
|
|
if (opengl)
|
|
|
{
|
|
|
SDL.SDL_GL_DeleteContext(tempContext);
|
|
|
}
|
|
|
else if (metal)
|
|
|
{
|
|
|
SDL.SDL_Metal_DestroyView(tempContext);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return new FNAWindow(
|
|
|
window,
|
|
|
@"\\.\DISPLAY" + (
|
|
|
SDL.SDL_GetWindowDisplayIndex(window) + 1
|
|
|
).ToString()
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public static void DisposeWindow(GameWindow window)
|
|
|
{
|
|
|
/* Some window managers might try to minimize the window as we're
|
|
|
* destroying it. This looks pretty stupid and could cause problems,
|
|
|
* so set this hint right before we destroy everything.
|
|
|
* -flibit
|
|
|
*/
|
|
|
SDL.SDL_SetHintWithPriority(
|
|
|
SDL.SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS,
|
|
|
"0",
|
|
|
SDL.SDL_HintPriority.SDL_HINT_OVERRIDE
|
|
|
);
|
|
|
|
|
|
if (Mouse.WindowHandle == window.Handle)
|
|
|
{
|
|
|
Mouse.WindowHandle = IntPtr.Zero;
|
|
|
}
|
|
|
|
|
|
if (TouchPanel.WindowHandle == window.Handle)
|
|
|
{
|
|
|
TouchPanel.WindowHandle = IntPtr.Zero;
|
|
|
}
|
|
|
|
|
|
SDL.SDL_DestroyWindow(window.Handle);
|
|
|
}
|
|
|
|
|
|
public static void ApplyWindowChanges(
|
|
|
IntPtr window,
|
|
|
int clientWidth,
|
|
|
int clientHeight,
|
|
|
bool wantsFullscreen,
|
|
|
string screenDeviceName,
|
|
|
ref string resultDeviceName
|
|
|
) {
|
|
|
bool center = false;
|
|
|
if ( Environment.GetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI") == "1" &&
|
|
|
OSVersion.Equals("Mac OS X") )
|
|
|
{
|
|
|
/* For high-DPI windows, halve the size!
|
|
|
* The drawable size is now the primary width/height, so
|
|
|
* the window needs to accommodate the GL viewport.
|
|
|
* -flibit
|
|
|
*/
|
|
|
clientWidth /= 2;
|
|
|
clientHeight /= 2;
|
|
|
}
|
|
|
|
|
|
// When windowed, set the size before moving
|
|
|
if (!wantsFullscreen)
|
|
|
{
|
|
|
bool resize = false;
|
|
|
if ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN) != 0)
|
|
|
{
|
|
|
SDL.SDL_SetWindowFullscreen(window, 0);
|
|
|
resize = true;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
int w, h;
|
|
|
SDL.SDL_GetWindowSize(
|
|
|
window,
|
|
|
out w,
|
|
|
out h
|
|
|
);
|
|
|
resize = (clientWidth != w || clientHeight != h);
|
|
|
}
|
|
|
if (resize)
|
|
|
{
|
|
|
SDL.SDL_SetWindowSize(window, clientWidth, clientHeight);
|
|
|
center = true;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Get on the right display!
|
|
|
int displayIndex = 0;
|
|
|
for (int i = 0; i < GraphicsAdapter.Adapters.Count; i += 1)
|
|
|
{
|
|
|
if (screenDeviceName == GraphicsAdapter.Adapters[i].DeviceName)
|
|
|
{
|
|
|
displayIndex = i;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Just to be sure, become a window first before changing displays
|
|
|
if (resultDeviceName != screenDeviceName)
|
|
|
{
|
|
|
SDL.SDL_SetWindowFullscreen(window, 0);
|
|
|
resultDeviceName = screenDeviceName;
|
|
|
center = true;
|
|
|
}
|
|
|
|
|
|
// Window always gets centered on changes, per XNA behavior
|
|
|
if (center)
|
|
|
{
|
|
|
int pos = SDL.SDL_WINDOWPOS_CENTERED_DISPLAY(displayIndex);
|
|
|
SDL.SDL_SetWindowPosition(
|
|
|
window,
|
|
|
pos,
|
|
|
pos
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Set fullscreen after we've done all the ugly stuff.
|
|
|
if (wantsFullscreen)
|
|
|
{
|
|
|
if ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_SHOWN) == 0)
|
|
|
{
|
|
|
/* If we're still hidden, we can't actually go fullscreen yet.
|
|
|
* But, we can at least set the hidden window size to match
|
|
|
* what the window/drawable sizes will eventually be later.
|
|
|
* -flibit
|
|
|
*/
|
|
|
SDL.SDL_DisplayMode mode;
|
|
|
SDL.SDL_GetCurrentDisplayMode(
|
|
|
displayIndex,
|
|
|
out mode
|
|
|
);
|
|
|
SDL.SDL_SetWindowSize(window, mode.w, mode.h);
|
|
|
}
|
|
|
SDL.SDL_SetWindowFullscreen(
|
|
|
window,
|
|
|
(uint) SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static Rectangle GetWindowBounds(IntPtr window)
|
|
|
{
|
|
|
Rectangle result;
|
|
|
if ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN) != 0)
|
|
|
{
|
|
|
/* FIXME: SDL2 bug!
|
|
|
* SDL's a little weird about SDL_GetWindowSize.
|
|
|
* If you call it early enough (for example,
|
|
|
* Game.Initialize()), it reports outdated ints.
|
|
|
* So you know what, let's just use this.
|
|
|
* -flibit
|
|
|
*/
|
|
|
SDL.SDL_DisplayMode mode;
|
|
|
SDL.SDL_GetCurrentDisplayMode(
|
|
|
SDL.SDL_GetWindowDisplayIndex(
|
|
|
window
|
|
|
),
|
|
|
out mode
|
|
|
);
|
|
|
result.X = 0;
|
|
|
result.Y = 0;
|
|
|
result.Width = mode.w;
|
|
|
result.Height = mode.h;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
SDL.SDL_GetWindowPosition(
|
|
|
window,
|
|
|
out result.X,
|
|
|
out result.Y
|
|
|
);
|
|
|
SDL.SDL_GetWindowSize(
|
|
|
window,
|
|
|
out result.Width,
|
|
|
out result.Height
|
|
|
);
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
public static bool GetWindowResizable(IntPtr window)
|
|
|
{
|
|
|
return ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE) != 0);
|
|
|
}
|
|
|
|
|
|
public static void SetWindowResizable(IntPtr window, bool resizable)
|
|
|
{
|
|
|
SDL.SDL_SetWindowResizable(
|
|
|
window,
|
|
|
resizable ?
|
|
|
SDL.SDL_bool.SDL_TRUE :
|
|
|
SDL.SDL_bool.SDL_FALSE
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public static bool GetWindowBorderless(IntPtr window)
|
|
|
{
|
|
|
return ((SDL.SDL_GetWindowFlags(window) & (uint) SDL.SDL_WindowFlags.SDL_WINDOW_BORDERLESS) != 0);
|
|
|
}
|
|
|
|
|
|
public static void SetWindowBorderless(IntPtr window, bool borderless)
|
|
|
{
|
|
|
SDL.SDL_SetWindowBordered(
|
|
|
window,
|
|
|
borderless ?
|
|
|
SDL.SDL_bool.SDL_FALSE :
|
|
|
SDL.SDL_bool.SDL_TRUE
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public static void SetWindowTitle(IntPtr window, string title)
|
|
|
{
|
|
|
SDL.SDL_SetWindowTitle(
|
|
|
window,
|
|
|
title
|
|
|
);
|
|
|
}
|
|
|
|
|
|
private static void INTERNAL_SetIcon(IntPtr window, string title)
|
|
|
{
|
|
|
string fileIn = String.Empty;
|
|
|
|
|
|
/* If the game's using SDL2_image, provide the option to use a PNG
|
|
|
* instead of a BMP. Nice for anyone who cares about transparency.
|
|
|
* -flibit
|
|
|
*/
|
|
|
try
|
|
|
{
|
|
|
fileIn = INTERNAL_GetIconName(title + ".png");
|
|
|
if (!String.IsNullOrEmpty(fileIn))
|
|
|
{
|
|
|
IntPtr icon = SDL_image.IMG_Load(fileIn);
|
|
|
SDL.SDL_SetWindowIcon(window, icon);
|
|
|
SDL.SDL_FreeSurface(icon);
|
|
|
return;
|
|
|
}
|
|
|
}
|
|
|
catch(DllNotFoundException)
|
|
|
{
|
|
|
// Not that big a deal guys.
|
|
|
}
|
|
|
|
|
|
fileIn = INTERNAL_GetIconName(title + ".bmp");
|
|
|
if (!String.IsNullOrEmpty(fileIn))
|
|
|
{
|
|
|
IntPtr icon = SDL.SDL_LoadBMP(fileIn);
|
|
|
SDL.SDL_SetWindowIcon(window, icon);
|
|
|
SDL.SDL_FreeSurface(icon);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static string INTERNAL_GetIconName(string title)
|
|
|
{
|
|
|
string fileIn = Path.Combine(TitleLocation.Path, title);
|
|
|
if (File.Exists(fileIn))
|
|
|
{
|
|
|
// If the title and filename work, it just works. Fine.
|
|
|
return fileIn;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
// But sometimes the title has invalid characters inside.
|
|
|
fileIn = Path.Combine(
|
|
|
TitleLocation.Path,
|
|
|
INTERNAL_StripBadChars(title)
|
|
|
);
|
|
|
if (File.Exists(fileIn))
|
|
|
{
|
|
|
return fileIn;
|
|
|
}
|
|
|
}
|
|
|
return String.Empty;
|
|
|
}
|
|
|
|
|
|
private static string INTERNAL_StripBadChars(string path)
|
|
|
{
|
|
|
/* In addition to the filesystem's invalid charset, we need to
|
|
|
* blacklist the Windows standard set too, no matter what.
|
|
|
* -flibit
|
|
|
*/
|
|
|
char[] hardCodeBadChars = new char[]
|
|
|
{
|
|
|
'<',
|
|
|
'>',
|
|
|
':',
|
|
|
'"',
|
|
|
'/',
|
|
|
'\\',
|
|
|
'|',
|
|
|
'?',
|
|
|
'*'
|
|
|
};
|
|
|
List<char> badChars = new List<char>();
|
|
|
badChars.AddRange(Path.GetInvalidFileNameChars());
|
|
|
badChars.AddRange(hardCodeBadChars);
|
|
|
|
|
|
string stripChars = path;
|
|
|
foreach (char c in badChars)
|
|
|
{
|
|
|
stripChars = stripChars.Replace(c.ToString(), "");
|
|
|
}
|
|
|
return stripChars;
|
|
|
}
|
|
|
|
|
|
public static void SetTextInputRectangle(Rectangle rectangle)
|
|
|
{
|
|
|
SDL.SDL_Rect rect = new SDL.SDL_Rect();
|
|
|
rect.x = rectangle.X;
|
|
|
rect.y = rectangle.Y;
|
|
|
rect.w = rectangle.Width;
|
|
|
rect.h = rectangle.Height;
|
|
|
SDL.SDL_SetTextInputRect(ref rect);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Display Methods
|
|
|
|
|
|
private static DisplayOrientation INTERNAL_ConvertOrientation(SDL.SDL_DisplayOrientation orientation)
|
|
|
{
|
|
|
switch (orientation)
|
|
|
{
|
|
|
case SDL.SDL_DisplayOrientation.SDL_ORIENTATION_LANDSCAPE:
|
|
|
return DisplayOrientation.LandscapeLeft;
|
|
|
|
|
|
case SDL.SDL_DisplayOrientation.SDL_ORIENTATION_LANDSCAPE_FLIPPED:
|
|
|
return DisplayOrientation.LandscapeRight;
|
|
|
|
|
|
case SDL.SDL_DisplayOrientation.SDL_ORIENTATION_PORTRAIT:
|
|
|
return DisplayOrientation.Portrait;
|
|
|
|
|
|
default:
|
|
|
throw new NotSupportedException("FNA does not support this device orientation.");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static void INTERNAL_HandleOrientationChange(
|
|
|
DisplayOrientation orientation,
|
|
|
GraphicsDevice graphicsDevice,
|
|
|
FNAWindow window
|
|
|
) {
|
|
|
// Flip the backbuffer dimensions if needed
|
|
|
int width = graphicsDevice.PresentationParameters.BackBufferWidth;
|
|
|
int height = graphicsDevice.PresentationParameters.BackBufferHeight;
|
|
|
int min = Math.Min(width, height);
|
|
|
int max = Math.Max(width, height);
|
|
|
|
|
|
if (orientation == DisplayOrientation.Portrait)
|
|
|
{
|
|
|
graphicsDevice.PresentationParameters.BackBufferWidth = min;
|
|
|
graphicsDevice.PresentationParameters.BackBufferHeight = max;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
graphicsDevice.PresentationParameters.BackBufferWidth = max;
|
|
|
graphicsDevice.PresentationParameters.BackBufferHeight = min;
|
|
|
}
|
|
|
|
|
|
// Update the graphics device and window
|
|
|
graphicsDevice.PresentationParameters.DisplayOrientation = orientation;
|
|
|
window.CurrentOrientation = orientation;
|
|
|
|
|
|
graphicsDevice.Reset();
|
|
|
window.INTERNAL_OnOrientationChanged();
|
|
|
}
|
|
|
|
|
|
public static bool SupportsOrientationChanges()
|
|
|
{
|
|
|
return OSVersion.Equals("iOS") || OSVersion.Equals("Android");
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Event Loop
|
|
|
|
|
|
public static void RunLoop(Game game)
|
|
|
{
|
|
|
SDL.SDL_ShowWindow(game.Window.Handle);
|
|
|
game.IsActive = true;
|
|
|
|
|
|
Rectangle windowBounds = game.Window.ClientBounds;
|
|
|
Mouse.INTERNAL_WindowWidth = windowBounds.Width;
|
|
|
Mouse.INTERNAL_WindowHeight = windowBounds.Height;
|
|
|
|
|
|
// Which display did we end up on?
|
|
|
int displayIndex = SDL.SDL_GetWindowDisplayIndex(
|
|
|
game.Window.Handle
|
|
|
);
|
|
|
|
|
|
// Store this for internal event filter work
|
|
|
activeGames.Add(game);
|
|
|
|
|
|
// OSX has some fancy fullscreen features, let's use them!
|
|
|
bool osxUseSpaces;
|
|
|
if (OSVersion.Equals("Mac OS X"))
|
|
|
{
|
|
|
string hint = SDL.SDL_GetHint(SDL.SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES);
|
|
|
osxUseSpaces = (String.IsNullOrEmpty(hint) || hint.Equals("1"));
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
osxUseSpaces = false;
|
|
|
}
|
|
|
|
|
|
// Perform initial check for a touch device
|
|
|
TouchPanel.TouchDeviceExists = GetTouchCapabilities().IsConnected;
|
|
|
|
|
|
// Do we want to read keycodes or scancodes?
|
|
|
if (UseScancodes)
|
|
|
{
|
|
|
FNALoggerEXT.LogInfo("Using scancodes instead of keycodes!");
|
|
|
}
|
|
|
|
|
|
// Active Key List
|
|
|
List<Keys> keys = new List<Keys>();
|
|
|
|
|
|
/* Setup Text Input Control Character Arrays
|
|
|
* (Only 7 control keys supported at this time)
|
|
|
*/
|
|
|
char[] textInputCharacters = new char[]
|
|
|
{
|
|
|
(char) 2, // Home
|
|
|
(char) 3, // End
|
|
|
(char) 8, // Backspace
|
|
|
(char) 9, // Tab
|
|
|
(char) 13, // Enter
|
|
|
(char) 127, // Delete
|
|
|
(char) 22 // Ctrl+V (Paste)
|
|
|
};
|
|
|
Dictionary<Keys, int> textInputBindings = new Dictionary<Keys, int>()
|
|
|
{
|
|
|
{ Keys.Home, 0 },
|
|
|
{ Keys.End, 1 },
|
|
|
{ Keys.Back, 2 },
|
|
|
{ Keys.Tab, 3 },
|
|
|
{ Keys.Enter, 4 },
|
|
|
{ Keys.Delete, 5 }
|
|
|
// Ctrl+V is special!
|
|
|
};
|
|
|
bool[] textInputControlDown = new bool[textInputCharacters.Length];
|
|
|
int[] textInputControlRepeat = new int[textInputCharacters.Length];
|
|
|
bool textInputSuppress = false;
|
|
|
|
|
|
SDL.SDL_Event evt;
|
|
|
|
|
|
while (game.RunApplication)
|
|
|
{
|
|
|
while (SDL.SDL_PollEvent(out evt) == 1)
|
|
|
{
|
|
|
// Keyboard
|
|
|
if (evt.type == SDL.SDL_EventType.SDL_KEYDOWN)
|
|
|
{
|
|
|
Keys key = ToXNAKey(ref evt.key.keysym);
|
|
|
if (!keys.Contains(key))
|
|
|
{
|
|
|
keys.Add(key);
|
|
|
int textIndex;
|
|
|
if (textInputBindings.TryGetValue(key, out textIndex))
|
|
|
{
|
|
|
textInputControlDown[textIndex] = true;
|
|
|
textInputControlRepeat[textIndex] = Environment.TickCount + 400;
|
|
|
TextInputEXT.OnTextInput(textInputCharacters[textIndex]);
|
|
|
}
|
|
|
else if (keys.Contains(Keys.LeftControl) && key == Keys.V)
|
|
|
{
|
|
|
textInputControlDown[6] = true;
|
|
|
textInputControlRepeat[6] = Environment.TickCount + 400;
|
|
|
TextInputEXT.OnTextInput(textInputCharacters[6]);
|
|
|
textInputSuppress = true;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_KEYUP)
|
|
|
{
|
|
|
Keys key = ToXNAKey(ref evt.key.keysym);
|
|
|
if (keys.Remove(key))
|
|
|
{
|
|
|
int value;
|
|
|
if (textInputBindings.TryGetValue(key, out value))
|
|
|
{
|
|
|
textInputControlDown[value] = false;
|
|
|
}
|
|
|
else if ((!keys.Contains(Keys.LeftControl) && textInputControlDown[3]) || key == Keys.V)
|
|
|
{
|
|
|
textInputControlDown[6] = false;
|
|
|
textInputSuppress = false;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Mouse Input
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_MOUSEBUTTONDOWN)
|
|
|
{
|
|
|
Mouse.INTERNAL_onClicked(evt.button.button - 1);
|
|
|
}
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_MOUSEWHEEL)
|
|
|
{
|
|
|
// 120 units per notch. Because reasons.
|
|
|
Mouse.INTERNAL_MouseWheel += evt.wheel.y * 120;
|
|
|
}
|
|
|
|
|
|
// Touch Input
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_FINGERDOWN)
|
|
|
{
|
|
|
// Windows only notices a touch screen once it's touched
|
|
|
TouchPanel.TouchDeviceExists = true;
|
|
|
|
|
|
TouchPanel.INTERNAL_onTouchEvent(
|
|
|
(int) evt.tfinger.fingerId,
|
|
|
TouchLocationState.Pressed,
|
|
|
evt.tfinger.x,
|
|
|
evt.tfinger.y,
|
|
|
0,
|
|
|
0
|
|
|
);
|
|
|
}
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_FINGERMOTION)
|
|
|
{
|
|
|
TouchPanel.INTERNAL_onTouchEvent(
|
|
|
(int) evt.tfinger.fingerId,
|
|
|
TouchLocationState.Moved,
|
|
|
evt.tfinger.x,
|
|
|
evt.tfinger.y,
|
|
|
evt.tfinger.dx,
|
|
|
evt.tfinger.dy
|
|
|
);
|
|
|
}
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_FINGERUP)
|
|
|
{
|
|
|
TouchPanel.INTERNAL_onTouchEvent(
|
|
|
(int) evt.tfinger.fingerId,
|
|
|
TouchLocationState.Released,
|
|
|
evt.tfinger.x,
|
|
|
evt.tfinger.y,
|
|
|
0,
|
|
|
0
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Various Window Events...
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_WINDOWEVENT)
|
|
|
{
|
|
|
// Window Focus
|
|
|
if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_GAINED)
|
|
|
{
|
|
|
game.IsActive = true;
|
|
|
|
|
|
if (!osxUseSpaces)
|
|
|
{
|
|
|
// If we alt-tab away, we lose the 'fullscreen desktop' flag on some WMs
|
|
|
SDL.SDL_SetWindowFullscreen(
|
|
|
game.Window.Handle,
|
|
|
game.GraphicsDevice.PresentationParameters.IsFullScreen ?
|
|
|
(uint) SDL.SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP :
|
|
|
0
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Disable the screensaver when we're back.
|
|
|
SDL.SDL_DisableScreenSaver();
|
|
|
}
|
|
|
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_FOCUS_LOST)
|
|
|
{
|
|
|
game.IsActive = false;
|
|
|
|
|
|
if (!osxUseSpaces)
|
|
|
{
|
|
|
SDL.SDL_SetWindowFullscreen(game.Window.Handle, 0);
|
|
|
}
|
|
|
|
|
|
// Give the screensaver back, we're not that important now.
|
|
|
SDL.SDL_EnableScreenSaver();
|
|
|
}
|
|
|
|
|
|
// Window Resize
|
|
|
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_SIZE_CHANGED)
|
|
|
{
|
|
|
// This is called on both API and WM resizes
|
|
|
Mouse.INTERNAL_WindowWidth = evt.window.data1;
|
|
|
Mouse.INTERNAL_WindowHeight = evt.window.data2;
|
|
|
}
|
|
|
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_RESIZED)
|
|
|
{
|
|
|
/* This should be called on user resize only, NOT ApplyChanges!
|
|
|
* Sadly some window managers are idiots and fire events anyway.
|
|
|
* Also ignore any other "resizes" (alt-tab, fullscreen, etc.)
|
|
|
* -flibit
|
|
|
*/
|
|
|
if (GetWindowResizable(game.Window.Handle))
|
|
|
{
|
|
|
((FNAWindow) game.Window).INTERNAL_ClientSizeChanged();
|
|
|
}
|
|
|
}
|
|
|
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_EXPOSED)
|
|
|
{
|
|
|
// This is typically called when the window is made bigger
|
|
|
game.RedrawWindow();
|
|
|
}
|
|
|
|
|
|
// Window Move
|
|
|
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_MOVED)
|
|
|
{
|
|
|
/* Apparently if you move the window to a new
|
|
|
* display, a GraphicsDevice Reset occurs.
|
|
|
* -flibit
|
|
|
*/
|
|
|
int newIndex = SDL.SDL_GetWindowDisplayIndex(
|
|
|
game.Window.Handle
|
|
|
);
|
|
|
if (newIndex != displayIndex)
|
|
|
{
|
|
|
displayIndex = newIndex;
|
|
|
game.GraphicsDevice.Reset(
|
|
|
game.GraphicsDevice.PresentationParameters,
|
|
|
GraphicsAdapter.Adapters[displayIndex]
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Mouse Focus
|
|
|
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_ENTER)
|
|
|
{
|
|
|
SDL.SDL_DisableScreenSaver();
|
|
|
}
|
|
|
else if (evt.window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_LEAVE)
|
|
|
{
|
|
|
SDL.SDL_EnableScreenSaver();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Display Events
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_DISPLAYEVENT)
|
|
|
{
|
|
|
// Orientation Change
|
|
|
if (evt.display.displayEvent == SDL.SDL_DisplayEventID.SDL_DISPLAYEVENT_ORIENTATION)
|
|
|
{
|
|
|
DisplayOrientation orientation = INTERNAL_ConvertOrientation(
|
|
|
(SDL.SDL_DisplayOrientation) evt.display.data1
|
|
|
);
|
|
|
|
|
|
INTERNAL_HandleOrientationChange(
|
|
|
orientation,
|
|
|
game.GraphicsDevice,
|
|
|
(FNAWindow) game.Window
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Controller device management
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_CONTROLLERDEVICEADDED)
|
|
|
{
|
|
|
INTERNAL_AddInstance(evt.cdevice.which);
|
|
|
}
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_CONTROLLERDEVICEREMOVED)
|
|
|
{
|
|
|
INTERNAL_RemoveInstance(evt.cdevice.which);
|
|
|
}
|
|
|
|
|
|
// Text Input
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_TEXTINPUT && !textInputSuppress)
|
|
|
{
|
|
|
// Based on the SDL2# LPUtf8StrMarshaler
|
|
|
unsafe
|
|
|
{
|
|
|
byte* endPtr = evt.text.text;
|
|
|
if (*endPtr != 0)
|
|
|
{
|
|
|
int bytes = 0;
|
|
|
while (*endPtr != 0)
|
|
|
{
|
|
|
endPtr++;
|
|
|
bytes += 1;
|
|
|
}
|
|
|
|
|
|
/* UTF8 will never encode more characters
|
|
|
* than bytes in a string, so bytes is a
|
|
|
* suitable upper estimate of size needed
|
|
|
*/
|
|
|
char* charsBuffer = stackalloc char[bytes];
|
|
|
int chars = Encoding.UTF8.GetChars(
|
|
|
evt.text.text,
|
|
|
bytes,
|
|
|
charsBuffer,
|
|
|
bytes
|
|
|
);
|
|
|
|
|
|
for (int i = 0; i < chars; i += 1)
|
|
|
{
|
|
|
TextInputEXT.OnTextInput(charsBuffer[i]);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Quit
|
|
|
else if (evt.type == SDL.SDL_EventType.SDL_QUIT)
|
|
|
{
|
|
|
game.RunApplication = false;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
// Text Input Controls Key Handling
|
|
|
for (int i = 0; i < textInputCharacters.Length; i += 1)
|
|
|
{
|
|
|
if (textInputControlDown[i] && textInputControlRepeat[i] <= Environment.TickCount)
|
|
|
{
|
|
|
TextInputEXT.OnTextInput(textInputCharacters[i]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Keyboard.SetKeys(keys);
|
|
|
game.Tick();
|
|
|
}
|
|
|
|
|
|
// Okay, we don't care about the events anymore
|
|
|
activeGames.Remove(game);
|
|
|
|
|
|
// We out.
|
|
|
game.Exit();
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region IGL/IAL Methods
|
|
|
|
|
|
public static IGLDevice CreateGLDevice(
|
|
|
PresentationParameters presentationParameters,
|
|
|
GraphicsAdapter adapter
|
|
|
) {
|
|
|
if (string.IsNullOrEmpty(ActualGLDevice))
|
|
|
{
|
|
|
/* This may be a GraphicsDevice with no Game.
|
|
|
* in that case, try this var one last time.
|
|
|
*/
|
|
|
ActualGLDevice = Environment.GetEnvironmentVariable(
|
|
|
"FNA_GRAPHICS_FORCE_GLDEVICE"
|
|
|
);
|
|
|
if (string.IsNullOrEmpty(ActualGLDevice))
|
|
|
{
|
|
|
// No device requested at all? Try to guess.
|
|
|
SDL.SDL_WindowFlags flags = (SDL.SDL_WindowFlags) SDL.SDL_GetWindowFlags(
|
|
|
presentationParameters.DeviceWindowHandle
|
|
|
);
|
|
|
if ((flags & SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN) == SDL.SDL_WindowFlags.SDL_WINDOW_VULKAN)
|
|
|
{
|
|
|
ActualGLDevice = VULKAN;
|
|
|
}
|
|
|
else if ((flags & SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL) == SDL.SDL_WindowFlags.SDL_WINDOW_OPENGL)
|
|
|
{
|
|
|
ActualGLDevice = OPENGL;
|
|
|
}
|
|
|
else if ( OSVersion.Equals("Mac OS X") ||
|
|
|
OSVersion.Equals("iOS") ||
|
|
|
OSVersion.Equals("tvOS") )
|
|
|
{
|
|
|
ActualGLDevice = METAL;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
switch (ActualGLDevice)
|
|
|
{
|
|
|
case VULKAN: break; // Maybe some day!
|
|
|
case METAL:
|
|
|
return new MetalDevice(presentationParameters);
|
|
|
case MODERNGL:
|
|
|
// FIXME: This is still experimental! -flibit
|
|
|
return new ModernGLDevice(presentationParameters);
|
|
|
case THREADEDGL:
|
|
|
// FIXME: This is still experimental! -flibit
|
|
|
return new ThreadedGLDevice(presentationParameters);
|
|
|
case OPENGL:
|
|
|
return new OpenGLDevice(presentationParameters);
|
|
|
}
|
|
|
throw new NotSupportedException(
|
|
|
"The requested GLDevice is not present!"
|
|
|
);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Graphics Methods
|
|
|
|
|
|
public static GraphicsAdapter[] GetGraphicsAdapters()
|
|
|
{
|
|
|
SDL.SDL_DisplayMode filler = new SDL.SDL_DisplayMode();
|
|
|
GraphicsAdapter[] adapters = new GraphicsAdapter[SDL.SDL_GetNumVideoDisplays()];
|
|
|
for (int i = 0; i < adapters.Length; i += 1)
|
|
|
{
|
|
|
List<DisplayMode> modes = new List<DisplayMode>();
|
|
|
int numModes = SDL.SDL_GetNumDisplayModes(i);
|
|
|
for (int j = numModes - 1; j >= 0; j -= 1)
|
|
|
{
|
|
|
SDL.SDL_GetDisplayMode(i, j, out filler);
|
|
|
|
|
|
// Check for dupes caused by varying refresh rates.
|
|
|
bool dupe = false;
|
|
|
foreach (DisplayMode mode in modes)
|
|
|
{
|
|
|
if (filler.w == mode.Width && filler.h == mode.Height)
|
|
|
{
|
|
|
dupe = true;
|
|
|
}
|
|
|
}
|
|
|
if (!dupe)
|
|
|
{
|
|
|
modes.Add(
|
|
|
new DisplayMode(
|
|
|
filler.w,
|
|
|
filler.h,
|
|
|
SurfaceFormat.Color // FIXME: Assumption!
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
adapters[i] = new GraphicsAdapter(
|
|
|
new DisplayModeCollection(modes),
|
|
|
@"\\.\DISPLAY" + (i + 1).ToString(),
|
|
|
SDL.SDL_GetDisplayName(i)
|
|
|
);
|
|
|
}
|
|
|
return adapters;
|
|
|
}
|
|
|
|
|
|
public static DisplayMode GetCurrentDisplayMode(int adapterIndex)
|
|
|
{
|
|
|
SDL.SDL_DisplayMode filler = new SDL.SDL_DisplayMode();
|
|
|
SDL.SDL_GetCurrentDisplayMode(adapterIndex, out filler);
|
|
|
|
|
|
if ( OSVersion.Equals("iOS") &&
|
|
|
Environment.GetEnvironmentVariable("FNA_GRAPHICS_ENABLE_HIGHDPI") == "1" )
|
|
|
{
|
|
|
// Provide the actual resolution in pixels, not points.
|
|
|
filler.w = RetinaWidth;
|
|
|
filler.h = RetinaHeight;
|
|
|
}
|
|
|
|
|
|
return new DisplayMode(
|
|
|
filler.w,
|
|
|
filler.h,
|
|
|
SurfaceFormat.Color // FIXME: Assumption!
|
|
|
);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Mouse Methods
|
|
|
|
|
|
public static void GetMouseState(
|
|
|
IntPtr window,
|
|
|
out int x,
|
|
|
out int y,
|
|
|
out ButtonState left,
|
|
|
out ButtonState middle,
|
|
|
out ButtonState right,
|
|
|
out ButtonState x1,
|
|
|
out ButtonState x2
|
|
|
) {
|
|
|
uint flags;
|
|
|
if (GetRelativeMouseMode())
|
|
|
{
|
|
|
flags = SDL.SDL_GetRelativeMouseState(out x, out y);
|
|
|
}
|
|
|
else if (SupportsGlobalMouse)
|
|
|
{
|
|
|
flags = SDL.SDL_GetGlobalMouseState(out x, out y);
|
|
|
int wx = 0, wy = 0;
|
|
|
SDL.SDL_GetWindowPosition(window, out wx, out wy);
|
|
|
x -= wx;
|
|
|
y -= wy;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
/* This is inaccurate, but what can you do... */
|
|
|
flags = SDL.SDL_GetMouseState(out x, out y);
|
|
|
}
|
|
|
left = (ButtonState) (flags & SDL.SDL_BUTTON_LMASK);
|
|
|
middle = (ButtonState) ((flags & SDL.SDL_BUTTON_MMASK) >> 1);
|
|
|
right = (ButtonState) ((flags & SDL.SDL_BUTTON_RMASK) >> 2);
|
|
|
x1 = (ButtonState) ((flags & SDL.SDL_BUTTON_X1MASK) >> 3);
|
|
|
x2 = (ButtonState) ((flags & SDL.SDL_BUTTON_X2MASK) >> 4);
|
|
|
}
|
|
|
|
|
|
public static void OnIsMouseVisibleChanged(bool visible)
|
|
|
{
|
|
|
SDL.SDL_ShowCursor(visible ? 1 : 0);
|
|
|
}
|
|
|
|
|
|
public static bool GetRelativeMouseMode()
|
|
|
{
|
|
|
return SDL.SDL_GetRelativeMouseMode() == SDL.SDL_bool.SDL_TRUE;
|
|
|
}
|
|
|
|
|
|
public static void SetRelativeMouseMode(bool enable)
|
|
|
{
|
|
|
SDL.SDL_SetRelativeMouseMode(
|
|
|
enable ?
|
|
|
SDL.SDL_bool.SDL_TRUE :
|
|
|
SDL.SDL_bool.SDL_FALSE
|
|
|
);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Storage Methods
|
|
|
|
|
|
private static string GetBaseDirectory()
|
|
|
{
|
|
|
if (Environment.GetEnvironmentVariable("FNA_SDL2_FORCE_BASE_PATH") != "1")
|
|
|
{
|
|
|
// If your platform uses a CLR, you want to be in this list!
|
|
|
if ( OSVersion.Equals("Windows") ||
|
|
|
OSVersion.Equals("Mac OS X") ||
|
|
|
OSVersion.Equals("Linux") ||
|
|
|
OSVersion.Equals("FreeBSD") ||
|
|
|
OSVersion.Equals("OpenBSD") ||
|
|
|
OSVersion.Equals("NetBSD") )
|
|
|
{
|
|
|
return AppDomain.CurrentDomain.BaseDirectory;
|
|
|
}
|
|
|
}
|
|
|
string result = SDL.SDL_GetBasePath();
|
|
|
if (string.IsNullOrEmpty(result))
|
|
|
{
|
|
|
result = AppDomain.CurrentDomain.BaseDirectory;
|
|
|
}
|
|
|
if (string.IsNullOrEmpty(result))
|
|
|
{
|
|
|
/* In the chance that there is no base directory,
|
|
|
* return the working directory and hope for the best.
|
|
|
*
|
|
|
* If we've reached this, the game has either been
|
|
|
* started from its directory, or a wrapper has set up
|
|
|
* the working directory to the game dir for us.
|
|
|
*
|
|
|
* Note about Android:
|
|
|
*
|
|
|
* There is no way from the C# side of things to cleanly
|
|
|
* obtain where the game is located without looking at an
|
|
|
* instance of System.Diagnostics.StackTrace or without
|
|
|
* some interop between the Java and C# side of things.
|
|
|
* We're assuming that either the environment itself is
|
|
|
* setting one of the possible base paths to point to the
|
|
|
* game dir, or that the Java side has called into the C#
|
|
|
* side to set Environment.CurrentDirectory.
|
|
|
*
|
|
|
* In the best case, nothing would be set and the game
|
|
|
* wouldn't use the title location in the first place, as
|
|
|
* the assets would be read directly from the .apk / .obb
|
|
|
* -ade
|
|
|
*/
|
|
|
result = Environment.CurrentDirectory;
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
public static string GetStorageRoot()
|
|
|
{
|
|
|
// Generate the path of the game's savefolder
|
|
|
string exeName = Path.GetFileNameWithoutExtension(
|
|
|
AppDomain.CurrentDomain.FriendlyName
|
|
|
).Replace(".vshost", "");
|
|
|
|
|
|
// Get the OS save folder, append the EXE name
|
|
|
if (OSVersion.Equals("Windows"))
|
|
|
{
|
|
|
return Path.Combine(
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
|
|
|
"SavedGames",
|
|
|
exeName
|
|
|
);
|
|
|
}
|
|
|
if (OSVersion.Equals("Mac OS X"))
|
|
|
{
|
|
|
string osConfigDir = Environment.GetEnvironmentVariable("HOME");
|
|
|
if (String.IsNullOrEmpty(osConfigDir))
|
|
|
{
|
|
|
return "."; // Oh well.
|
|
|
}
|
|
|
return Path.Combine(
|
|
|
osConfigDir,
|
|
|
"Library/Application Support",
|
|
|
exeName
|
|
|
);
|
|
|
}
|
|
|
if ( OSVersion.Equals("Linux") ||
|
|
|
OSVersion.Equals("FreeBSD") ||
|
|
|
OSVersion.Equals("OpenBSD") ||
|
|
|
OSVersion.Equals("NetBSD") )
|
|
|
{
|
|
|
// Assuming a non-macOS Unix platform will follow the XDG. Which it should.
|
|
|
string osConfigDir = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
|
|
|
if (String.IsNullOrEmpty(osConfigDir))
|
|
|
{
|
|
|
osConfigDir = Environment.GetEnvironmentVariable("HOME");
|
|
|
if (String.IsNullOrEmpty(osConfigDir))
|
|
|
{
|
|
|
return "."; // Oh well.
|
|
|
}
|
|
|
osConfigDir += "/.local/share";
|
|
|
}
|
|
|
return Path.Combine(osConfigDir, exeName);
|
|
|
}
|
|
|
|
|
|
/* There is a minor inaccuracy here: SDL_GetPrefPath
|
|
|
* creates the directories right away, whereas XNA will
|
|
|
* only create the directory upon creating a container.
|
|
|
* So if you create a StorageDevice and hit a property,
|
|
|
* the game folder is made early!
|
|
|
* -flibit
|
|
|
*/
|
|
|
return SDL.SDL_GetPrefPath(null, exeName);
|
|
|
}
|
|
|
|
|
|
public static DriveInfo GetDriveInfo(string storageRoot)
|
|
|
{
|
|
|
if (OSVersion.Equals("WinRT"))
|
|
|
{
|
|
|
// WinRT DriveInfo is a bunch of crap -flibit
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
DriveInfo result;
|
|
|
try
|
|
|
{
|
|
|
result = new DriveInfo(MonoPathRootWorkaround(storageRoot));
|
|
|
}
|
|
|
catch(Exception e)
|
|
|
{
|
|
|
FNALoggerEXT.LogError("Failed to get DriveInfo: " + e.ToString());
|
|
|
result = null;
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
private static string MonoPathRootWorkaround(string storageRoot)
|
|
|
{
|
|
|
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
|
|
|
{
|
|
|
// This is what we should be doing everywhere...
|
|
|
return Path.GetPathRoot(storageRoot);
|
|
|
}
|
|
|
|
|
|
// This is stolen from Mono's Path.cs
|
|
|
if (storageRoot == null)
|
|
|
{
|
|
|
return null;
|
|
|
}
|
|
|
if (storageRoot.Trim().Length == 0)
|
|
|
{
|
|
|
throw new ArgumentException("The specified path is not of a legal form.");
|
|
|
}
|
|
|
if (!Path.IsPathRooted(storageRoot))
|
|
|
{
|
|
|
return string.Empty;
|
|
|
}
|
|
|
|
|
|
/* FIXME: Mono bug!
|
|
|
*
|
|
|
* For Unix, the Mono Path.GetPathRoot is pretty lazy:
|
|
|
* https://github.com/mono/mono/blob/master/mcs/class/corlib/System.IO/Path.cs#L443
|
|
|
* It should actually be checking the drives and
|
|
|
* comparing them to the provided path.
|
|
|
* If a Mono maintainer is reading this, please steal
|
|
|
* this code so we don't have to hack around Mono!
|
|
|
*
|
|
|
* -flibit
|
|
|
*/
|
|
|
int drive = -1, length = 0;
|
|
|
string[] drives = Environment.GetLogicalDrives();
|
|
|
for (int i = 0; i < drives.Length; i += 1)
|
|
|
{
|
|
|
if (string.IsNullOrEmpty(drives[i]))
|
|
|
{
|
|
|
// ... What?
|
|
|
continue;
|
|
|
}
|
|
|
string name = drives[i];
|
|
|
if (name[name.Length - 1] != Path.DirectorySeparatorChar)
|
|
|
{
|
|
|
name += Path.DirectorySeparatorChar;
|
|
|
}
|
|
|
if ( storageRoot.StartsWith(name) &&
|
|
|
name.Length > length )
|
|
|
{
|
|
|
drive = i;
|
|
|
length = name.Length;
|
|
|
}
|
|
|
}
|
|
|
if (drive >= 0)
|
|
|
{
|
|
|
return drives[drive];
|
|
|
}
|
|
|
|
|
|
// Uhhhhh
|
|
|
return Path.GetPathRoot(storageRoot);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Logging/Messaging Methods
|
|
|
|
|
|
public static void ShowRuntimeError(string title, string message)
|
|
|
{
|
|
|
SDL.SDL_ShowSimpleMessageBox(
|
|
|
SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
|
|
|
title ?? "",
|
|
|
message ?? "",
|
|
|
IntPtr.Zero
|
|
|
);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Image I/O Methods
|
|
|
|
|
|
private static IntPtr TextureDataFromStreamInternal(
|
|
|
Stream stream,
|
|
|
int reqWidth,
|
|
|
int reqHeight,
|
|
|
bool zoom
|
|
|
) {
|
|
|
// Load the SDL_Surface* from RWops, get the image data
|
|
|
IntPtr surface = SDL_image.IMG_Load_RW(
|
|
|
FakeRWops.Alloc(stream),
|
|
|
1
|
|
|
);
|
|
|
if (surface == IntPtr.Zero)
|
|
|
{
|
|
|
// File not found, supported, etc.
|
|
|
FNALoggerEXT.LogError(
|
|
|
"TextureDataFromStream: " +
|
|
|
SDL.SDL_GetError()
|
|
|
);
|
|
|
return IntPtr.Zero;
|
|
|
}
|
|
|
surface = INTERNAL_convertSurfaceFormat(surface);
|
|
|
|
|
|
// Image scaling, if applicable
|
|
|
if (reqWidth != -1 && reqHeight != -1)
|
|
|
{
|
|
|
// Get the file surface dimensions now...
|
|
|
int rw;
|
|
|
int rh;
|
|
|
unsafe
|
|
|
{
|
|
|
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface;
|
|
|
rw = surPtr->w;
|
|
|
rh = surPtr->h;
|
|
|
}
|
|
|
|
|
|
// Calculate the image scale factor
|
|
|
bool scaleWidth;
|
|
|
if (zoom)
|
|
|
{
|
|
|
scaleWidth = rw < rh;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
scaleWidth = rw > rh;
|
|
|
}
|
|
|
float scale;
|
|
|
if (scaleWidth)
|
|
|
{
|
|
|
scale = reqWidth / (float) rw;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
scale = reqHeight / (float) rh;
|
|
|
}
|
|
|
|
|
|
// Calculate the scaled image size, crop if zoomed
|
|
|
int resultWidth;
|
|
|
int resultHeight;
|
|
|
SDL.SDL_Rect crop = new SDL.SDL_Rect();
|
|
|
if (zoom)
|
|
|
{
|
|
|
resultWidth = reqWidth;
|
|
|
resultHeight = reqHeight;
|
|
|
if (scaleWidth)
|
|
|
{
|
|
|
crop.x = 0;
|
|
|
crop.w = rw;
|
|
|
crop.y = (int) (rh / 2 - (reqHeight / scale) / 2);
|
|
|
crop.h = (int) (reqHeight / scale);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
crop.y = 0;
|
|
|
crop.h = rh;
|
|
|
crop.x = (int) (rw / 2 - (reqWidth / scale) / 2);
|
|
|
crop.w = (int) (reqWidth / scale);
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
resultWidth = (int) (rw * scale);
|
|
|
resultHeight = (int) (rh * scale);
|
|
|
}
|
|
|
|
|
|
// Alloc surface, blit!
|
|
|
IntPtr newSurface = SDL.SDL_CreateRGBSurface(
|
|
|
0,
|
|
|
resultWidth,
|
|
|
resultHeight,
|
|
|
32,
|
|
|
0x000000FF,
|
|
|
0x0000FF00,
|
|
|
0x00FF0000,
|
|
|
0xFF000000
|
|
|
);
|
|
|
SDL.SDL_SetSurfaceBlendMode(
|
|
|
surface,
|
|
|
SDL.SDL_BlendMode.SDL_BLENDMODE_NONE
|
|
|
);
|
|
|
if (zoom)
|
|
|
{
|
|
|
SDL.SDL_BlitScaled(
|
|
|
surface,
|
|
|
ref crop,
|
|
|
newSurface,
|
|
|
IntPtr.Zero
|
|
|
);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
SDL.SDL_BlitScaled(
|
|
|
surface,
|
|
|
IntPtr.Zero,
|
|
|
newSurface,
|
|
|
IntPtr.Zero
|
|
|
);
|
|
|
}
|
|
|
SDL.SDL_FreeSurface(surface);
|
|
|
surface = newSurface;
|
|
|
}
|
|
|
|
|
|
return surface;
|
|
|
}
|
|
|
|
|
|
private static unsafe void TextureDataClearAlpha(
|
|
|
byte* pixels,
|
|
|
int len
|
|
|
) {
|
|
|
/* Ensure that the alpha pixels are... well, actual alpha.
|
|
|
* You think this looks stupid, but be assured: Your paint program is
|
|
|
* almost certainly even stupider.
|
|
|
* -flibit
|
|
|
*/
|
|
|
for (int i = 0; i < len; i += 4, pixels += 4)
|
|
|
{
|
|
|
if (pixels[3] == 0)
|
|
|
{
|
|
|
pixels[0] = 0;
|
|
|
pixels[1] = 0;
|
|
|
pixels[2] = 0;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static void TextureDataFromStream(
|
|
|
Stream stream,
|
|
|
out int width,
|
|
|
out int height,
|
|
|
out byte[] pixels,
|
|
|
int reqWidth = -1,
|
|
|
int reqHeight = -1,
|
|
|
bool zoom = false
|
|
|
) {
|
|
|
IntPtr surface = TextureDataFromStreamInternal(
|
|
|
stream,
|
|
|
reqWidth,
|
|
|
reqHeight,
|
|
|
zoom
|
|
|
);
|
|
|
if (surface == IntPtr.Zero)
|
|
|
{
|
|
|
width = 0;
|
|
|
height = 0;
|
|
|
pixels = null;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Copy surface data to output managed byte array
|
|
|
unsafe
|
|
|
{
|
|
|
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface;
|
|
|
width = surPtr->w;
|
|
|
height = surPtr->h;
|
|
|
pixels = new byte[width * height * 4]; // MUST be SurfaceFormat.Color!
|
|
|
|
|
|
Marshal.Copy(surPtr->pixels, pixels, 0, pixels.Length);
|
|
|
fixed (byte* pixPtr = &pixels[0])
|
|
|
{
|
|
|
TextureDataClearAlpha(pixPtr, pixels.Length);
|
|
|
}
|
|
|
}
|
|
|
SDL.SDL_FreeSurface(surface);
|
|
|
}
|
|
|
|
|
|
public static void TextureDataFromStreamPtr(
|
|
|
Stream stream,
|
|
|
out int width,
|
|
|
out int height,
|
|
|
out IntPtr pixels,
|
|
|
out int len,
|
|
|
int reqWidth = -1,
|
|
|
int reqHeight = -1,
|
|
|
bool zoom = false
|
|
|
) {
|
|
|
IntPtr surface = TextureDataFromStreamInternal(
|
|
|
stream,
|
|
|
reqWidth,
|
|
|
reqHeight,
|
|
|
zoom
|
|
|
);
|
|
|
if (surface == IntPtr.Zero)
|
|
|
{
|
|
|
width = 0;
|
|
|
height = 0;
|
|
|
pixels = IntPtr.Zero;
|
|
|
len = 0;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Copy surface data to output managed byte array
|
|
|
unsafe
|
|
|
{
|
|
|
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface;
|
|
|
width = surPtr->w;
|
|
|
height = surPtr->h;
|
|
|
len = width * height * 4;
|
|
|
pixels = Marshal.AllocHGlobal(len); // MUST be SurfaceFormat.Color!
|
|
|
|
|
|
SDL.SDL_memcpy(pixels, surPtr->pixels, (IntPtr) len);
|
|
|
TextureDataClearAlpha((byte*) pixels, len);
|
|
|
}
|
|
|
SDL.SDL_FreeSurface(surface);
|
|
|
}
|
|
|
|
|
|
public static void SavePNG(
|
|
|
Stream stream,
|
|
|
int width,
|
|
|
int height,
|
|
|
int imgWidth,
|
|
|
int imgHeight,
|
|
|
byte[] data
|
|
|
) {
|
|
|
IntPtr surface = INTERNAL_getScaledSurface(
|
|
|
data,
|
|
|
imgWidth,
|
|
|
imgHeight,
|
|
|
width,
|
|
|
height
|
|
|
);
|
|
|
SDL_image.IMG_SavePNG_RW(
|
|
|
surface,
|
|
|
FakeRWops.Alloc(stream),
|
|
|
1
|
|
|
);
|
|
|
SDL.SDL_FreeSurface(surface);
|
|
|
}
|
|
|
|
|
|
public static void SaveJPG(
|
|
|
Stream stream,
|
|
|
int width,
|
|
|
int height,
|
|
|
int imgWidth,
|
|
|
int imgHeight,
|
|
|
byte[] data
|
|
|
) {
|
|
|
// FIXME: What does XNA pick for this? -flibit
|
|
|
const int quality = 100;
|
|
|
|
|
|
IntPtr surface = INTERNAL_getScaledSurface(
|
|
|
data,
|
|
|
imgWidth,
|
|
|
imgHeight,
|
|
|
width,
|
|
|
height
|
|
|
);
|
|
|
|
|
|
// FIXME: Hack for Bugzilla #3972
|
|
|
IntPtr temp = SDL.SDL_ConvertSurfaceFormat(
|
|
|
surface,
|
|
|
SDL.SDL_PIXELFORMAT_RGB24,
|
|
|
0
|
|
|
);
|
|
|
SDL.SDL_FreeSurface(surface);
|
|
|
surface = temp;
|
|
|
|
|
|
SDL_image.IMG_SaveJPG_RW(
|
|
|
surface,
|
|
|
FakeRWops.Alloc(stream),
|
|
|
1,
|
|
|
quality
|
|
|
);
|
|
|
SDL.SDL_FreeSurface(surface);
|
|
|
}
|
|
|
|
|
|
public static IntPtr INTERNAL_getScaledSurface(
|
|
|
byte[] data,
|
|
|
int srcW,
|
|
|
int srcH,
|
|
|
int dstW,
|
|
|
int dstH
|
|
|
) {
|
|
|
// Create an SDL_Surface*, write the pixel data
|
|
|
IntPtr surface = SDL.SDL_CreateRGBSurface(
|
|
|
0,
|
|
|
srcW,
|
|
|
srcH,
|
|
|
32,
|
|
|
0x000000FF,
|
|
|
0x0000FF00,
|
|
|
0x00FF0000,
|
|
|
0xFF000000
|
|
|
);
|
|
|
SDL.SDL_LockSurface(surface);
|
|
|
unsafe
|
|
|
{
|
|
|
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface;
|
|
|
Marshal.Copy(
|
|
|
data,
|
|
|
0,
|
|
|
surPtr->pixels,
|
|
|
data.Length
|
|
|
);
|
|
|
}
|
|
|
SDL.SDL_UnlockSurface(surface);
|
|
|
|
|
|
// Blit to a scaled surface of the size we want, if needed.
|
|
|
if (srcW != dstW || srcH != dstH)
|
|
|
{
|
|
|
IntPtr scaledSurface = SDL.SDL_CreateRGBSurface(
|
|
|
0,
|
|
|
dstW,
|
|
|
dstH,
|
|
|
32,
|
|
|
0x000000FF,
|
|
|
0x0000FF00,
|
|
|
0x00FF0000,
|
|
|
0xFF000000
|
|
|
);
|
|
|
SDL.SDL_SetSurfaceBlendMode(
|
|
|
surface,
|
|
|
SDL.SDL_BlendMode.SDL_BLENDMODE_NONE
|
|
|
);
|
|
|
SDL.SDL_BlitScaled(
|
|
|
surface,
|
|
|
IntPtr.Zero,
|
|
|
scaledSurface,
|
|
|
IntPtr.Zero
|
|
|
);
|
|
|
SDL.SDL_FreeSurface(surface);
|
|
|
surface = scaledSurface;
|
|
|
}
|
|
|
|
|
|
return surface;
|
|
|
}
|
|
|
|
|
|
private static unsafe IntPtr INTERNAL_convertSurfaceFormat(IntPtr surface)
|
|
|
{
|
|
|
IntPtr result = surface;
|
|
|
unsafe
|
|
|
{
|
|
|
SDL.SDL_Surface* surPtr = (SDL.SDL_Surface*) surface;
|
|
|
SDL.SDL_PixelFormat* pixelFormatPtr = (SDL.SDL_PixelFormat*) surPtr->format;
|
|
|
|
|
|
// SurfaceFormat.Color is SDL_PIXELFORMAT_ABGR8888
|
|
|
if (pixelFormatPtr->format != SDL.SDL_PIXELFORMAT_ABGR8888)
|
|
|
{
|
|
|
// Create a properly formatted copy, free the old surface
|
|
|
result = SDL.SDL_ConvertSurfaceFormat(surface, SDL.SDL_PIXELFORMAT_ABGR8888, 0);
|
|
|
SDL.SDL_FreeSurface(surface);
|
|
|
}
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
private static class FakeRWops
|
|
|
{
|
|
|
private static readonly Dictionary<IntPtr, Stream> streamMap =
|
|
|
new Dictionary<IntPtr, Stream>();
|
|
|
|
|
|
// Based on PNG_ZBUF_SIZE default
|
|
|
private static byte[] temp = new byte[8192];
|
|
|
|
|
|
private static readonly SDL.SDLRWopsSizeCallback sizeFunc = size;
|
|
|
private static readonly SDL.SDLRWopsSeekCallback seekFunc = seek;
|
|
|
private static readonly SDL.SDLRWopsReadCallback readFunc = read;
|
|
|
private static readonly SDL.SDLRWopsWriteCallback writeFunc = write;
|
|
|
private static readonly SDL.SDLRWopsCloseCallback closeFunc = close;
|
|
|
private static readonly IntPtr sizePtr =
|
|
|
Marshal.GetFunctionPointerForDelegate(sizeFunc);
|
|
|
private static readonly IntPtr seekPtr =
|
|
|
Marshal.GetFunctionPointerForDelegate(seekFunc);
|
|
|
private static readonly IntPtr readPtr =
|
|
|
Marshal.GetFunctionPointerForDelegate(readFunc);
|
|
|
private static readonly IntPtr writePtr =
|
|
|
Marshal.GetFunctionPointerForDelegate(writeFunc);
|
|
|
private static readonly IntPtr closePtr =
|
|
|
Marshal.GetFunctionPointerForDelegate(closeFunc);
|
|
|
|
|
|
public static IntPtr Alloc(Stream stream)
|
|
|
{
|
|
|
IntPtr rwops = SDL.SDL_AllocRW();
|
|
|
unsafe
|
|
|
{
|
|
|
SDL.SDL_RWops* p = (SDL.SDL_RWops*) rwops;
|
|
|
p->size = sizePtr;
|
|
|
p->seek = seekPtr;
|
|
|
p->read = readPtr;
|
|
|
p->write = writePtr;
|
|
|
p->close = closePtr;
|
|
|
}
|
|
|
lock (streamMap)
|
|
|
{
|
|
|
streamMap.Add(rwops, stream);
|
|
|
}
|
|
|
return rwops;
|
|
|
}
|
|
|
|
|
|
private static byte[] GetTemp(int len)
|
|
|
{
|
|
|
if (len > temp.Length)
|
|
|
{
|
|
|
temp = new byte[len];
|
|
|
}
|
|
|
return temp;
|
|
|
}
|
|
|
|
|
|
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsSizeCallback))]
|
|
|
private static long size(IntPtr context)
|
|
|
{
|
|
|
Stream stream;
|
|
|
lock (streamMap)
|
|
|
{
|
|
|
stream = streamMap[context];
|
|
|
}
|
|
|
return stream.Length;
|
|
|
}
|
|
|
|
|
|
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsSeekCallback))]
|
|
|
private static long seek(IntPtr context, long offset, int whence)
|
|
|
{
|
|
|
Stream stream;
|
|
|
lock (streamMap)
|
|
|
{
|
|
|
stream = streamMap[context];
|
|
|
}
|
|
|
stream.Seek(offset, (SeekOrigin) whence);
|
|
|
return stream.Position;
|
|
|
}
|
|
|
|
|
|
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsReadCallback))]
|
|
|
private static IntPtr read(
|
|
|
IntPtr context,
|
|
|
IntPtr ptr,
|
|
|
IntPtr size,
|
|
|
IntPtr maxnum
|
|
|
) {
|
|
|
Stream stream;
|
|
|
int len = size.ToInt32() * maxnum.ToInt32();
|
|
|
lock (streamMap)
|
|
|
{
|
|
|
stream = streamMap[context];
|
|
|
|
|
|
// Other streams may contend for temp!
|
|
|
len = stream.Read(
|
|
|
GetTemp(len),
|
|
|
0,
|
|
|
len
|
|
|
);
|
|
|
Marshal.Copy(temp, 0, ptr, len);
|
|
|
}
|
|
|
return (IntPtr) len;
|
|
|
}
|
|
|
|
|
|
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsWriteCallback))]
|
|
|
private static IntPtr write(
|
|
|
IntPtr context,
|
|
|
IntPtr ptr,
|
|
|
IntPtr size,
|
|
|
IntPtr num
|
|
|
) {
|
|
|
Stream stream;
|
|
|
int len = size.ToInt32() * num.ToInt32();
|
|
|
lock (streamMap)
|
|
|
{
|
|
|
stream = streamMap[context];
|
|
|
|
|
|
// Other streams may contend for temp!
|
|
|
Marshal.Copy(
|
|
|
ptr,
|
|
|
GetTemp(len),
|
|
|
0,
|
|
|
len
|
|
|
);
|
|
|
stream.Write(temp, 0, len);
|
|
|
}
|
|
|
return (IntPtr) len;
|
|
|
}
|
|
|
|
|
|
[ObjCRuntime.MonoPInvokeCallback(typeof(SDL.SDLRWopsCloseCallback))]
|
|
|
public static int close(IntPtr context)
|
|
|
{
|
|
|
lock (streamMap)
|
|
|
{
|
|
|
streamMap.Remove(context);
|
|
|
}
|
|
|
SDL.SDL_FreeRW(context);
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Microphone Implementation
|
|
|
|
|
|
/* Microphone is almost never used, so we give this subsystem
|
|
|
* special treatment and init only when we start calling these
|
|
|
* functions.
|
|
|
* -flibit
|
|
|
*/
|
|
|
private static bool micInit = false;
|
|
|
|
|
|
public static Microphone[] GetMicrophones()
|
|
|
{
|
|
|
// Init subsystem if needed
|
|
|
if (!micInit)
|
|
|
{
|
|
|
SDL.SDL_InitSubSystem(SDL.SDL_INIT_AUDIO);
|
|
|
micInit = true;
|
|
|
}
|
|
|
|
|
|
// How many devices do we have...?
|
|
|
int numDev = SDL.SDL_GetNumAudioDevices(1);
|
|
|
if (numDev < 1)
|
|
|
{
|
|
|
// Blech
|
|
|
return new Microphone[0];
|
|
|
}
|
|
|
Microphone[] result = new Microphone[numDev + 1];
|
|
|
|
|
|
// Default input format
|
|
|
SDL.SDL_AudioSpec have;
|
|
|
SDL.SDL_AudioSpec want = new SDL.SDL_AudioSpec();
|
|
|
want.freq = Microphone.SAMPLERATE;
|
|
|
want.format = SDL.AUDIO_S16;
|
|
|
want.channels = 1;
|
|
|
want.samples = 4096; /* FIXME: Anything specific? */
|
|
|
|
|
|
// First mic is always OS default
|
|
|
result[0] = new Microphone(
|
|
|
SDL.SDL_OpenAudioDevice(
|
|
|
null,
|
|
|
1,
|
|
|
ref want,
|
|
|
out have,
|
|
|
0
|
|
|
),
|
|
|
"Default Device"
|
|
|
);
|
|
|
for (int i = 0; i < numDev; i += 1)
|
|
|
{
|
|
|
string name = SDL.SDL_GetAudioDeviceName(i, 1);
|
|
|
result[i + 1] = new Microphone(
|
|
|
SDL.SDL_OpenAudioDevice(
|
|
|
name,
|
|
|
1,
|
|
|
ref want,
|
|
|
out have,
|
|
|
0
|
|
|
),
|
|
|
name
|
|
|
);
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
public static unsafe int GetMicrophoneSamples(
|
|
|
uint handle,
|
|
|
byte[] buffer,
|
|
|
int offset,
|
|
|
int count
|
|
|
) {
|
|
|
fixed (byte* ptr = &buffer[offset])
|
|
|
{
|
|
|
return (int) SDL.SDL_DequeueAudio(
|
|
|
handle,
|
|
|
(IntPtr) ptr,
|
|
|
(uint) count
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static int GetMicrophoneQueuedBytes(uint handle)
|
|
|
{
|
|
|
return (int) SDL.SDL_GetQueuedAudioSize(handle);
|
|
|
}
|
|
|
|
|
|
public static void StartMicrophone(uint handle)
|
|
|
{
|
|
|
SDL.SDL_PauseAudioDevice(handle, 0);
|
|
|
}
|
|
|
|
|
|
public static void StopMicrophone(uint handle)
|
|
|
{
|
|
|
SDL.SDL_PauseAudioDevice(handle, 1);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region GamePad Backend
|
|
|
|
|
|
// Controller device information
|
|
|
private static IntPtr[] INTERNAL_devices = new IntPtr[GamePad.GAMEPAD_COUNT];
|
|
|
private static Dictionary<int, int> INTERNAL_instanceList = new Dictionary<int, int>();
|
|
|
private static string[] INTERNAL_guids = GenStringArray();
|
|
|
|
|
|
// Light bar information
|
|
|
private static string[] INTERNAL_lightBars = GenStringArray();
|
|
|
|
|
|
// Cached GamePadStates/Capabilities
|
|
|
private static GamePadState[] INTERNAL_states = new GamePadState[GamePad.GAMEPAD_COUNT];
|
|
|
private static GamePadCapabilities[] INTERNAL_capabilities = new GamePadCapabilities[GamePad.GAMEPAD_COUNT];
|
|
|
|
|
|
private static readonly GamePadType[] INTERNAL_gamepadType = new GamePadType[]
|
|
|
{
|
|
|
GamePadType.Unknown,
|
|
|
GamePadType.GamePad,
|
|
|
GamePadType.Wheel,
|
|
|
GamePadType.ArcadeStick,
|
|
|
GamePadType.FlightStick,
|
|
|
GamePadType.DancePad,
|
|
|
GamePadType.Guitar,
|
|
|
GamePadType.DrumKit,
|
|
|
GamePadType.BigButtonPad
|
|
|
};
|
|
|
|
|
|
public static GamePadCapabilities GetGamePadCapabilities(int index)
|
|
|
{
|
|
|
if (INTERNAL_devices[index] == IntPtr.Zero)
|
|
|
{
|
|
|
return new GamePadCapabilities();
|
|
|
}
|
|
|
return INTERNAL_capabilities[index];
|
|
|
}
|
|
|
|
|
|
public static GamePadState GetGamePadState(int index, GamePadDeadZone deadZoneMode)
|
|
|
{
|
|
|
IntPtr device = INTERNAL_devices[index];
|
|
|
if (device == IntPtr.Zero)
|
|
|
{
|
|
|
return new GamePadState();
|
|
|
}
|
|
|
|
|
|
// The "master" button state is built from this.
|
|
|
Buttons gc_buttonState = (Buttons) 0;
|
|
|
|
|
|
// Sticks
|
|
|
Vector2 stickLeft = new Vector2(
|
|
|
(float) SDL.SDL_GameControllerGetAxis(
|
|
|
device,
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX
|
|
|
) / 32767.0f,
|
|
|
(float) SDL.SDL_GameControllerGetAxis(
|
|
|
device,
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY
|
|
|
) / -32767.0f
|
|
|
);
|
|
|
Vector2 stickRight = new Vector2(
|
|
|
(float) SDL.SDL_GameControllerGetAxis(
|
|
|
device,
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX
|
|
|
) / 32767.0f,
|
|
|
(float) SDL.SDL_GameControllerGetAxis(
|
|
|
device,
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY
|
|
|
) / -32767.0f
|
|
|
);
|
|
|
gc_buttonState |= READ_StickToButtons(
|
|
|
stickLeft,
|
|
|
Buttons.LeftThumbstickLeft,
|
|
|
Buttons.LeftThumbstickRight,
|
|
|
Buttons.LeftThumbstickUp,
|
|
|
Buttons.LeftThumbstickDown,
|
|
|
GamePad.LeftDeadZone
|
|
|
);
|
|
|
gc_buttonState |= READ_StickToButtons(
|
|
|
stickRight,
|
|
|
Buttons.RightThumbstickLeft,
|
|
|
Buttons.RightThumbstickRight,
|
|
|
Buttons.RightThumbstickUp,
|
|
|
Buttons.RightThumbstickDown,
|
|
|
GamePad.RightDeadZone
|
|
|
);
|
|
|
|
|
|
// Triggers
|
|
|
float triggerLeft = (float) SDL.SDL_GameControllerGetAxis(
|
|
|
device,
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT
|
|
|
) / 32767.0f;
|
|
|
float triggerRight = (float) SDL.SDL_GameControllerGetAxis(
|
|
|
device,
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
|
|
) / 32767.0f;
|
|
|
if (triggerLeft > GamePad.TriggerThreshold)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.LeftTrigger;
|
|
|
}
|
|
|
if (triggerRight > GamePad.TriggerThreshold)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.RightTrigger;
|
|
|
}
|
|
|
|
|
|
// Buttons
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.A;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.B;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.X;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.Y;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.Back;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.BigButton;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.Start;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.LeftStick;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.RightStick;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.LeftShoulder;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.RightShoulder;
|
|
|
}
|
|
|
|
|
|
// DPad
|
|
|
ButtonState dpadUp = ButtonState.Released;
|
|
|
ButtonState dpadDown = ButtonState.Released;
|
|
|
ButtonState dpadLeft = ButtonState.Released;
|
|
|
ButtonState dpadRight = ButtonState.Released;
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.DPadUp;
|
|
|
dpadUp = ButtonState.Pressed;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.DPadDown;
|
|
|
dpadDown = ButtonState.Pressed;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.DPadLeft;
|
|
|
dpadLeft = ButtonState.Pressed;
|
|
|
}
|
|
|
if (SDL.SDL_GameControllerGetButton(device, SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT) != 0)
|
|
|
{
|
|
|
gc_buttonState |= Buttons.DPadRight;
|
|
|
dpadRight = ButtonState.Pressed;
|
|
|
}
|
|
|
|
|
|
// Build the GamePadState, increment PacketNumber if state changed.
|
|
|
GamePadState gc_builtState = new GamePadState(
|
|
|
new GamePadThumbSticks(stickLeft, stickRight, deadZoneMode),
|
|
|
new GamePadTriggers(triggerLeft, triggerRight, deadZoneMode),
|
|
|
new GamePadButtons(gc_buttonState),
|
|
|
new GamePadDPad(dpadUp, dpadDown, dpadLeft, dpadRight)
|
|
|
);
|
|
|
gc_builtState.IsConnected = true;
|
|
|
gc_builtState.PacketNumber = INTERNAL_states[index].PacketNumber;
|
|
|
if (gc_builtState != INTERNAL_states[index])
|
|
|
{
|
|
|
gc_builtState.PacketNumber += 1;
|
|
|
INTERNAL_states[index] = gc_builtState;
|
|
|
}
|
|
|
|
|
|
return gc_builtState;
|
|
|
}
|
|
|
|
|
|
public static bool SetGamePadVibration(int index, float leftMotor, float rightMotor)
|
|
|
{
|
|
|
IntPtr device = INTERNAL_devices[index];
|
|
|
if (device == IntPtr.Zero)
|
|
|
{
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return SDL.SDL_GameControllerRumble(
|
|
|
device,
|
|
|
(ushort) (MathHelper.Clamp(leftMotor, 0.0f, 1.0f) * 0xFFFF),
|
|
|
(ushort) (MathHelper.Clamp(rightMotor, 0.0f, 1.0f) * 0xFFFF),
|
|
|
0
|
|
|
) == 0;
|
|
|
}
|
|
|
|
|
|
public static string GetGamePadGUID(int index)
|
|
|
{
|
|
|
return INTERNAL_guids[index];
|
|
|
}
|
|
|
|
|
|
public static void SetGamePadLightBar(int index, Color color)
|
|
|
{
|
|
|
if (String.IsNullOrEmpty(INTERNAL_lightBars[index]))
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
string baseDir = INTERNAL_lightBars[index];
|
|
|
try
|
|
|
{
|
|
|
File.WriteAllText(baseDir + "red/brightness", color.R.ToString());
|
|
|
File.WriteAllText(baseDir + "green/brightness", color.G.ToString());
|
|
|
File.WriteAllText(baseDir + "blue/brightness", color.B.ToString());
|
|
|
}
|
|
|
catch
|
|
|
{
|
|
|
// If something went wrong, assume the worst and just remove it.
|
|
|
INTERNAL_lightBars[index] = String.Empty;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private static void INTERNAL_AddInstance(int dev)
|
|
|
{
|
|
|
int which = -1;
|
|
|
for (int i = 0; i < INTERNAL_devices.Length; i += 1)
|
|
|
{
|
|
|
if (INTERNAL_devices[i] == IntPtr.Zero)
|
|
|
{
|
|
|
which = i;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (which == -1)
|
|
|
{
|
|
|
return; // Ignoring more than 4 controllers.
|
|
|
}
|
|
|
|
|
|
// Clear the error buffer. We're about to do a LOT of dangerous stuff.
|
|
|
SDL.SDL_ClearError();
|
|
|
|
|
|
// Open the device!
|
|
|
INTERNAL_devices[which] = SDL.SDL_GameControllerOpen(dev);
|
|
|
|
|
|
// We use this when dealing with GUID initialization.
|
|
|
IntPtr thisJoystick = SDL.SDL_GameControllerGetJoystick(INTERNAL_devices[which]);
|
|
|
|
|
|
// Pair up the instance ID to the player index.
|
|
|
// FIXME: Remove check after 2.0.4? -flibit
|
|
|
int thisInstance = SDL.SDL_JoystickInstanceID(thisJoystick);
|
|
|
if (INTERNAL_instanceList.ContainsKey(thisInstance))
|
|
|
{
|
|
|
// Duplicate? Usually this is OSX being dumb, but...?
|
|
|
INTERNAL_devices[which] = IntPtr.Zero;
|
|
|
return;
|
|
|
}
|
|
|
INTERNAL_instanceList.Add(thisInstance, which);
|
|
|
|
|
|
// Start with a fresh state.
|
|
|
INTERNAL_states[which] = new GamePadState();
|
|
|
INTERNAL_states[which].IsConnected = true;
|
|
|
|
|
|
// Initialize the haptics for the joystick, if applicable.
|
|
|
bool hasRumble = SDL.SDL_GameControllerRumble(
|
|
|
INTERNAL_devices[which],
|
|
|
0,
|
|
|
0,
|
|
|
0
|
|
|
) == 0;
|
|
|
|
|
|
// An SDL_GameController _should_ always be complete...
|
|
|
GamePadCapabilities caps = new GamePadCapabilities();
|
|
|
caps.IsConnected = true;
|
|
|
caps.GamePadType = INTERNAL_gamepadType[(int) SDL.SDL_JoystickGetType(thisJoystick)];
|
|
|
caps.HasAButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_A
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasBButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_B
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasXButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_X
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasYButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_Y
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasBackButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_BACK
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasBigButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_GUIDE
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasStartButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_START
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasLeftStickButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSTICK
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasRightStickButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSTICK
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasLeftShoulderButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_LEFTSHOULDER
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasRightShoulderButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_RIGHTSHOULDER
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasDPadUpButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_UP
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasDPadDownButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_DOWN
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasDPadLeftButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_LEFT
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasDPadRightButton = SDL.SDL_GameControllerGetBindForButton(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerButton.SDL_CONTROLLER_BUTTON_DPAD_RIGHT
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasLeftXThumbStick = SDL.SDL_GameControllerGetBindForAxis(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTX
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasLeftYThumbStick = SDL.SDL_GameControllerGetBindForAxis(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_LEFTY
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasRightXThumbStick = SDL.SDL_GameControllerGetBindForAxis(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTX
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasRightYThumbStick = SDL.SDL_GameControllerGetBindForAxis(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_RIGHTY
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasLeftTrigger = SDL.SDL_GameControllerGetBindForAxis(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERLEFT
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasRightTrigger = SDL.SDL_GameControllerGetBindForAxis(
|
|
|
INTERNAL_devices[which],
|
|
|
SDL.SDL_GameControllerAxis.SDL_CONTROLLER_AXIS_TRIGGERRIGHT
|
|
|
).bindType != SDL.SDL_GameControllerBindType.SDL_CONTROLLER_BINDTYPE_NONE;
|
|
|
caps.HasLeftVibrationMotor = hasRumble;
|
|
|
caps.HasRightVibrationMotor = hasRumble;
|
|
|
caps.HasVoiceSupport = false;
|
|
|
INTERNAL_capabilities[which] = caps;
|
|
|
|
|
|
/* Store the GUID string for this device
|
|
|
* FIXME: Replace GetGUIDEXT string with 3 short values -flibit
|
|
|
*/
|
|
|
ushort vendor = SDL.SDL_JoystickGetVendor(thisJoystick);
|
|
|
ushort product = SDL.SDL_JoystickGetProduct(thisJoystick);
|
|
|
if (vendor == 0x00 && product == 0x00)
|
|
|
{
|
|
|
INTERNAL_guids[which] = "xinput";
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
INTERNAL_guids[which] = string.Format(
|
|
|
"{0:x2}{1:x2}{2:x2}{3:x2}",
|
|
|
vendor & 0xFF,
|
|
|
vendor >> 8,
|
|
|
product & 0xFF,
|
|
|
product >> 8
|
|
|
);
|
|
|
}
|
|
|
|
|
|
// Initialize light bar
|
|
|
if ( OSVersion.Equals("Linux") &&
|
|
|
( INTERNAL_guids[which].Equals("4c05c405") ||
|
|
|
INTERNAL_guids[which].Equals("4c05cc09") ) )
|
|
|
{
|
|
|
// Get all of the individual PS4 LED instances
|
|
|
List<string> ledList = new List<string>();
|
|
|
string[] dirs = Directory.GetDirectories("/sys/class/leds/");
|
|
|
foreach (string dir in dirs)
|
|
|
{
|
|
|
if ( dir.EndsWith("blue") &&
|
|
|
( dir.Contains("054C:05C4") ||
|
|
|
dir.Contains("054C:09CC") ) )
|
|
|
{
|
|
|
ledList.Add(dir.Substring(0, dir.LastIndexOf(':') + 1));
|
|
|
}
|
|
|
}
|
|
|
// Find how many of these are already in use
|
|
|
int numLights = 0;
|
|
|
for (int i = 0; i < INTERNAL_lightBars.Length; i += 1)
|
|
|
{
|
|
|
if (!String.IsNullOrEmpty(INTERNAL_lightBars[i]))
|
|
|
{
|
|
|
numLights += 1;
|
|
|
}
|
|
|
}
|
|
|
// If all are not already in use, use the first unused light
|
|
|
if (numLights < ledList.Count)
|
|
|
{
|
|
|
INTERNAL_lightBars[which] = ledList[numLights];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// Print controller information to stdout.
|
|
|
FNALoggerEXT.LogInfo(
|
|
|
"Controller " + which.ToString() + ": " +
|
|
|
SDL.SDL_GameControllerName(INTERNAL_devices[which])
|
|
|
);
|
|
|
}
|
|
|
|
|
|
private static void INTERNAL_RemoveInstance(int dev)
|
|
|
{
|
|
|
int output;
|
|
|
if (!INTERNAL_instanceList.TryGetValue(dev, out output))
|
|
|
{
|
|
|
// Odds are, this is controller 5+ getting removed.
|
|
|
return;
|
|
|
}
|
|
|
INTERNAL_instanceList.Remove(dev);
|
|
|
SDL.SDL_GameControllerClose(INTERNAL_devices[output]);
|
|
|
INTERNAL_devices[output] = IntPtr.Zero;
|
|
|
INTERNAL_states[output] = new GamePadState();
|
|
|
INTERNAL_guids[output] = String.Empty;
|
|
|
|
|
|
// A lot of errors can happen here, but honestly, they can be ignored...
|
|
|
SDL.SDL_ClearError();
|
|
|
|
|
|
FNALoggerEXT.LogInfo("Removed device, player: " + output.ToString());
|
|
|
}
|
|
|
|
|
|
// GetState can convert stick values to button values
|
|
|
private static Buttons READ_StickToButtons(Vector2 stick, Buttons left, Buttons right, Buttons up , Buttons down, float DeadZoneSize)
|
|
|
{
|
|
|
Buttons b = (Buttons) 0;
|
|
|
|
|
|
if (stick.X > DeadZoneSize)
|
|
|
{
|
|
|
b |= right;
|
|
|
}
|
|
|
if (stick.X < -DeadZoneSize)
|
|
|
{
|
|
|
b |= left;
|
|
|
}
|
|
|
if (stick.Y > DeadZoneSize)
|
|
|
{
|
|
|
b |= up;
|
|
|
}
|
|
|
if (stick.Y < -DeadZoneSize)
|
|
|
{
|
|
|
b |= down;
|
|
|
}
|
|
|
|
|
|
return b;
|
|
|
}
|
|
|
|
|
|
private static string[] GenStringArray()
|
|
|
{
|
|
|
string[] result = new string[GamePad.GAMEPAD_COUNT];
|
|
|
for (int i = 0; i < result.Length; i += 1)
|
|
|
{
|
|
|
result[i] = String.Empty;
|
|
|
}
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Touch Methods
|
|
|
|
|
|
public static TouchPanelCapabilities GetTouchCapabilities()
|
|
|
{
|
|
|
/* Take these reported capabilities with a grain of salt.
|
|
|
* On Windows, touch devices won't be detected until they
|
|
|
* are interacted with. Also, MaximumTouchCount is completely
|
|
|
* bogus. For any touch device, XNA always reports 4.
|
|
|
*
|
|
|
* -caleb
|
|
|
*/
|
|
|
bool touchDeviceExists = SDL.SDL_GetNumTouchDevices() > 0;
|
|
|
return new TouchPanelCapabilities(
|
|
|
touchDeviceExists,
|
|
|
touchDeviceExists ? 4 : 0
|
|
|
);
|
|
|
}
|
|
|
|
|
|
public static unsafe void UpdateTouchPanelState()
|
|
|
{
|
|
|
// Poll the touch device for all active fingers
|
|
|
long touchDevice = SDL.SDL_GetTouchDevice(0);
|
|
|
for (int i = 0; i < TouchPanel.MAX_TOUCHES; i += 1)
|
|
|
{
|
|
|
SDL.SDL_Finger* finger = (SDL.SDL_Finger*) SDL.SDL_GetTouchFinger(touchDevice, i);
|
|
|
if (finger == null)
|
|
|
{
|
|
|
// No finger found at this index
|
|
|
TouchPanel.SetFinger(i, TouchPanel.NO_FINGER, Vector2.Zero);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// Send the finger data to the TouchPanel
|
|
|
TouchPanel.SetFinger(
|
|
|
i,
|
|
|
(int) finger->id,
|
|
|
new Vector2(
|
|
|
(float) Math.Round(finger->x * TouchPanel.DisplayWidth),
|
|
|
(float) Math.Round(finger->y * TouchPanel.DisplayHeight)
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
public static int GetNumTouchFingers()
|
|
|
{
|
|
|
return SDL.SDL_GetNumTouchFingers(
|
|
|
SDL.SDL_GetTouchDevice(0)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region SDL2<->XNA Key Conversion Methods
|
|
|
|
|
|
/* From: http://blogs.msdn.com/b/shawnhar/archive/2007/07/02/twin-paths-to-garbage-collector-nirvana.aspx
|
|
|
* "If you use an enum type as a dictionary key, internal dictionary operations will cause boxing.
|
|
|
* You can avoid this by using integer keys, and casting your enum values to ints before adding
|
|
|
* them to the dictionary."
|
|
|
*/
|
|
|
private static Dictionary<int, Keys> INTERNAL_keyMap = new Dictionary<int, Keys>()
|
|
|
{
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_a, Keys.A },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_b, Keys.B },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_c, Keys.C },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_d, Keys.D },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_e, Keys.E },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_f, Keys.F },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_g, Keys.G },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_h, Keys.H },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_i, Keys.I },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_j, Keys.J },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_k, Keys.K },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_l, Keys.L },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_m, Keys.M },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_n, Keys.N },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_o, Keys.O },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_p, Keys.P },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_q, Keys.Q },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_r, Keys.R },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_s, Keys.S },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_t, Keys.T },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_u, Keys.U },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_v, Keys.V },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_w, Keys.W },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_x, Keys.X },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_y, Keys.Y },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_z, Keys.Z },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_0, Keys.D0 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_1, Keys.D1 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_2, Keys.D2 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_3, Keys.D3 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_4, Keys.D4 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_5, Keys.D5 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_6, Keys.D6 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_7, Keys.D7 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_8, Keys.D8 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_9, Keys.D9 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_0, Keys.NumPad0 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_1, Keys.NumPad1 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_2, Keys.NumPad2 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_3, Keys.NumPad3 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_4, Keys.NumPad4 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_5, Keys.NumPad5 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_6, Keys.NumPad6 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_7, Keys.NumPad7 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_8, Keys.NumPad8 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_9, Keys.NumPad9 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_CLEAR, Keys.OemClear },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_DECIMAL, Keys.Decimal },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_DIVIDE, Keys.Divide },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_ENTER, Keys.Enter },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_MINUS, Keys.Subtract },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_MULTIPLY, Keys.Multiply },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_PERIOD, Keys.OemPeriod },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_KP_PLUS, Keys.Add },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F1, Keys.F1 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F2, Keys.F2 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F3, Keys.F3 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F4, Keys.F4 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F5, Keys.F5 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F6, Keys.F6 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F7, Keys.F7 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F8, Keys.F8 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F9, Keys.F9 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F10, Keys.F10 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F11, Keys.F11 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F12, Keys.F12 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F13, Keys.F13 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F14, Keys.F14 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F15, Keys.F15 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F16, Keys.F16 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F17, Keys.F17 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F18, Keys.F18 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F19, Keys.F19 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F20, Keys.F20 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F21, Keys.F21 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F22, Keys.F22 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F23, Keys.F23 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_F24, Keys.F24 },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_SPACE, Keys.Space },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_UP, Keys.Up },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_DOWN, Keys.Down },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_LEFT, Keys.Left },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_RIGHT, Keys.Right },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_LALT, Keys.LeftAlt },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_RALT, Keys.RightAlt },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_LCTRL, Keys.LeftControl },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_RCTRL, Keys.RightControl },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_LGUI, Keys.LeftWindows },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_RGUI, Keys.RightWindows },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_LSHIFT, Keys.LeftShift },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_RSHIFT, Keys.RightShift },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_APPLICATION, Keys.Apps },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_SLASH, Keys.OemQuestion },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_BACKSLASH, Keys.OemBackslash },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_LEFTBRACKET, Keys.OemOpenBrackets },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_RIGHTBRACKET, Keys.OemCloseBrackets },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_CAPSLOCK, Keys.CapsLock },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_COMMA, Keys.OemComma },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_DELETE, Keys.Delete },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_END, Keys.End },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_BACKSPACE, Keys.Back },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_RETURN, Keys.Enter },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_ESCAPE, Keys.Escape },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_HOME, Keys.Home },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_INSERT, Keys.Insert },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_MINUS, Keys.OemMinus },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_NUMLOCKCLEAR, Keys.NumLock },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_PAGEUP, Keys.PageUp },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_PAGEDOWN, Keys.PageDown },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_PAUSE, Keys.Pause },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_PERIOD, Keys.OemPeriod },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_EQUALS, Keys.OemPlus },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_PRINTSCREEN, Keys.PrintScreen },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_QUOTE, Keys.OemQuotes },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_SCROLLLOCK, Keys.Scroll },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_SEMICOLON, Keys.OemSemicolon },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_SLEEP, Keys.Sleep },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_TAB, Keys.Tab },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_BACKQUOTE, Keys.OemTilde },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_VOLUMEUP, Keys.VolumeUp },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_VOLUMEDOWN, Keys.VolumeDown },
|
|
|
{ '²' /* FIXME: AZERTY SDL2? -flibit */, Keys.OemTilde },
|
|
|
{ 'é' /* FIXME: BEPO SDL2? -flibit */, Keys.None },
|
|
|
{ '|' /* FIXME: Norwegian SDL2? -flibit */, Keys.OemPipe },
|
|
|
{ '+' /* FIXME: Norwegian SDL2? -flibit */, Keys.OemPlus },
|
|
|
{ 'ø' /* FIXME: Norwegian SDL2? -flibit */, Keys.OemSemicolon },
|
|
|
{ 'æ' /* FIXME: Norwegian SDL2? -flibit */, Keys.OemQuotes },
|
|
|
{ (int) SDL.SDL_Keycode.SDLK_UNKNOWN, Keys.None }
|
|
|
};
|
|
|
private static Dictionary<int, Keys> INTERNAL_scanMap = new Dictionary<int, Keys>()
|
|
|
{
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_A, Keys.A },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_B, Keys.B },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_C, Keys.C },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_D, Keys.D },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_E, Keys.E },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F, Keys.F },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_G, Keys.G },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_H, Keys.H },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_I, Keys.I },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_J, Keys.J },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_K, Keys.K },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_L, Keys.L },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_M, Keys.M },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_N, Keys.N },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_O, Keys.O },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_P, Keys.P },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_Q, Keys.Q },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_R, Keys.R },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_S, Keys.S },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_T, Keys.T },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_U, Keys.U },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_V, Keys.V },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_W, Keys.W },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_X, Keys.X },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_Y, Keys.Y },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_Z, Keys.Z },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_0, Keys.D0 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_1, Keys.D1 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_2, Keys.D2 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_3, Keys.D3 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_4, Keys.D4 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_5, Keys.D5 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_6, Keys.D6 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_7, Keys.D7 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_8, Keys.D8 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_9, Keys.D9 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_0, Keys.NumPad0 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_1, Keys.NumPad1 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_2, Keys.NumPad2 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_3, Keys.NumPad3 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_4, Keys.NumPad4 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_5, Keys.NumPad5 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_6, Keys.NumPad6 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_7, Keys.NumPad7 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_8, Keys.NumPad8 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_9, Keys.NumPad9 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_CLEAR, Keys.OemClear },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_DECIMAL, Keys.Decimal },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_DIVIDE, Keys.Divide },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_ENTER, Keys.Enter },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_MINUS, Keys.Subtract },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY, Keys.Multiply },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_PERIOD, Keys.OemPeriod },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_KP_PLUS, Keys.Add },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F1, Keys.F1 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F2, Keys.F2 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F3, Keys.F3 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F4, Keys.F4 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F5, Keys.F5 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F6, Keys.F6 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F7, Keys.F7 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F8, Keys.F8 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F9, Keys.F9 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F10, Keys.F10 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F11, Keys.F11 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F12, Keys.F12 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F13, Keys.F13 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F14, Keys.F14 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F15, Keys.F15 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F16, Keys.F16 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F17, Keys.F17 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F18, Keys.F18 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F19, Keys.F19 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F20, Keys.F20 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F21, Keys.F21 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F22, Keys.F22 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F23, Keys.F23 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_F24, Keys.F24 },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SPACE, Keys.Space },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_UP, Keys.Up },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_DOWN, Keys.Down },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LEFT, Keys.Left },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RIGHT, Keys.Right },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LALT, Keys.LeftAlt },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RALT, Keys.RightAlt },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LCTRL, Keys.LeftControl },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RCTRL, Keys.RightControl },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LGUI, Keys.LeftWindows },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RGUI, Keys.RightWindows },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LSHIFT, Keys.LeftShift },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RSHIFT, Keys.RightShift },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_APPLICATION, Keys.Apps },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SLASH, Keys.OemQuestion },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_BACKSLASH, Keys.OemBackslash },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_LEFTBRACKET, Keys.OemOpenBrackets },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET, Keys.OemCloseBrackets },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_CAPSLOCK, Keys.CapsLock },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_COMMA, Keys.OemComma },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_DELETE, Keys.Delete },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_END, Keys.End },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_BACKSPACE, Keys.Back },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_RETURN, Keys.Enter },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_ESCAPE, Keys.Escape },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_HOME, Keys.Home },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_INSERT, Keys.Insert },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_MINUS, Keys.OemMinus },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR, Keys.NumLock },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PAGEUP, Keys.PageUp },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PAGEDOWN, Keys.PageDown },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PAUSE, Keys.Pause },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PERIOD, Keys.OemPeriod },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_EQUALS, Keys.OemPlus },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_PRINTSCREEN, Keys.PrintScreen },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_APOSTROPHE, Keys.OemQuotes },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SCROLLLOCK, Keys.Scroll },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SEMICOLON, Keys.OemSemicolon },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_SLEEP, Keys.Sleep },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_TAB, Keys.Tab },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_GRAVE, Keys.OemTilde },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEUP, Keys.VolumeUp },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEDOWN, Keys.VolumeDown },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_UNKNOWN, Keys.None },
|
|
|
/* FIXME: The following scancodes need verification! */
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_NONUSHASH, Keys.None },
|
|
|
{ (int) SDL.SDL_Scancode.SDL_SCANCODE_NONUSBACKSLASH, Keys.None }
|
|
|
};
|
|
|
private static Dictionary<int, SDL.SDL_Scancode> INTERNAL_xnaMap = new Dictionary<int, SDL.SDL_Scancode>()
|
|
|
{
|
|
|
{ (int) Keys.A, SDL.SDL_Scancode.SDL_SCANCODE_A },
|
|
|
{ (int) Keys.B, SDL.SDL_Scancode.SDL_SCANCODE_B },
|
|
|
{ (int) Keys.C, SDL.SDL_Scancode.SDL_SCANCODE_C },
|
|
|
{ (int) Keys.D, SDL.SDL_Scancode.SDL_SCANCODE_D },
|
|
|
{ (int) Keys.E, SDL.SDL_Scancode.SDL_SCANCODE_E },
|
|
|
{ (int) Keys.F, SDL.SDL_Scancode.SDL_SCANCODE_F },
|
|
|
{ (int) Keys.G, SDL.SDL_Scancode.SDL_SCANCODE_G },
|
|
|
{ (int) Keys.H, SDL.SDL_Scancode.SDL_SCANCODE_H },
|
|
|
{ (int) Keys.I, SDL.SDL_Scancode.SDL_SCANCODE_I },
|
|
|
{ (int) Keys.J, SDL.SDL_Scancode.SDL_SCANCODE_J },
|
|
|
{ (int) Keys.K, SDL.SDL_Scancode.SDL_SCANCODE_K },
|
|
|
{ (int) Keys.L, SDL.SDL_Scancode.SDL_SCANCODE_L },
|
|
|
{ (int) Keys.M, SDL.SDL_Scancode.SDL_SCANCODE_M },
|
|
|
{ (int) Keys.N, SDL.SDL_Scancode.SDL_SCANCODE_N },
|
|
|
{ (int) Keys.O, SDL.SDL_Scancode.SDL_SCANCODE_O },
|
|
|
{ (int) Keys.P, SDL.SDL_Scancode.SDL_SCANCODE_P },
|
|
|
{ (int) Keys.Q, SDL.SDL_Scancode.SDL_SCANCODE_Q },
|
|
|
{ (int) Keys.R, SDL.SDL_Scancode.SDL_SCANCODE_R },
|
|
|
{ (int) Keys.S, SDL.SDL_Scancode.SDL_SCANCODE_S },
|
|
|
{ (int) Keys.T, SDL.SDL_Scancode.SDL_SCANCODE_T },
|
|
|
{ (int) Keys.U, SDL.SDL_Scancode.SDL_SCANCODE_U },
|
|
|
{ (int) Keys.V, SDL.SDL_Scancode.SDL_SCANCODE_V },
|
|
|
{ (int) Keys.W, SDL.SDL_Scancode.SDL_SCANCODE_W },
|
|
|
{ (int) Keys.X, SDL.SDL_Scancode.SDL_SCANCODE_X },
|
|
|
{ (int) Keys.Y, SDL.SDL_Scancode.SDL_SCANCODE_Y },
|
|
|
{ (int) Keys.Z, SDL.SDL_Scancode.SDL_SCANCODE_Z },
|
|
|
{ (int) Keys.D0, SDL.SDL_Scancode.SDL_SCANCODE_0 },
|
|
|
{ (int) Keys.D1, SDL.SDL_Scancode.SDL_SCANCODE_1 },
|
|
|
{ (int) Keys.D2, SDL.SDL_Scancode.SDL_SCANCODE_2 },
|
|
|
{ (int) Keys.D3, SDL.SDL_Scancode.SDL_SCANCODE_3 },
|
|
|
{ (int) Keys.D4, SDL.SDL_Scancode.SDL_SCANCODE_4 },
|
|
|
{ (int) Keys.D5, SDL.SDL_Scancode.SDL_SCANCODE_5 },
|
|
|
{ (int) Keys.D6, SDL.SDL_Scancode.SDL_SCANCODE_6 },
|
|
|
{ (int) Keys.D7, SDL.SDL_Scancode.SDL_SCANCODE_7 },
|
|
|
{ (int) Keys.D8, SDL.SDL_Scancode.SDL_SCANCODE_8 },
|
|
|
{ (int) Keys.D9, SDL.SDL_Scancode.SDL_SCANCODE_9 },
|
|
|
{ (int) Keys.NumPad0, SDL.SDL_Scancode.SDL_SCANCODE_KP_0 },
|
|
|
{ (int) Keys.NumPad1, SDL.SDL_Scancode.SDL_SCANCODE_KP_1 },
|
|
|
{ (int) Keys.NumPad2, SDL.SDL_Scancode.SDL_SCANCODE_KP_2 },
|
|
|
{ (int) Keys.NumPad3, SDL.SDL_Scancode.SDL_SCANCODE_KP_3 },
|
|
|
{ (int) Keys.NumPad4, SDL.SDL_Scancode.SDL_SCANCODE_KP_4 },
|
|
|
{ (int) Keys.NumPad5, SDL.SDL_Scancode.SDL_SCANCODE_KP_5 },
|
|
|
{ (int) Keys.NumPad6, SDL.SDL_Scancode.SDL_SCANCODE_KP_6 },
|
|
|
{ (int) Keys.NumPad7, SDL.SDL_Scancode.SDL_SCANCODE_KP_7 },
|
|
|
{ (int) Keys.NumPad8, SDL.SDL_Scancode.SDL_SCANCODE_KP_8 },
|
|
|
{ (int) Keys.NumPad9, SDL.SDL_Scancode.SDL_SCANCODE_KP_9 },
|
|
|
{ (int) Keys.OemClear, SDL.SDL_Scancode.SDL_SCANCODE_KP_CLEAR },
|
|
|
{ (int) Keys.Decimal, SDL.SDL_Scancode.SDL_SCANCODE_KP_DECIMAL },
|
|
|
{ (int) Keys.Divide, SDL.SDL_Scancode.SDL_SCANCODE_KP_DIVIDE },
|
|
|
{ (int) Keys.Multiply, SDL.SDL_Scancode.SDL_SCANCODE_KP_MULTIPLY },
|
|
|
{ (int) Keys.Subtract, SDL.SDL_Scancode.SDL_SCANCODE_KP_MINUS },
|
|
|
{ (int) Keys.Add, SDL.SDL_Scancode.SDL_SCANCODE_KP_PLUS },
|
|
|
{ (int) Keys.F1, SDL.SDL_Scancode.SDL_SCANCODE_F1 },
|
|
|
{ (int) Keys.F2, SDL.SDL_Scancode.SDL_SCANCODE_F2 },
|
|
|
{ (int) Keys.F3, SDL.SDL_Scancode.SDL_SCANCODE_F3 },
|
|
|
{ (int) Keys.F4, SDL.SDL_Scancode.SDL_SCANCODE_F4 },
|
|
|
{ (int) Keys.F5, SDL.SDL_Scancode.SDL_SCANCODE_F5 },
|
|
|
{ (int) Keys.F6, SDL.SDL_Scancode.SDL_SCANCODE_F6 },
|
|
|
{ (int) Keys.F7, SDL.SDL_Scancode.SDL_SCANCODE_F7 },
|
|
|
{ (int) Keys.F8, SDL.SDL_Scancode.SDL_SCANCODE_F8 },
|
|
|
{ (int) Keys.F9, SDL.SDL_Scancode.SDL_SCANCODE_F9 },
|
|
|
{ (int) Keys.F10, SDL.SDL_Scancode.SDL_SCANCODE_F10 },
|
|
|
{ (int) Keys.F11, SDL.SDL_Scancode.SDL_SCANCODE_F11 },
|
|
|
{ (int) Keys.F12, SDL.SDL_Scancode.SDL_SCANCODE_F12 },
|
|
|
{ (int) Keys.F13, SDL.SDL_Scancode.SDL_SCANCODE_F13 },
|
|
|
{ (int) Keys.F14, SDL.SDL_Scancode.SDL_SCANCODE_F14 },
|
|
|
{ (int) Keys.F15, SDL.SDL_Scancode.SDL_SCANCODE_F15 },
|
|
|
{ (int) Keys.F16, SDL.SDL_Scancode.SDL_SCANCODE_F16 },
|
|
|
{ (int) Keys.F17, SDL.SDL_Scancode.SDL_SCANCODE_F17 },
|
|
|
{ (int) Keys.F18, SDL.SDL_Scancode.SDL_SCANCODE_F18 },
|
|
|
{ (int) Keys.F19, SDL.SDL_Scancode.SDL_SCANCODE_F19 },
|
|
|
{ (int) Keys.F20, SDL.SDL_Scancode.SDL_SCANCODE_F20 },
|
|
|
{ (int) Keys.F21, SDL.SDL_Scancode.SDL_SCANCODE_F21 },
|
|
|
{ (int) Keys.F22, SDL.SDL_Scancode.SDL_SCANCODE_F22 },
|
|
|
{ (int) Keys.F23, SDL.SDL_Scancode.SDL_SCANCODE_F23 },
|
|
|
{ (int) Keys.F24, SDL.SDL_Scancode.SDL_SCANCODE_F24 },
|
|
|
{ (int) Keys.Space, SDL.SDL_Scancode.SDL_SCANCODE_SPACE },
|
|
|
{ (int) Keys.Up, SDL.SDL_Scancode.SDL_SCANCODE_UP },
|
|
|
{ (int) Keys.Down, SDL.SDL_Scancode.SDL_SCANCODE_DOWN },
|
|
|
{ (int) Keys.Left, SDL.SDL_Scancode.SDL_SCANCODE_LEFT },
|
|
|
{ (int) Keys.Right, SDL.SDL_Scancode.SDL_SCANCODE_RIGHT },
|
|
|
{ (int) Keys.LeftAlt, SDL.SDL_Scancode.SDL_SCANCODE_LALT },
|
|
|
{ (int) Keys.RightAlt, SDL.SDL_Scancode.SDL_SCANCODE_RALT },
|
|
|
{ (int) Keys.LeftControl, SDL.SDL_Scancode.SDL_SCANCODE_LCTRL },
|
|
|
{ (int) Keys.RightControl, SDL.SDL_Scancode.SDL_SCANCODE_RCTRL },
|
|
|
{ (int) Keys.LeftWindows, SDL.SDL_Scancode.SDL_SCANCODE_LGUI },
|
|
|
{ (int) Keys.RightWindows, SDL.SDL_Scancode.SDL_SCANCODE_RGUI },
|
|
|
{ (int) Keys.LeftShift, SDL.SDL_Scancode.SDL_SCANCODE_LSHIFT },
|
|
|
{ (int) Keys.RightShift, SDL.SDL_Scancode.SDL_SCANCODE_RSHIFT },
|
|
|
{ (int) Keys.Apps, SDL.SDL_Scancode.SDL_SCANCODE_APPLICATION },
|
|
|
{ (int) Keys.OemQuestion, SDL.SDL_Scancode.SDL_SCANCODE_SLASH },
|
|
|
{ (int) Keys.OemBackslash, SDL.SDL_Scancode.SDL_SCANCODE_BACKSLASH },
|
|
|
{ (int) Keys.OemOpenBrackets, SDL.SDL_Scancode.SDL_SCANCODE_LEFTBRACKET },
|
|
|
{ (int) Keys.OemCloseBrackets, SDL.SDL_Scancode.SDL_SCANCODE_RIGHTBRACKET },
|
|
|
{ (int) Keys.CapsLock, SDL.SDL_Scancode.SDL_SCANCODE_CAPSLOCK },
|
|
|
{ (int) Keys.OemComma, SDL.SDL_Scancode.SDL_SCANCODE_COMMA },
|
|
|
{ (int) Keys.Delete, SDL.SDL_Scancode.SDL_SCANCODE_DELETE },
|
|
|
{ (int) Keys.End, SDL.SDL_Scancode.SDL_SCANCODE_END },
|
|
|
{ (int) Keys.Back, SDL.SDL_Scancode.SDL_SCANCODE_BACKSPACE },
|
|
|
{ (int) Keys.Enter, SDL.SDL_Scancode.SDL_SCANCODE_RETURN },
|
|
|
{ (int) Keys.Escape, SDL.SDL_Scancode.SDL_SCANCODE_ESCAPE },
|
|
|
{ (int) Keys.Home, SDL.SDL_Scancode.SDL_SCANCODE_HOME },
|
|
|
{ (int) Keys.Insert, SDL.SDL_Scancode.SDL_SCANCODE_INSERT },
|
|
|
{ (int) Keys.OemMinus, SDL.SDL_Scancode.SDL_SCANCODE_MINUS },
|
|
|
{ (int) Keys.NumLock, SDL.SDL_Scancode.SDL_SCANCODE_NUMLOCKCLEAR },
|
|
|
{ (int) Keys.PageUp, SDL.SDL_Scancode.SDL_SCANCODE_PAGEUP },
|
|
|
{ (int) Keys.PageDown, SDL.SDL_Scancode.SDL_SCANCODE_PAGEDOWN },
|
|
|
{ (int) Keys.Pause, SDL.SDL_Scancode.SDL_SCANCODE_PAUSE },
|
|
|
{ (int) Keys.OemPeriod, SDL.SDL_Scancode.SDL_SCANCODE_PERIOD },
|
|
|
{ (int) Keys.OemPlus, SDL.SDL_Scancode.SDL_SCANCODE_EQUALS },
|
|
|
{ (int) Keys.PrintScreen, SDL.SDL_Scancode.SDL_SCANCODE_PRINTSCREEN },
|
|
|
{ (int) Keys.OemQuotes, SDL.SDL_Scancode.SDL_SCANCODE_APOSTROPHE },
|
|
|
{ (int) Keys.Scroll, SDL.SDL_Scancode.SDL_SCANCODE_SCROLLLOCK },
|
|
|
{ (int) Keys.OemSemicolon, SDL.SDL_Scancode.SDL_SCANCODE_SEMICOLON },
|
|
|
{ (int) Keys.Sleep, SDL.SDL_Scancode.SDL_SCANCODE_SLEEP },
|
|
|
{ (int) Keys.Tab, SDL.SDL_Scancode.SDL_SCANCODE_TAB },
|
|
|
{ (int) Keys.OemTilde, SDL.SDL_Scancode.SDL_SCANCODE_GRAVE },
|
|
|
{ (int) Keys.VolumeUp, SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEUP },
|
|
|
{ (int) Keys.VolumeDown, SDL.SDL_Scancode.SDL_SCANCODE_VOLUMEDOWN },
|
|
|
{ (int) Keys.None, SDL.SDL_Scancode.SDL_SCANCODE_UNKNOWN }
|
|
|
};
|
|
|
|
|
|
private static Keys ToXNAKey(ref SDL.SDL_Keysym key)
|
|
|
{
|
|
|
Keys retVal;
|
|
|
if (UseScancodes)
|
|
|
{
|
|
|
if (INTERNAL_scanMap.TryGetValue((int) key.scancode, out retVal))
|
|
|
{
|
|
|
return retVal;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
if (INTERNAL_keyMap.TryGetValue((int) key.sym, out retVal))
|
|
|
{
|
|
|
return retVal;
|
|
|
}
|
|
|
}
|
|
|
FNALoggerEXT.LogWarn(
|
|
|
"KEY/SCANCODE MISSING FROM SDL2->XNA DICTIONARY: " +
|
|
|
key.sym.ToString() + " " +
|
|
|
key.scancode.ToString()
|
|
|
);
|
|
|
return Keys.None;
|
|
|
}
|
|
|
|
|
|
public static Keys GetKeyFromScancode(Keys scancode)
|
|
|
{
|
|
|
if (UseScancodes)
|
|
|
{
|
|
|
return scancode;
|
|
|
}
|
|
|
SDL.SDL_Scancode retVal;
|
|
|
if (INTERNAL_xnaMap.TryGetValue((int) scancode, out retVal))
|
|
|
{
|
|
|
Keys result;
|
|
|
SDL.SDL_Keycode sym = SDL.SDL_GetKeyFromScancode(retVal);
|
|
|
if (INTERNAL_keyMap.TryGetValue((int) sym, out result))
|
|
|
{
|
|
|
return result;
|
|
|
}
|
|
|
FNALoggerEXT.LogWarn(
|
|
|
"KEYCODE MISSING FROM SDL2->XNA DICTIONARY: " +
|
|
|
sym.ToString()
|
|
|
);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
FNALoggerEXT.LogWarn(
|
|
|
"SCANCODE MISSING FROM XNA->SDL2 DICTIONARY: " +
|
|
|
scancode.ToString()
|
|
|
);
|
|
|
}
|
|
|
return Keys.None;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
#region Private Static Win32 WM_PAINT Interop
|
|
|
|
|
|
private static SDL.SDL_EventFilter win32OnPaint = Win32OnPaint;
|
|
|
private static unsafe int Win32OnPaint(IntPtr func, IntPtr evtPtr)
|
|
|
{
|
|
|
SDL.SDL_Event* evt = (SDL.SDL_Event*) evtPtr;
|
|
|
if ( evt->type == SDL.SDL_EventType.SDL_WINDOWEVENT &&
|
|
|
evt->window.windowEvent == SDL.SDL_WindowEventID.SDL_WINDOWEVENT_EXPOSED )
|
|
|
{
|
|
|
foreach (Game game in activeGames)
|
|
|
{
|
|
|
if ( game.Window != null &&
|
|
|
evt->window.windowID == SDL.SDL_GetWindowID(game.Window.Handle) )
|
|
|
{
|
|
|
game.RedrawWindow();
|
|
|
return 0;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return 1;
|
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
}
|
|
|
}
|
|
|
|