mirror of https://github.com/ppy/SDL3-CS.git
Merge pull request #77 from Susko3/add-input-visualisation-test
Add positional input visualization test
This commit is contained in:
commit
e6dff89353
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace SDL.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for tests that use SDL3 main callbacks.
|
||||
/// See https://wiki.libsdl.org/SDL3/README/main-functions#how-to-use-main-callbacks-in-sdl3.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
[Apartment(ApartmentState.STA)]
|
||||
public abstract unsafe class MainCallbacksTest
|
||||
{
|
||||
[Test]
|
||||
public void TestEnterMainCallbacks()
|
||||
{
|
||||
var objectHandle = new ObjectHandle<MainCallbacksTest>(this, GCHandleType.Normal);
|
||||
SDL3.SDL_EnterAppMainCallbacks(0, (byte**)objectHandle.Handle, &AppInit, &AppIterate, &AppEvent, &AppQuit);
|
||||
}
|
||||
|
||||
protected virtual int Init()
|
||||
{
|
||||
SDL3.SDL_SetLogPriorities(SDL_LogPriority.SDL_LOG_PRIORITY_VERBOSE);
|
||||
SDL3.SDL_SetLogOutputFunction(&LogOutput, IntPtr.Zero);
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
protected const int TERMINATE_ERROR = -1;
|
||||
protected const int CONTINUE = 0;
|
||||
protected const int TERMINATE_SUCCESS = 1;
|
||||
|
||||
protected virtual int Iterate()
|
||||
{
|
||||
Thread.Sleep(10);
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
protected virtual int Event(SDL_Event e)
|
||||
{
|
||||
switch (e.Type)
|
||||
{
|
||||
case SDL_EventType.SDL_EVENT_QUIT:
|
||||
case SDL_EventType.SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||
case SDL_EventType.SDL_EVENT_TERMINATING:
|
||||
case SDL_EventType.SDL_EVENT_KEY_DOWN when e.key.keysym.sym == SDL_Keycode.SDLK_ESCAPE:
|
||||
return TERMINATE_SUCCESS;
|
||||
}
|
||||
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
protected virtual void Quit()
|
||||
{
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
private static void LogOutput(IntPtr userdata, SDL_LogCategory category, SDL_LogPriority priority, byte* message)
|
||||
{
|
||||
Console.WriteLine(SDL3.PtrToStringUTF8(message));
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
private static int AppInit(IntPtr* appState, int argc, byte** argv)
|
||||
{
|
||||
IntPtr handle = (IntPtr)argv;
|
||||
*appState = handle;
|
||||
|
||||
var objectHandle = new ObjectHandle<MainCallbacksTest>(handle, true);
|
||||
if (objectHandle.GetTarget(out var target))
|
||||
return target.Init();
|
||||
|
||||
return TERMINATE_ERROR;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
private static int AppIterate(IntPtr appState)
|
||||
{
|
||||
var objectHandle = new ObjectHandle<MainCallbacksTest>(appState, true);
|
||||
if (objectHandle.GetTarget(out var target))
|
||||
return target.Iterate();
|
||||
|
||||
return TERMINATE_ERROR;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
private static int AppEvent(IntPtr appState, SDL_Event* e)
|
||||
{
|
||||
var objectHandle = new ObjectHandle<MainCallbacksTest>(appState, true);
|
||||
if (objectHandle.GetTarget(out var target))
|
||||
return target.Event(*e);
|
||||
|
||||
return TERMINATE_ERROR;
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
||||
private static void AppQuit(IntPtr appState)
|
||||
{
|
||||
using var objectHandle = new ObjectHandle<MainCallbacksTest>(appState, true);
|
||||
if (objectHandle.GetTarget(out var target))
|
||||
target.Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ namespace SDL.Tests
|
|||
|
||||
private GCHandle handle;
|
||||
|
||||
private readonly bool fromPointer;
|
||||
private readonly bool canFree;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps the provided object with a <see cref="GCHandle" />, using the given <see cref="GCHandleType" />.
|
||||
|
|
@ -38,18 +38,19 @@ namespace SDL.Tests
|
|||
public ObjectHandle(T target, GCHandleType handleType)
|
||||
{
|
||||
handle = GCHandle.Alloc(target, handleType);
|
||||
fromPointer = false;
|
||||
canFree = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recreates an <see cref="ObjectHandle{T}" /> based on the passed <see cref="IntPtr" />.
|
||||
/// Disposing this object will not free the handle, the original object must be disposed instead.
|
||||
/// If <paramref name="ownsHandle"/> is <c>true</c>, disposing this object will free the handle.
|
||||
/// </summary>
|
||||
/// <param name="handle">Handle.</param>
|
||||
public ObjectHandle(IntPtr handle)
|
||||
/// <param name="handle"><see cref="Handle"/> from a previously constructed <see cref="ObjectHandle{T}(T, GCHandleType)"/>.</param>
|
||||
/// <param name="ownsHandle">Whether this instance owns the underlying <see cref="GCHandle"/>.</param>
|
||||
public ObjectHandle(IntPtr handle, bool ownsHandle = false)
|
||||
{
|
||||
this.handle = GCHandle.FromIntPtr(handle);
|
||||
fromPointer = true;
|
||||
canFree = ownsHandle;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -86,7 +87,7 @@ namespace SDL.Tests
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!fromPointer && handle.IsAllocated)
|
||||
if (canFree && handle.IsAllocated)
|
||||
handle.Free();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Drawing;
|
||||
using static SDL.SDL3;
|
||||
|
||||
namespace SDL.Tests
|
||||
{
|
||||
public unsafe class TestPositionalInputVisualisation : MainCallbacksTest
|
||||
{
|
||||
private SDL_Window* window;
|
||||
private SDL_Renderer* renderer;
|
||||
|
||||
protected override int Init()
|
||||
{
|
||||
// decouple pen, mouse and touch events
|
||||
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS, "0");
|
||||
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS, "0");
|
||||
SDL_SetHint(SDL_HINT_PEN_NOT_MOUSE, "2");
|
||||
|
||||
SDL_Init(SDL_InitFlags.SDL_INIT_VIDEO);
|
||||
|
||||
window = SDL_CreateWindow(nameof(TestPositionalInputVisualisation), 1800, 950, SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY);
|
||||
renderer = SDL_CreateRenderer(window, (Utf8String)null);
|
||||
|
||||
return base.Init();
|
||||
}
|
||||
|
||||
private readonly SortedDictionary<(SDL_TouchID TouchID, SDL_FingerID FingerID), PointF> activeTouches = new SortedDictionary<(SDL_TouchID TouchID, SDL_FingerID FingerID), PointF>();
|
||||
private readonly SortedDictionary<SDL_MouseID, PointF> activeMice = new SortedDictionary<SDL_MouseID, PointF>();
|
||||
private readonly SortedDictionary<SDL_PenID, PointF> activePens = new SortedDictionary<SDL_PenID, PointF>();
|
||||
|
||||
/// <summary>
|
||||
/// Sets a random, but stable color for this object.
|
||||
/// </summary>
|
||||
private void setColor(object o, byte alpha)
|
||||
{
|
||||
int color = o.ToString()?.GetHashCode() ?? 0;
|
||||
byte b1 = (byte)color;
|
||||
byte b2 = (byte)(color / 256);
|
||||
byte b3 = (byte)(color / 256 / 256);
|
||||
SDL_SetRenderDrawColor(renderer, b1, b2, b3, alpha);
|
||||
}
|
||||
|
||||
private void fillRect(RectangleF rect)
|
||||
{
|
||||
var r = new SDL_FRect { x = rect.X, y = rect.Y, h = rect.Height, w = rect.Width };
|
||||
SDL_RenderFillRect(renderer, &r);
|
||||
}
|
||||
|
||||
protected override int Iterate()
|
||||
{
|
||||
const float gray = 0.1f;
|
||||
SDL_SetRenderDrawColorFloat(renderer, gray, gray, gray, 1.0f);
|
||||
SDL_RenderClear(renderer);
|
||||
|
||||
// mice are horizontal lines: -
|
||||
foreach (var p in activeMice)
|
||||
{
|
||||
setColor(p.Key, 200);
|
||||
RectangleF rect = new RectangleF(p.Value, SizeF.Empty);
|
||||
rect.Inflate(50, 20);
|
||||
fillRect(rect);
|
||||
}
|
||||
|
||||
// fingers are vertical lines: |
|
||||
foreach (var p in activeTouches)
|
||||
{
|
||||
setColor(p.Key, 200);
|
||||
RectangleF rect = new RectangleF(p.Value, SizeF.Empty);
|
||||
rect.Inflate(20, 50);
|
||||
fillRect(rect);
|
||||
}
|
||||
|
||||
// pens are squares: □
|
||||
foreach (var p in activePens)
|
||||
{
|
||||
setColor(p.Key, 200);
|
||||
RectangleF rect = new RectangleF(p.Value, SizeF.Empty);
|
||||
rect.Inflate(30, 30);
|
||||
fillRect(rect);
|
||||
}
|
||||
|
||||
SDL_RenderPresent(renderer);
|
||||
|
||||
return base.Iterate();
|
||||
}
|
||||
|
||||
protected override int Event(SDL_Event e)
|
||||
{
|
||||
SDL_ConvertEventToRenderCoordinates(renderer, &e);
|
||||
|
||||
switch (e.Type)
|
||||
{
|
||||
case SDL_EventType.SDL_EVENT_MOUSE_MOTION:
|
||||
activeMice[e.motion.which] = new PointF(e.motion.x, e.motion.y);
|
||||
break;
|
||||
|
||||
case SDL_EventType.SDL_EVENT_MOUSE_REMOVED:
|
||||
activeMice.Remove(e.mdevice.which);
|
||||
break;
|
||||
|
||||
case SDL_EventType.SDL_EVENT_FINGER_DOWN:
|
||||
case SDL_EventType.SDL_EVENT_FINGER_MOTION:
|
||||
activeTouches[(e.tfinger.touchID, e.tfinger.fingerID)] = new PointF(e.tfinger.x, e.tfinger.y);
|
||||
break;
|
||||
|
||||
case SDL_EventType.SDL_EVENT_FINGER_UP:
|
||||
activeTouches.Remove((e.tfinger.touchID, e.tfinger.fingerID));
|
||||
break;
|
||||
|
||||
case SDL_EventType.SDL_EVENT_PEN_MOTION:
|
||||
activePens[e.pmotion.which] = new PointF(e.pmotion.x, e.pmotion.y);
|
||||
break;
|
||||
|
||||
case SDL_EventType.SDL_EVENT_KEY_DOWN:
|
||||
switch (e.key.keysym.sym)
|
||||
{
|
||||
case SDL_Keycode.SDLK_r:
|
||||
SDL_SetRelativeMouseMode(SDL_GetRelativeMouseMode() == SDL_bool.SDL_TRUE ? SDL_bool.SDL_FALSE : SDL_bool.SDL_TRUE);
|
||||
break;
|
||||
|
||||
case SDL_Keycode.SDLK_f:
|
||||
SDL_SetWindowFullscreen(window, SDL_bool.SDL_TRUE);
|
||||
break;
|
||||
|
||||
case SDL_Keycode.SDLK_w:
|
||||
SDL_SetWindowFullscreen(window, SDL_bool.SDL_FALSE);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return base.Event(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue