427 lines
19 KiB
C#
427 lines
19 KiB
C#
using ImGuiNET;
|
|
using SDL3;
|
|
|
|
namespace ResoniteImGuiLib.SDL3;
|
|
|
|
/// <summary>
|
|
/// Implementation of SDL3 platform backend for ImGui.
|
|
/// https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_sdl3.h
|
|
/// </summary>
|
|
public class ImGuiSDL3 : IDisposable
|
|
{
|
|
public readonly IntPtr Window;
|
|
public readonly IntPtr Renderer;
|
|
public readonly uint WindowId;
|
|
private uint _mouseWindowId;
|
|
private int _mousePendingLeaveFrame;
|
|
private IntPtr[] _mouseCursors = new IntPtr[(int)ImGuiMouseCursor.COUNT];
|
|
private IntPtr _mouseLastCursor = -1;
|
|
private int _mouseButtonsDown;
|
|
|
|
// I don't think we actually need these since it works just fine without them
|
|
//
|
|
// [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
// public delegate IntPtr Platform_GetClipboardTextFn(IntPtr ctx);
|
|
//
|
|
// [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
|
// public delegate void Platform_SetClipboardTextFn(IntPtr ctx, IntPtr text);
|
|
//
|
|
// private static Platform_GetClipboardTextFn getClipboardDelegate = GetClipboardText;
|
|
// private static Platform_SetClipboardTextFn setClipboardDelegate = SetClipboardText;
|
|
//
|
|
// private static IntPtr getClipboardPtr;
|
|
// private static IntPtr setClipboardPtr;
|
|
//
|
|
// private static IntPtr clipboardTextPtr = IntPtr.Zero;
|
|
|
|
public ImGuiSDL3(IntPtr window, IntPtr renderer)
|
|
{
|
|
ImGuiIOPtr io = ImGui.GetIO();
|
|
|
|
io.BackendFlags |= ImGuiBackendFlags.HasMouseCursors;
|
|
io.BackendFlags |= ImGuiBackendFlags.HasSetMousePos;
|
|
Window = window;
|
|
WindowId = SDL.GetWindowID(Window);
|
|
Renderer = renderer;
|
|
|
|
// I don't think we actually need these since it works just fine without them
|
|
//
|
|
// ImGuiPlatformIOPtr platformIo = ImGui.GetPlatformIO();
|
|
// platformIo.Platform_SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(setClipboardDelegate);
|
|
// platformIo.Platform_GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(getClipboardDelegate);
|
|
// getClipboardPtr = platformIo.Platform_GetClipboardTextFn;
|
|
// setClipboardPtr = platformIo.Platform_SetClipboardTextFn;
|
|
|
|
_mouseCursors[(int)ImGuiMouseCursor.Arrow] = SDL.CreateSystemCursor(SDL.SystemCursor.Default);
|
|
_mouseCursors[(int)ImGuiMouseCursor.TextInput] = SDL.CreateSystemCursor(SDL.SystemCursor.Text);
|
|
_mouseCursors[(int)ImGuiMouseCursor.ResizeAll] = SDL.CreateSystemCursor(SDL.SystemCursor.Move);
|
|
_mouseCursors[(int)ImGuiMouseCursor.ResizeNS] = SDL.CreateSystemCursor(SDL.SystemCursor.NSResize);
|
|
_mouseCursors[(int)ImGuiMouseCursor.ResizeEW] = SDL.CreateSystemCursor(SDL.SystemCursor.EWResize);
|
|
_mouseCursors[(int)ImGuiMouseCursor.ResizeNESW] = SDL.CreateSystemCursor(SDL.SystemCursor.NESWResize);
|
|
_mouseCursors[(int)ImGuiMouseCursor.ResizeNWSE] = SDL.CreateSystemCursor(SDL.SystemCursor.NWSEResize);
|
|
_mouseCursors[(int)ImGuiMouseCursor.Hand] = SDL.CreateSystemCursor(SDL.SystemCursor.Pointer);
|
|
_mouseCursors[(int)ImGuiMouseCursor.NotAllowed] = SDL.CreateSystemCursor(SDL.SystemCursor.NotAllowed);
|
|
|
|
ImGuiViewportPtr viewport = ImGui.GetMainViewport();
|
|
SetupPlatformHandles(viewport, window);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
foreach (IntPtr cursor in _mouseCursors)
|
|
SDL.DestroyCursor(cursor);
|
|
}
|
|
|
|
public void NewFrame()
|
|
{
|
|
ImGuiIOPtr io = ImGui.GetIO();
|
|
|
|
SDL.GetWindowSize(Window, out int w, out int h);
|
|
if (SDL.GetWindowFlags(Window).HasFlag(SDL.WindowFlags.Minimized))
|
|
{
|
|
w = h = 0;
|
|
}
|
|
|
|
SDL.GetWindowSizeInPixels(Window, out int displayW, out int displayH);
|
|
io.DisplaySize = new System.Numerics.Vector2(w, h);
|
|
|
|
if (w > 0 && h > 0)
|
|
io.DisplayFramebufferScale = new System.Numerics.Vector2((float)displayW / w, (float)displayH / h);
|
|
|
|
if (_mousePendingLeaveFrame > 0 && _mousePendingLeaveFrame >= ImGui.GetFrameCount())
|
|
{
|
|
_mouseWindowId = 0;
|
|
_mousePendingLeaveFrame = 0;
|
|
io.AddMousePosEvent(-float.MaxValue, -float.MaxValue);
|
|
}
|
|
|
|
UpdateMouseData();
|
|
UpdateMouseCursor();
|
|
}
|
|
|
|
public unsafe bool ProcessEvent(SDL.Event e)
|
|
{
|
|
ImGuiIOPtr io = ImGui.GetIO();
|
|
|
|
switch ((SDL.EventType)e.Type)
|
|
{
|
|
case SDL.EventType.MouseMotion:
|
|
if (GetViewportForWindowId(e.Motion.WindowID) == null)
|
|
return false;
|
|
|
|
io.AddMouseSourceEvent(e.Motion.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse);
|
|
io.AddMousePosEvent(e.Motion.X, e.Motion.Y);
|
|
return true;
|
|
case SDL.EventType.MouseWheel:
|
|
if (GetViewportForWindowId(e.Wheel.WindowID) == null)
|
|
return false;
|
|
|
|
float wheelX = -e.Wheel.X;
|
|
float wheelY = e.Wheel.Y;
|
|
|
|
io.AddMouseSourceEvent(e.Wheel.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse);
|
|
io.AddMouseWheelEvent(wheelX, wheelY);
|
|
return true;
|
|
case SDL.EventType.MouseButtonDown:
|
|
case SDL.EventType.MouseButtonUp:
|
|
if (GetViewportForWindowId(e.Button.WindowID) == null)
|
|
return false;
|
|
|
|
int mouseButton = e.Button.Button switch
|
|
{
|
|
SDL.ButtonLeft => 0,
|
|
SDL.ButtonRight => 1,
|
|
SDL.ButtonMiddle => 2,
|
|
SDL.ButtonX1 => 3,
|
|
SDL.ButtonX2 => 4,
|
|
_ => -1
|
|
};
|
|
if (mouseButton == -1) break;
|
|
|
|
io.AddMouseSourceEvent(e.Button.Which == SDL.TouchMouseID ? ImGuiMouseSource.TouchScreen : ImGuiMouseSource.Mouse);
|
|
io.AddMouseButtonEvent(mouseButton, (SDL.EventType)e.Type == SDL.EventType.MouseButtonDown);
|
|
_mouseButtonsDown = (SDL.EventType)e.Type == SDL.EventType.MouseButtonDown ? _mouseButtonsDown | 1 << mouseButton : _mouseButtonsDown & ~(1 << mouseButton);
|
|
return true;
|
|
case SDL.EventType.TextInput:
|
|
if (GetViewportForWindowId(e.Text.WindowID) == null)
|
|
return false;
|
|
|
|
ImGuiNative.ImGuiIO_AddInputCharactersUTF8(io, (byte*)e.Text.Text);
|
|
|
|
return true;
|
|
case SDL.EventType.KeyDown:
|
|
case SDL.EventType.KeyUp:
|
|
if (GetViewportForWindowId(e.Key.WindowID) == null)
|
|
return false;
|
|
|
|
UpdateKeyModifiers(e.Key.Mod);
|
|
ImGuiKey imguiKey = KeyEventToImGui(e.Key.Key, e.Key.Scancode);
|
|
bool pressed = (SDL.EventType)e.Type == SDL.EventType.KeyDown;
|
|
|
|
io.AddKeyEvent(imguiKey, pressed);
|
|
io.SetKeyEventNativeData(imguiKey, (int)e.Key.Key, (int)e.Key.Scancode, (int)e.Key.Scancode);
|
|
|
|
return true;
|
|
case SDL.EventType.WindowMouseEnter:
|
|
if (GetViewportForWindowId(e.Window.WindowID) == null)
|
|
return false;
|
|
|
|
_mouseWindowId = e.Window.WindowID;
|
|
_mousePendingLeaveFrame = 0;
|
|
return true;
|
|
case SDL.EventType.WindowMouseLeave:
|
|
if (GetViewportForWindowId(e.Window.WindowID) == null)
|
|
return false;
|
|
|
|
_mousePendingLeaveFrame = ImGui.GetFrameCount() + 1;
|
|
return true;
|
|
case SDL.EventType.WindowFocusGained:
|
|
case SDL.EventType.WindowFocusLost:
|
|
if (GetViewportForWindowId(e.Window.WindowID) == null)
|
|
return false;
|
|
|
|
io.AddFocusEvent((SDL.EventType)e.Type == SDL.EventType.WindowFocusGained);
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void UpdateMouseData()
|
|
{
|
|
ImGuiIOPtr io = ImGui.GetIO();
|
|
|
|
IntPtr focusedWindow = SDL.GetKeyboardFocus();
|
|
bool isAppFocused = focusedWindow == Window;
|
|
if (isAppFocused)
|
|
{
|
|
if (io.WantSetMousePos)
|
|
{
|
|
SDL.WarpMouseInWindow(Window, (int)io.MousePos.X, (int)io.MousePos.Y);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateMouseCursor()
|
|
{
|
|
ImGuiIOPtr io = ImGui.GetIO();
|
|
if ((io.ConfigFlags & ImGuiConfigFlags.NoMouseCursorChange) != 0)
|
|
return;
|
|
|
|
ImGuiMouseCursor imguiCursor = ImGui.GetMouseCursor();
|
|
|
|
if (io.MouseDrawCursor || imguiCursor == ImGuiMouseCursor.None)
|
|
{
|
|
SDL.HideCursor();
|
|
}
|
|
else
|
|
{
|
|
IntPtr expectedCursor = _mouseCursors[(int)imguiCursor];
|
|
if (_mouseLastCursor != expectedCursor)
|
|
{
|
|
SDL.SetCursor(expectedCursor);
|
|
_mouseLastCursor = expectedCursor;
|
|
}
|
|
|
|
SDL.ShowCursor();
|
|
}
|
|
}
|
|
|
|
private ImGuiKey KeyEventToImGui(SDL.Keycode keycoade, SDL.Scancode scancode)
|
|
{
|
|
switch (scancode)
|
|
{
|
|
case SDL.Scancode.Kp0: return ImGuiKey.Keypad0;
|
|
case SDL.Scancode.Kp1: return ImGuiKey.Keypad1;
|
|
case SDL.Scancode.Kp2: return ImGuiKey.Keypad2;
|
|
case SDL.Scancode.Kp3: return ImGuiKey.Keypad3;
|
|
case SDL.Scancode.Kp4: return ImGuiKey.Keypad4;
|
|
case SDL.Scancode.Kp5: return ImGuiKey.Keypad5;
|
|
case SDL.Scancode.Kp6: return ImGuiKey.Keypad6;
|
|
case SDL.Scancode.Kp7: return ImGuiKey.Keypad7;
|
|
case SDL.Scancode.Kp8: return ImGuiKey.Keypad8;
|
|
case SDL.Scancode.Kp9: return ImGuiKey.Keypad9;
|
|
case SDL.Scancode.KpPeriod: return ImGuiKey.KeypadDecimal;
|
|
case SDL.Scancode.KpDivide: return ImGuiKey.KeypadDivide;
|
|
case SDL.Scancode.KpMultiply: return ImGuiKey.KeypadMultiply;
|
|
case SDL.Scancode.KpMinus: return ImGuiKey.KeypadSubtract;
|
|
case SDL.Scancode.KpPlus: return ImGuiKey.KeypadAdd;
|
|
case SDL.Scancode.KpEnter: return ImGuiKey.KeypadEnter;
|
|
case SDL.Scancode.KpEquals: return ImGuiKey.KeypadEqual;
|
|
default: break;
|
|
}
|
|
|
|
switch (keycoade)
|
|
{
|
|
case SDL.Keycode.Tab: return ImGuiKey.Tab;
|
|
case SDL.Keycode.Left: return ImGuiKey.LeftArrow;
|
|
case SDL.Keycode.Right: return ImGuiKey.RightArrow;
|
|
case SDL.Keycode.Up: return ImGuiKey.UpArrow;
|
|
case SDL.Keycode.Down: return ImGuiKey.DownArrow;
|
|
case SDL.Keycode.Pageup: return ImGuiKey.PageUp;
|
|
case SDL.Keycode.Pagedown: return ImGuiKey.PageDown;
|
|
case SDL.Keycode.Home: return ImGuiKey.Home;
|
|
case SDL.Keycode.End: return ImGuiKey.End;
|
|
case SDL.Keycode.Insert: return ImGuiKey.Insert;
|
|
case SDL.Keycode.Delete: return ImGuiKey.Delete;
|
|
case SDL.Keycode.Backspace: return ImGuiKey.Backspace;
|
|
case SDL.Keycode.Space: return ImGuiKey.Space;
|
|
case SDL.Keycode.Return: return ImGuiKey.Enter;
|
|
case SDL.Keycode.Escape: return ImGuiKey.Escape;
|
|
case SDL.Keycode.Apostrophe: return ImGuiKey.Apostrophe;
|
|
case SDL.Keycode.Comma: return ImGuiKey.Comma;
|
|
case SDL.Keycode.Minus: return ImGuiKey.Minus;
|
|
case SDL.Keycode.Period: return ImGuiKey.Period;
|
|
case SDL.Keycode.Slash: return ImGuiKey.Slash;
|
|
case SDL.Keycode.Semicolon: return ImGuiKey.Semicolon;
|
|
case SDL.Keycode.Equals: return ImGuiKey.Equal;
|
|
case SDL.Keycode.LeftBracket: return ImGuiKey.LeftBracket;
|
|
case SDL.Keycode.Backslash: return ImGuiKey.Backslash;
|
|
case SDL.Keycode.RightBracket: return ImGuiKey.RightBracket;
|
|
case SDL.Keycode.Grave: return ImGuiKey.GraveAccent;
|
|
case SDL.Keycode.Capslock: return ImGuiKey.CapsLock;
|
|
case SDL.Keycode.ScrollLock: return ImGuiKey.ScrollLock;
|
|
case SDL.Keycode.NumLockClear: return ImGuiKey.NumLock;
|
|
case SDL.Keycode.PrintScreen: return ImGuiKey.PrintScreen;
|
|
case SDL.Keycode.Pause: return ImGuiKey.Pause;
|
|
case SDL.Keycode.LCtrl: return ImGuiKey.LeftCtrl;
|
|
case SDL.Keycode.LShift: return ImGuiKey.LeftShift;
|
|
case SDL.Keycode.LAlt: return ImGuiKey.LeftAlt;
|
|
case SDL.Keycode.LGui: return ImGuiKey.LeftSuper;
|
|
case SDL.Keycode.RCtrl: return ImGuiKey.RightCtrl;
|
|
case SDL.Keycode.RShift: return ImGuiKey.RightShift;
|
|
case SDL.Keycode.RAlt: return ImGuiKey.RightAlt;
|
|
case SDL.Keycode.RGUI: return ImGuiKey.RightSuper;
|
|
case SDL.Keycode.Application: return ImGuiKey.Menu;
|
|
case SDL.Keycode.Alpha0: return ImGuiKey._0;
|
|
case SDL.Keycode.Alpha1: return ImGuiKey._1;
|
|
case SDL.Keycode.Alpha2: return ImGuiKey._2;
|
|
case SDL.Keycode.Alpha3: return ImGuiKey._3;
|
|
case SDL.Keycode.Alpha4: return ImGuiKey._4;
|
|
case SDL.Keycode.Alpha5: return ImGuiKey._5;
|
|
case SDL.Keycode.Alpha6: return ImGuiKey._6;
|
|
case SDL.Keycode.Alpha7: return ImGuiKey._7;
|
|
case SDL.Keycode.Alpha8: return ImGuiKey._8;
|
|
case SDL.Keycode.Alpha9: return ImGuiKey._9;
|
|
case SDL.Keycode.A: return ImGuiKey.A;
|
|
case SDL.Keycode.B: return ImGuiKey.B;
|
|
case SDL.Keycode.C: return ImGuiKey.C;
|
|
case SDL.Keycode.D: return ImGuiKey.D;
|
|
case SDL.Keycode.E: return ImGuiKey.E;
|
|
case SDL.Keycode.F: return ImGuiKey.F;
|
|
case SDL.Keycode.G: return ImGuiKey.G;
|
|
case SDL.Keycode.H: return ImGuiKey.H;
|
|
case SDL.Keycode.I: return ImGuiKey.I;
|
|
case SDL.Keycode.J: return ImGuiKey.J;
|
|
case SDL.Keycode.K: return ImGuiKey.K;
|
|
case SDL.Keycode.L: return ImGuiKey.L;
|
|
case SDL.Keycode.M: return ImGuiKey.M;
|
|
case SDL.Keycode.N: return ImGuiKey.N;
|
|
case SDL.Keycode.O: return ImGuiKey.O;
|
|
case SDL.Keycode.P: return ImGuiKey.P;
|
|
case SDL.Keycode.Q: return ImGuiKey.Q;
|
|
case SDL.Keycode.R: return ImGuiKey.R;
|
|
case SDL.Keycode.S: return ImGuiKey.S;
|
|
case SDL.Keycode.T: return ImGuiKey.T;
|
|
case SDL.Keycode.U: return ImGuiKey.U;
|
|
case SDL.Keycode.V: return ImGuiKey.V;
|
|
case SDL.Keycode.W: return ImGuiKey.W;
|
|
case SDL.Keycode.X: return ImGuiKey.X;
|
|
case SDL.Keycode.Y: return ImGuiKey.Y;
|
|
case SDL.Keycode.Z: return ImGuiKey.Z;
|
|
case SDL.Keycode.F1: return ImGuiKey.F1;
|
|
case SDL.Keycode.F2: return ImGuiKey.F2;
|
|
case SDL.Keycode.F3: return ImGuiKey.F3;
|
|
case SDL.Keycode.F4: return ImGuiKey.F4;
|
|
case SDL.Keycode.F5: return ImGuiKey.F5;
|
|
case SDL.Keycode.F6: return ImGuiKey.F6;
|
|
case SDL.Keycode.F7: return ImGuiKey.F7;
|
|
case SDL.Keycode.F8: return ImGuiKey.F8;
|
|
case SDL.Keycode.F9: return ImGuiKey.F9;
|
|
case SDL.Keycode.F10: return ImGuiKey.F10;
|
|
case SDL.Keycode.F11: return ImGuiKey.F11;
|
|
case SDL.Keycode.F12: return ImGuiKey.F12;
|
|
case SDL.Keycode.F13: return ImGuiKey.F13;
|
|
case SDL.Keycode.F14: return ImGuiKey.F14;
|
|
case SDL.Keycode.F15: return ImGuiKey.F15;
|
|
case SDL.Keycode.F16: return ImGuiKey.F16;
|
|
case SDL.Keycode.F17: return ImGuiKey.F17;
|
|
case SDL.Keycode.F18: return ImGuiKey.F18;
|
|
case SDL.Keycode.F19: return ImGuiKey.F19;
|
|
case SDL.Keycode.F20: return ImGuiKey.F20;
|
|
case SDL.Keycode.F21: return ImGuiKey.F21;
|
|
case SDL.Keycode.F22: return ImGuiKey.F22;
|
|
case SDL.Keycode.F23: return ImGuiKey.F23;
|
|
case SDL.Keycode.F24: return ImGuiKey.F24;
|
|
case SDL.Keycode.AcBack: return ImGuiKey.AppBack;
|
|
case SDL.Keycode.AcForward: return ImGuiKey.AppForward;
|
|
default: break;
|
|
}
|
|
|
|
return ImGuiKey.None;
|
|
}
|
|
|
|
public static IntPtr Data() => ImGui.GetIO().BackendPlatformUserData;
|
|
|
|
// I don't think we actually need these since it works just fine without them
|
|
//
|
|
// public static IntPtr GetClipboardText(IntPtr ctx)
|
|
// {
|
|
// if (clipboardTextPtr != IntPtr.Zero)
|
|
// {
|
|
// Marshal.FreeHGlobal(clipboardTextPtr);
|
|
// clipboardTextPtr = IntPtr.Zero;
|
|
// }
|
|
//
|
|
// string text = SDL.GetClipboardText();
|
|
// clipboardTextPtr = Marshal.StringToHGlobalAnsi(text ?? string.Empty);
|
|
// return clipboardTextPtr;
|
|
// }
|
|
//
|
|
// public static void SetClipboardText(IntPtr ctx, IntPtr text)
|
|
// {
|
|
// string managedText = Marshal.PtrToStringAnsi(text);
|
|
// if (managedText != null)
|
|
// {
|
|
// SDL.SetClipboardText(managedText);
|
|
// }
|
|
// }
|
|
|
|
// !! This is sorta fucked
|
|
// public static void UpdateKeyModifiers(SDL.Keymod keymods)
|
|
// {
|
|
// ImGuiIOPtr io = ImGui.GetIO();
|
|
// io.KeyCtrl = keymods.HasFlag(SDL.Keymod.LCtrl) || keymods.HasFlag(SDL.Keymod.RCtrl);
|
|
// io.KeyShift = keymods.HasFlag(SDL.Keymod.LShift) || keymods.HasFlag(SDL.Keymod.RShift);
|
|
// io.KeyAlt = keymods.HasFlag(SDL.Keymod.LAlt) || keymods.HasFlag(SDL.Keymod.RAlt);
|
|
// io.KeySuper = keymods.HasFlag(SDL.Keymod.LGUI) || keymods.HasFlag(SDL.Keymod.RGUI);
|
|
// }
|
|
|
|
private static void UpdateKeyModifiers(SDL.Keymod mod)
|
|
{
|
|
ImGuiIOPtr io = ImGui.GetIO();
|
|
io.AddKeyEvent(ImGuiKey.ModCtrl, (mod & SDL.Keymod.Ctrl) != 0);
|
|
io.AddKeyEvent(ImGuiKey.ModShift, (mod & SDL.Keymod.Shift) != 0);
|
|
io.AddKeyEvent(ImGuiKey.ModAlt, (mod & SDL.Keymod.Alt) != 0);
|
|
io.AddKeyEvent(ImGuiKey.ModSuper, (mod & SDL.Keymod.GUI) != 0);
|
|
}
|
|
|
|
public static ImGuiViewportPtr? GetViewportForWindowId(uint id)
|
|
{
|
|
ImGuiViewportPtr viewport = ImGui.GetMainViewport();
|
|
return viewport.ID == id ? ImGui.GetMainViewport() : null;
|
|
}
|
|
|
|
private static void SetupPlatformHandles(ImGuiViewportPtr viewport, IntPtr window)
|
|
{
|
|
viewport.PlatformHandle = window;
|
|
viewport.PlatformHandleRaw = 0;
|
|
#if _WIN32 && !__WINTR__
|
|
SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowWin32HWNDPointer, 0);
|
|
#elif __APPLE__ && SDL_VIDEO_DRIVER_COCOA
|
|
SDL.GetPointerProperty(SDL.GetWindowProperties(window), SDL.Props.WindowCocoaWindow, 0);
|
|
#endif
|
|
}
|
|
} |