mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
* Start converting SDL2 backend to SDL3. Game starts, but a lot of stuff is broken. Oh well. * Fix text input SDL3 changed the API somewhat, for the better. Changes all over UI/Clyde/SDL3 layer. * Fix mouse buttons being broken * Remove records from SDL3 WSI The fact that this shaved 2-3% off Robust.Client.dll is mindboggling. Records are so bad. * Set Windows/X11 native window properties * Fix window resize events getting wrong size oops * Remove "using static" from SDL3 WSI Seriously seems to hurt IDE performance, oh well. * Apparently I never called CheckThreadApartment(). * Add STAThreadAttribute to sandbox Necessary for content start * Set window title on creation properly. * Load window icons * Fix GLFW NoTitleBar style handling Yeah this PR is supposed to be about SDL3, so what? * Implement more window creation settings in SDL3 Mostly the ones that need a lot of platform-specific stuff to work. * Make fullscreen work properly in SDL3. * File dialogs with SDL3 Removes need for swnfd. * Fix some TODOs * Fix WebView build
146 lines
4.9 KiB
C#
146 lines
4.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Robust.Client.UserInterface;
|
|
using SDL3;
|
|
|
|
namespace Robust.Client.Graphics.Clyde;
|
|
|
|
internal partial class Clyde
|
|
{
|
|
private sealed partial class Sdl3WindowingImpl : IFileDialogManager
|
|
{
|
|
public async Task<Stream?> OpenFile(FileDialogFilters? filters = null)
|
|
{
|
|
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_OPENFILE, filters);
|
|
if (fileName == null)
|
|
return null;
|
|
|
|
return File.OpenRead(fileName);
|
|
}
|
|
|
|
public async Task<(Stream fileStream, bool alreadyExisted)?> SaveFile(FileDialogFilters? filters = null, bool truncate = true)
|
|
{
|
|
var fileName = await ShowFileDialogOfType(SDL.SDL_FILEDIALOG_SAVEFILE, filters);
|
|
if (fileName == null)
|
|
return null;
|
|
|
|
try
|
|
{
|
|
return (File.Open(fileName, truncate ? FileMode.Truncate : FileMode.Open), true);
|
|
}
|
|
catch (FileNotFoundException)
|
|
{
|
|
return (File.Open(fileName, FileMode.Create), false);
|
|
}
|
|
}
|
|
|
|
private unsafe Task<string?> ShowFileDialogOfType(int type, FileDialogFilters? filters)
|
|
{
|
|
var props = SDL.SDL_CreateProperties();
|
|
|
|
SDL.SDL_DialogFileFilter* filtersAlloc = null;
|
|
if (filters != null)
|
|
{
|
|
filtersAlloc = (SDL.SDL_DialogFileFilter*)NativeMemory.Alloc(
|
|
(UIntPtr)filters.Groups.Count,
|
|
(UIntPtr)sizeof(SDL.SDL_DialogFileFilter));
|
|
|
|
SDL.SDL_SetNumberProperty(props, SDL.SDL_PROP_FILE_DIALOG_NFILTERS_NUMBER, filters.Groups.Count);
|
|
SDL.SDL_SetPointerProperty(props, SDL.SDL_PROP_FILE_DIALOG_FILTERS_POINTER, (nint)filtersAlloc);
|
|
|
|
// All these mallocs aren't gonna win any performance awards, but oh well.
|
|
for (var i = 0; i < filters.Groups.Count; i++)
|
|
{
|
|
var (name, pattern) = ConvertFilterGroup(filters.Groups[i]);
|
|
filtersAlloc[i].name = StringToNative(name);
|
|
filtersAlloc[i].pattern = StringToNative(pattern);
|
|
}
|
|
}
|
|
|
|
var task = ShowFileDialogWithProperties(type, props);
|
|
|
|
SDL.SDL_DestroyProperties(props);
|
|
|
|
if (filtersAlloc != null)
|
|
{
|
|
for (var i = 0; i < filters!.Groups.Count; i++)
|
|
{
|
|
var filter = filtersAlloc[i];
|
|
NativeMemory.Free(filter.name);
|
|
NativeMemory.Free(filter.pattern);
|
|
}
|
|
}
|
|
|
|
return task;
|
|
}
|
|
|
|
private static unsafe byte* StringToNative(string str)
|
|
{
|
|
var byteCount = Encoding.UTF8.GetByteCount(str);
|
|
|
|
var mem = (byte*) NativeMemory.Alloc((nuint)(byteCount + 1));
|
|
Encoding.UTF8.GetBytes(str, new Span<byte>(mem, byteCount));
|
|
mem[byteCount] = 0; // null-terminate
|
|
|
|
return mem;
|
|
}
|
|
|
|
private (string name, string pattern) ConvertFilterGroup(FileDialogFilters.Group group)
|
|
{
|
|
var name = string.Join(", ", group.Extensions.Select(e => $"*.{e}"));
|
|
var pattern = string.Join(";", group.Extensions);
|
|
return (name, pattern);
|
|
}
|
|
|
|
private unsafe Task<string?> ShowFileDialogWithProperties(int type, uint properties)
|
|
{
|
|
var tcs = new TaskCompletionSource<string?>();
|
|
|
|
var gcHandle = GCHandle.Alloc(new FileDialogState
|
|
{
|
|
Parent = this,
|
|
Tcs = tcs
|
|
});
|
|
|
|
SDL.SDL_ShowFileDialogWithProperties(
|
|
type,
|
|
&FileDialogCallback,
|
|
(void*)GCHandle.ToIntPtr(gcHandle),
|
|
properties);
|
|
|
|
return tcs.Task;
|
|
}
|
|
|
|
[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
|
|
private static unsafe void FileDialogCallback(void* userdata, byte** filelist, int filter)
|
|
{
|
|
var stateHandle = GCHandle.FromIntPtr((IntPtr)userdata);
|
|
var state = (FileDialogState)stateHandle.Target!;
|
|
stateHandle.Free();
|
|
|
|
if (filelist == null)
|
|
{
|
|
// Error
|
|
state.Parent._sawmill.Error("File dialog failed: {error}", SDL.SDL_GetError());
|
|
state.Tcs.SetResult(null);
|
|
return;
|
|
}
|
|
|
|
// Handles null (cancelled/none selected) transparently.
|
|
var str = Marshal.PtrToStringUTF8((nint) filelist[0]);
|
|
state.Tcs.SetResult(str);
|
|
}
|
|
|
|
private sealed class FileDialogState
|
|
{
|
|
public required Sdl3WindowingImpl Parent;
|
|
public required TaskCompletionSource<string?> Tcs;
|
|
}
|
|
}
|
|
}
|