mirror of https://github.com/ppy/SDL3-CS.git
Add sample/tests project
Just a playground to see how the string/type/array friendly overloads work. You'll have to provide the SDL3 library manually.
This commit is contained in:
parent
e85349e3fa
commit
bb9834d0a9
|
|
@ -0,0 +1,238 @@
|
|||
// 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 osu.Framework.Allocation;
|
||||
using SDL;
|
||||
using static SDL.SDL3;
|
||||
|
||||
namespace SDL3.Tests
|
||||
{
|
||||
public sealed unsafe class MyWindow : IDisposable
|
||||
{
|
||||
private bool flash;
|
||||
private ObjectHandle<MyWindow> objectHandle { get; }
|
||||
private SDL_Window* SDLWindowHandle;
|
||||
private SDL_Renderer* Renderer;
|
||||
private readonly bool initSuccess;
|
||||
|
||||
private const SDL_InitFlags initFlags = SDL_InitFlags.SDL_INIT_VIDEO | SDL_InitFlags.SDL_INIT_GAMEPAD;
|
||||
|
||||
public MyWindow()
|
||||
{
|
||||
if (SDL_InitSubSystem(initFlags) < 0)
|
||||
throw new InvalidOperationException($"failed to initialise SDL. Error: {SDL_GetError()}");
|
||||
|
||||
initSuccess = true;
|
||||
|
||||
objectHandle = new ObjectHandle<MyWindow>(this, GCHandleType.Normal);
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
SDL_SetGamepadEventsEnabled(SDL_bool.SDL_TRUE);
|
||||
SDL_SetEventFilter(&nativeFilter, objectHandle.Handle);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
SDL_SetWindowsMessageHook(&wndProc, objectHandle.Handle);
|
||||
}
|
||||
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static SDL_bool wndProc(IntPtr userdata, MSG* message)
|
||||
{
|
||||
var handle = new ObjectHandle<MyWindow>(userdata);
|
||||
if (handle.GetTarget(out var window))
|
||||
{
|
||||
Console.WriteLine($"from {window}, message: {message->message}");
|
||||
}
|
||||
|
||||
return SDL_TRUE; // sample use of definition from SDL3 class, not SDL_bool enum
|
||||
}
|
||||
|
||||
// ReSharper disable once UseCollectionExpression
|
||||
[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]
|
||||
private static int nativeFilter(IntPtr userdata, SDL_Event* e)
|
||||
{
|
||||
var handle = new ObjectHandle<MyWindow>(userdata);
|
||||
if (handle.GetTarget(out var window))
|
||||
return window.handleEventFromFilter(e);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public Action<SDL_Event>? EventFilter;
|
||||
|
||||
private int handleEventFromFilter(SDL_Event* e)
|
||||
{
|
||||
switch (e->type)
|
||||
{
|
||||
case SDL_EventType.SDL_EVENT_KEY_UP:
|
||||
case SDL_EventType.SDL_EVENT_KEY_DOWN:
|
||||
handleKeyFromFilter(e->key);
|
||||
break;
|
||||
|
||||
default:
|
||||
EventFilter?.Invoke(*e);
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void handleKeyFromFilter(SDL_KeyboardEvent e)
|
||||
{
|
||||
if (e.keysym.sym == SDL_Keycode.SDLK_f)
|
||||
{
|
||||
flash = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Create()
|
||||
{
|
||||
SDLWindowHandle = SDL_CreateWindow("hello"u8, 800, 600, SDL_WindowFlags.SDL_WINDOW_RESIZABLE | SDL_WindowFlags.SDL_WINDOW_HIGH_PIXEL_DENSITY);
|
||||
Renderer = SDL_CreateRenderer(SDLWindowHandle, (byte*)null, SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC);
|
||||
}
|
||||
|
||||
private void handleEvent(SDL_Event e)
|
||||
{
|
||||
switch (e.type)
|
||||
{
|
||||
case SDL_EventType.SDL_EVENT_QUIT:
|
||||
run = false;
|
||||
break;
|
||||
|
||||
case SDL_EventType.SDL_EVENT_KEY_DOWN:
|
||||
switch (e.key.keysym.sym)
|
||||
{
|
||||
case SDL_Keycode.SDLK_r:
|
||||
bool old = SDL_GetRelativeMouseMode() == SDL_bool.SDL_TRUE;
|
||||
SDL_SetRelativeMouseMode(old ? SDL_bool.SDL_FALSE : SDL_bool.SDL_TRUE);
|
||||
break;
|
||||
|
||||
case SDL_Keycode.SDLK_v:
|
||||
string? text = SDL_GetClipboardText();
|
||||
Console.WriteLine($"clipboard: {text}");
|
||||
break;
|
||||
|
||||
case SDL_Keycode.SDLK_F10:
|
||||
SDL_SetWindowFullscreen(SDLWindowHandle, SDL_bool.SDL_FALSE);
|
||||
break;
|
||||
|
||||
case SDL_Keycode.SDLK_F11:
|
||||
SDL_SetWindowFullscreen(SDLWindowHandle, SDL_bool.SDL_TRUE);
|
||||
break;
|
||||
|
||||
case SDL_Keycode.SDLK_j:
|
||||
{
|
||||
using var gamepads = SDL_GetGamepads();
|
||||
|
||||
if (gamepads == null || gamepads.Count == 0)
|
||||
break;
|
||||
|
||||
var gamepad = SDL_OpenGamepad(gamepads[0]);
|
||||
|
||||
int count;
|
||||
var bindings = SDL_GetGamepadBindings(gamepad, &count);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var binding = *bindings[i];
|
||||
Console.WriteLine(binding.input_type);
|
||||
Console.WriteLine(binding.output_type);
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
SDL_CloseGamepad(gamepad);
|
||||
break;
|
||||
}
|
||||
|
||||
case SDL_Keycode.SDLK_F1:
|
||||
SDL_StartTextInput();
|
||||
break;
|
||||
|
||||
case SDL_Keycode.SDLK_F2:
|
||||
SDL_StopTextInput();
|
||||
break;
|
||||
|
||||
case SDL_Keycode.SDLK_m:
|
||||
SDL_Keymod mod = e.key.keysym.Mod;
|
||||
Console.WriteLine(mod);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SDL_EventType.SDL_EVENT_TEXT_INPUT:
|
||||
Console.WriteLine(e.text.GetText());
|
||||
break;
|
||||
|
||||
case SDL_EventType.SDL_EVENT_GAMEPAD_ADDED:
|
||||
Console.WriteLine($"gamepad added: {e.gdevice.which}");
|
||||
break;
|
||||
|
||||
case SDL_EventType.SDL_EVENT_WINDOW_PEN_ENTER:
|
||||
SDL_PenCapabilityInfo info;
|
||||
var cap = (SDL_PEN_CAPABILITIES)SDL_GetPenCapabilities((SDL_PenID)e.window.data1, &info);
|
||||
|
||||
if (cap.HasFlag(SDL_PEN_CAPABILITIES.SDL_PEN_AXIS_XTILT_MASK))
|
||||
Console.WriteLine("has pen xtilt axis");
|
||||
|
||||
Console.WriteLine(info.max_tilt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool run = true;
|
||||
|
||||
private const int events_per_peep = 64;
|
||||
private readonly SDL_Event[] events = new SDL_Event[events_per_peep];
|
||||
|
||||
private void pollEvents()
|
||||
{
|
||||
SDL_PumpEvents();
|
||||
|
||||
int eventsRead;
|
||||
|
||||
do
|
||||
{
|
||||
fixed (SDL_Event* buf = events)
|
||||
eventsRead = SDL_PeepEvents(buf, events_per_peep, SDL_eventaction.SDL_GETEVENT, SDL_EventType.SDL_EVENT_FIRST, SDL_EventType.SDL_EVENT_LAST);
|
||||
|
||||
for (int i = 0; i < eventsRead; i++)
|
||||
handleEvent(events[i]);
|
||||
} while (eventsRead == events_per_peep);
|
||||
}
|
||||
|
||||
private float frame;
|
||||
|
||||
public void Run()
|
||||
{
|
||||
while (run)
|
||||
{
|
||||
if (flash)
|
||||
{
|
||||
flash = false;
|
||||
Console.WriteLine("flash!");
|
||||
}
|
||||
|
||||
pollEvents();
|
||||
|
||||
SDL_SetRenderDrawColorFloat(Renderer, SDL_sinf(frame) / 2 + 0.5f, SDL_cosf(frame) / 2 + 0.5f, 0.3f, 1.0f);
|
||||
SDL_RenderClear(Renderer);
|
||||
SDL_RenderPresent(Renderer);
|
||||
|
||||
frame += 0.015f;
|
||||
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (initSuccess)
|
||||
SDL_QuitSubSystem(initFlags);
|
||||
|
||||
objectHandle.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace osu.Framework.Allocation
|
||||
{
|
||||
/// <summary>
|
||||
/// Wrapper on <see cref="GCHandle" /> that supports the <see cref="IDisposable" /> pattern.
|
||||
/// </summary>
|
||||
public struct ObjectHandle<T> : IDisposable
|
||||
where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// The pointer from the <see cref="GCHandle" />, if it is allocated. Otherwise <see cref="IntPtr.Zero" />.
|
||||
/// </summary>
|
||||
public IntPtr Handle => handle.IsAllocated ? GCHandle.ToIntPtr(handle) : IntPtr.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The address of target object, if it is allocated and pinned. Otherwise <see cref="IntPtr.Zero" />.
|
||||
/// Note: This is not the same as the <see cref="Handle" />.
|
||||
/// </summary>
|
||||
public IntPtr Address => handle.IsAllocated ? handle.AddrOfPinnedObject() : IntPtr.Zero;
|
||||
|
||||
public bool IsAllocated => handle.IsAllocated;
|
||||
|
||||
private GCHandle handle;
|
||||
|
||||
private readonly bool fromPointer;
|
||||
|
||||
/// <summary>
|
||||
/// Wraps the provided object with a <see cref="GCHandle" />, using the given <see cref="GCHandleType" />.
|
||||
/// </summary>
|
||||
/// <param name="target">The target object to wrap.</param>
|
||||
/// <param name="handleType">The handle type to use.</param>
|
||||
public ObjectHandle(T target, GCHandleType handleType)
|
||||
{
|
||||
handle = GCHandle.Alloc(target, handleType);
|
||||
fromPointer = false;
|
||||
}
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
/// <param name="handle">Handle.</param>
|
||||
public ObjectHandle(IntPtr handle)
|
||||
{
|
||||
this.handle = GCHandle.FromIntPtr(handle);
|
||||
fromPointer = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the object being referenced.
|
||||
/// Returns true if successful and populates <paramref name="target"/> with the referenced object.
|
||||
/// Returns false If the handle is not allocated or the target is not of type <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
/// <param name="target">Populates this parameter with the targeted object.</param>
|
||||
public bool GetTarget(out T target)
|
||||
{
|
||||
if (!handle.IsAllocated)
|
||||
{
|
||||
target = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (handle.Target is T value)
|
||||
{
|
||||
target = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
}
|
||||
|
||||
target = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
#region IDisposable Support
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!fromPointer && handle.IsAllocated)
|
||||
handle.Free();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// 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.Diagnostics;
|
||||
using System.Text;
|
||||
using SDL;
|
||||
using static SDL.SDL3;
|
||||
|
||||
namespace SDL3.Tests
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
private static void Main()
|
||||
{
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "null byte \0 in string"u8);
|
||||
Debug.Assert(SDL_GetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4) == "null byte ");
|
||||
|
||||
SDL_SetHint(SDL_HINT_WINDOWS_CLOSE_ON_ALT_F4, "1"u8);
|
||||
|
||||
using (var window = new MyWindow())
|
||||
{
|
||||
Console.WriteLine($"SDL revision: {SDL_GetRevision()}");
|
||||
|
||||
printDisplays();
|
||||
|
||||
window.Setup();
|
||||
window.Create();
|
||||
|
||||
const SDL_Keymod state = SDL_Keymod.SDL_KMOD_CAPS | SDL_Keymod.SDL_KMOD_ALT;
|
||||
SDL_SetModState(state);
|
||||
Debug.Assert(SDL_GetModState() == state);
|
||||
|
||||
window.Run();
|
||||
}
|
||||
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
private static void printDisplays()
|
||||
{
|
||||
using var displays = SDL_GetDisplays();
|
||||
if (displays == null)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < displays.Count; i++)
|
||||
{
|
||||
SDL_DisplayID id = displays[i];
|
||||
Console.WriteLine(id);
|
||||
|
||||
using var modes = SDL_GetFullscreenDisplayModes(id);
|
||||
if (modes == null)
|
||||
continue;
|
||||
|
||||
for (int j = 0; j < modes.Count; j++)
|
||||
{
|
||||
SDL_DisplayMode mode = modes[j];
|
||||
Console.WriteLine($"{mode.w}x{mode.h}@{mode.refresh_rate}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RootNamespace>SDL3.Tests</RootNamespace>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SDL3-CS\SDL3-CS.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL3-CS", "SDL3-CS\SDL3-CS.
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL3-CS.SourceGeneration", "SDL3-CS.SourceGeneration\SDL3-CS.SourceGeneration.csproj", "{432C86D0-D371-4D01-BFFE-01D2CDCCA7B8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SDL3-CS.Tests", "SDL3-CS.Tests\SDL3-CS.Tests.csproj", "{CF980481-8227-4BED-970E-6433C83F64CB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -34,5 +36,9 @@ Global
|
|||
{432C86D0-D371-4D01-BFFE-01D2CDCCA7B8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{432C86D0-D371-4D01-BFFE-01D2CDCCA7B8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{432C86D0-D371-4D01-BFFE-01D2CDCCA7B8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CF980481-8227-4BED-970E-6433C83F64CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CF980481-8227-4BED-970E-6433C83F64CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CF980481-8227-4BED-970E-6433C83F64CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CF980481-8227-4BED-970E-6433C83F64CB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
Loading…
Reference in New Issue