Files
RobustToolbox/Robust.Client/Graphics/Clyde/Clyde.Cursor.cs
T
Pieter-Jan Briers abdc5418e2 Clyde object resource management.
Objects now use IDisposable().
Finalizers.
2020-07-18 16:01:24 +02:00

145 lines
4.7 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using OpenToolkit.GraphicsLibraryFramework;
using Robust.Client.Interfaces.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Maths;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using GlfwImage = OpenToolkit.GraphicsLibraryFramework.Image;
namespace Robust.Client.Graphics.Clyde
{
internal sealed partial class Clyde
{
// These are actually Cursor* but we can't do that because no pointer generic arguments.
// Need a queue to dispose cursors since the GLFW methods aren't allowed from non-main thread (finalizers).
// And they also aren't re-entrant.
private readonly ConcurrentQueue<IntPtr> _cursorDisposeQueue = new ConcurrentQueue<IntPtr>();
private readonly Dictionary<StandardCursorShape, CursorImpl> _standardCursors =
new Dictionary<StandardCursorShape, CursorImpl>();
// Keep current active cursor around so it doesn't get garbage collected.
private CursorImpl? _currentCursor;
public ICursor GetStandardCursor(StandardCursorShape shape)
{
return _standardCursors[shape];
}
public unsafe ICursor CreateCursor(Image<Rgba32> image, Vector2i hotSpot)
{
fixed (Rgba32* pixPtr = image.GetPixelSpan())
{
var gImg = new GlfwImage(image.Width, image.Height, (byte*) pixPtr);
var (hotX, hotY) = hotSpot;
var ptr = GLFW.CreateCursor(gImg, hotX, hotY);
return new CursorImpl(this, ptr, false);
}
}
public unsafe void SetCursor(ICursor? cursor)
{
if (_currentCursor == cursor)
{
// Nothing has to be done!
return;
}
if (cursor == null)
{
_currentCursor = null;
GLFW.SetCursor(_glfwWindow, null);
return;
}
if (!(cursor is CursorImpl impl) || impl.Owner != this)
{
throw new ArgumentException("Cursor is not created by this clyde instance.");
}
if (impl.Cursor == null)
{
throw new ObjectDisposedException(nameof(cursor));
}
_currentCursor = impl;
GLFW.SetCursor(_glfwWindow, impl.Cursor);
}
private unsafe void FlushCursorDispose()
{
while (_cursorDisposeQueue.TryDequeue(out var cursor))
{
var ptr = (Cursor*) cursor;
if (_currentCursor != null && ptr == _currentCursor.Cursor)
{
// Currently active cursor getting disposed.
_currentCursor = null;
}
GLFW.DestroyCursor(ptr);
}
}
private void InitCursors()
{
unsafe void AddStandardCursor(StandardCursorShape standardShape, CursorShape shape)
{
var ptr = GLFW.CreateStandardCursor(shape);
var impl = new CursorImpl(this, ptr, true);
_standardCursors.Add(standardShape, impl);
}
AddStandardCursor(StandardCursorShape.Arrow, CursorShape.Arrow);
AddStandardCursor(StandardCursorShape.IBeam, CursorShape.IBeam);
AddStandardCursor(StandardCursorShape.Crosshair, CursorShape.Crosshair);
AddStandardCursor(StandardCursorShape.Hand, CursorShape.Hand);
AddStandardCursor(StandardCursorShape.HResize, CursorShape.HResize);
AddStandardCursor(StandardCursorShape.VResize, CursorShape.VResize);
}
private sealed class CursorImpl : ICursor
{
private readonly bool _standard;
public Clyde Owner { get; }
public unsafe Cursor* Cursor { get; private set; }
public unsafe CursorImpl(Clyde clyde, Cursor* pointer, bool standard)
{
_standard = standard;
Owner = clyde;
Cursor = pointer;
}
~CursorImpl()
{
DisposeImpl();
}
private unsafe void DisposeImpl()
{
Owner._cursorDisposeQueue.Enqueue((IntPtr) Cursor);
Cursor = null;
}
public void Dispose()
{
if (_standard)
{
throw new InvalidOperationException("Can't dispose standard cursor shape.");
}
GC.SuppressFinalize(this);
DisposeImpl();
}
}
}
}