Merge HardwareCursor and SoftwareCursor into CursorManager.
This commit is contained in:
committed by
Matthias Mailänder
parent
d7f43b33c7
commit
847db5e59b
@@ -39,7 +39,7 @@ namespace OpenRA
|
||||
|
||||
public static ModData ModData;
|
||||
public static Settings Settings;
|
||||
public static ICursor Cursor;
|
||||
public static CursorManager Cursor;
|
||||
public static bool HideCursor;
|
||||
static WorldRenderer worldRenderer;
|
||||
static string modLaunchWrapper;
|
||||
@@ -435,25 +435,7 @@ namespace OpenRA
|
||||
if (Cursor != null)
|
||||
Cursor.Dispose();
|
||||
|
||||
if (Settings.Graphics.HardwareCursors)
|
||||
{
|
||||
try
|
||||
{
|
||||
Cursor = new HardwareCursor(ModData.CursorProvider);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Log.Write("debug", "Error was: " + e.Message);
|
||||
|
||||
Console.WriteLine("Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Console.WriteLine("Error was: " + e.Message);
|
||||
|
||||
Cursor = new SoftwareCursor(ModData.CursorProvider);
|
||||
}
|
||||
}
|
||||
else
|
||||
Cursor = new SoftwareCursor(ModData.CursorProvider);
|
||||
Cursor = new CursorManager(ModData.CursorProvider);
|
||||
|
||||
PerfHistory.Items["render"].HasNormalTick = false;
|
||||
PerfHistory.Items["batches"].HasNormalTick = false;
|
||||
|
||||
286
OpenRA.Game/Graphics/CursorManager.cs
Normal file
286
OpenRA.Game/Graphics/CursorManager.cs
Normal file
@@ -0,0 +1,286 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class CursorManager
|
||||
{
|
||||
class Cursor
|
||||
{
|
||||
public string Name;
|
||||
public int2 PaddedSize;
|
||||
public Rectangle Bounds;
|
||||
|
||||
public int Length;
|
||||
public Sprite[] Sprites;
|
||||
public IHardwareCursor[] Cursors;
|
||||
}
|
||||
|
||||
readonly Dictionary<string, Cursor> cursors = new Dictionary<string, Cursor>();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
|
||||
Cursor cursor;
|
||||
bool isLocked = false;
|
||||
int2 lockedPosition;
|
||||
bool hardwareCursorsDisabled = false;
|
||||
|
||||
public readonly bool DoubleCursorSize;
|
||||
|
||||
public CursorManager(CursorProvider cursorProvider)
|
||||
{
|
||||
// Cursor settings are applied on game start
|
||||
DoubleCursorSize = Game.Settings.Graphics.CursorDouble;
|
||||
hardwareCursorsDisabled = !Game.Settings.Graphics.HardwareCursors;
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA);
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var frames = kv.Value.Frames;
|
||||
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
||||
|
||||
var c = new Cursor
|
||||
{
|
||||
Name = kv.Key,
|
||||
Bounds = Rectangle.FromLTRB(0, 0, 1, 1),
|
||||
|
||||
Length = 0,
|
||||
Sprites = new Sprite[frames.Length],
|
||||
Cursors = new IHardwareCursor[frames.Length]
|
||||
};
|
||||
|
||||
// Hardware cursors have a number of odd platform-specific bugs/limitations.
|
||||
// Reduce the number of edge cases by padding the individual frames such that:
|
||||
// - the hotspot is inside the frame bounds (enforced by SDL)
|
||||
// - all frames within a sequence have the same size (needed for macOS 10.15)
|
||||
// - the frame size is a multiple of 8 (needed for Windows)
|
||||
foreach (var f in frames)
|
||||
{
|
||||
// Hotspot is specified relative to the center of the frame
|
||||
var hotspot = f.Offset.ToInt2() - kv.Value.Hotspot - new int2(f.Size) / 2;
|
||||
|
||||
// SheetBuilder expects data in BGRA
|
||||
var data = FrameToBGRA(kv.Key, f, palette);
|
||||
c.Sprites[c.Length++] = sheetBuilder.Add(data, f.Size, 0, hotspot);
|
||||
|
||||
// Bounds relative to the hotspot
|
||||
c.Bounds = Rectangle.Union(c.Bounds, new Rectangle(hotspot, f.Size));
|
||||
}
|
||||
|
||||
// Pad bottom-right edge to make the frame size a multiple of 8
|
||||
c.PaddedSize = 8 * new int2((c.Bounds.Width + 7) / 8, (c.Bounds.Height + 7) / 8);
|
||||
|
||||
cursors.Add(kv.Key, c);
|
||||
}
|
||||
|
||||
CreateOrUpdateHardwareCursors();
|
||||
|
||||
foreach (var s in sheetBuilder.AllSheets)
|
||||
s.ReleaseBuffer();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
void CreateOrUpdateHardwareCursors()
|
||||
{
|
||||
if (hardwareCursorsDisabled)
|
||||
return;
|
||||
|
||||
// Dispose any existing cursors to avoid leaking native resources
|
||||
ClearHardwareCursors();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var kv in cursors)
|
||||
{
|
||||
var template = kv.Value;
|
||||
for (var i = 0; i < template.Sprites.Length; i++)
|
||||
{
|
||||
if (template.Cursors[i] != null)
|
||||
template.Cursors[i].Dispose();
|
||||
|
||||
// Calculate the padding to position the frame within sequenceBounds
|
||||
var paddingTL = -(template.Bounds.Location - template.Sprites[i].Offset.XY.ToInt2());
|
||||
var paddingBR = template.PaddedSize - new int2(template.Sprites[i].Bounds.Size) - paddingTL;
|
||||
|
||||
template.Cursors[i] = CreateHardwareCursor(kv.Key, template.Sprites[i], paddingTL, paddingBR, -template.Bounds.Location);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Log.Write("debug", "Error was: " + e.Message);
|
||||
|
||||
Console.WriteLine("Failed to initialize hardware cursors. Falling back to software cursors.");
|
||||
Console.WriteLine("Error was: " + e.Message);
|
||||
|
||||
ClearHardwareCursors();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCursor(string cursorName)
|
||||
{
|
||||
if ((cursorName == null && cursor == null) || (cursor != null && cursorName == cursor.Name))
|
||||
return;
|
||||
|
||||
if (cursorName == null || !cursors.TryGetValue(cursorName, out cursor))
|
||||
cursor = null;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
int frame;
|
||||
int ticks;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (cursor == null || cursor.Cursors.Length == 1)
|
||||
return;
|
||||
|
||||
if (++ticks > 2)
|
||||
{
|
||||
ticks -= 2;
|
||||
frame++;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (cursor != null && frame >= cursor.Cursors.Length)
|
||||
frame %= cursor.Cursors.Length;
|
||||
|
||||
if (cursor == null || isLocked)
|
||||
Game.Renderer.Window.SetHardwareCursor(null);
|
||||
else
|
||||
Game.Renderer.Window.SetHardwareCursor(cursor.Cursors[frame]);
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
// Cursor is hidden
|
||||
if (cursor == null)
|
||||
return;
|
||||
|
||||
// Hardware cursor is enabled
|
||||
if (!isLocked && cursor.Cursors[frame % cursor.Length] != null)
|
||||
return;
|
||||
|
||||
// Render cursor in software
|
||||
var doubleCursor = DoubleCursorSize && cursor.Name != "default";
|
||||
var cursorSprite = cursor.Sprites[frame % cursor.Length];
|
||||
var cursorSize = doubleCursor ? 2.0f * cursorSprite.Size : cursorSprite.Size;
|
||||
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
|
||||
|
||||
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
|
||||
mousePos,
|
||||
cursorSize);
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
lockedPosition = Viewport.LastMousePos;
|
||||
Game.Renderer.Window.SetRelativeMouseMode(true);
|
||||
isLocked = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(false);
|
||||
isLocked = false;
|
||||
Update();
|
||||
}
|
||||
|
||||
public static byte[] FrameToBGRA(string name, ISpriteFrame frame, ImmutablePalette palette)
|
||||
{
|
||||
// Data is already in BGRA format
|
||||
if (frame.Type == SpriteFrameType.BGRA)
|
||||
return frame.Data;
|
||||
|
||||
// Cursors may be either native BGRA or Indexed.
|
||||
// Indexed sprites are converted to BGRA using the referenced palette.
|
||||
// All palettes must be explicitly referenced, even if they are embedded in the sprite.
|
||||
if (frame.Type == SpriteFrameType.Indexed && palette == null)
|
||||
throw new InvalidOperationException("Cursor sequence `{0}` attempted to load an indexed sprite but does not define Palette".F(name));
|
||||
|
||||
var width = frame.Size.Width;
|
||||
var height = frame.Size.Height;
|
||||
var data = new byte[4 * width * height];
|
||||
for (var j = 0; j < height; j++)
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(palette[frame.Data[j * width + i]]);
|
||||
var c = palette[frame.Data[j * width + i]];
|
||||
var k = 4 * (j * width + i);
|
||||
|
||||
// Convert RGBA to BGRA
|
||||
data[k] = bytes[2];
|
||||
data[k + 1] = bytes[1];
|
||||
data[k + 2] = bytes[0];
|
||||
data[k + 3] = bytes[3];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
IHardwareCursor CreateHardwareCursor(string name, Sprite data, int2 paddingTL, int2 paddingBR, int2 hotspot)
|
||||
{
|
||||
var size = data.Bounds.Size;
|
||||
var srcStride = data.Sheet.Size.Width;
|
||||
var srcData = data.Sheet.GetData();
|
||||
var newWidth = paddingTL.X + size.Width + paddingBR.X;
|
||||
var newHeight = paddingTL.Y + size.Height + paddingBR.Y;
|
||||
var rgbaData = new byte[4 * newWidth * newHeight];
|
||||
for (var j = 0; j < size.Height; j++)
|
||||
{
|
||||
for (var i = 0; i < size.Width; i++)
|
||||
{
|
||||
var src = 4 * ((j + data.Bounds.Top) * srcStride + data.Bounds.Left + i);
|
||||
var dest = 4 * ((j + paddingTL.Y) * newWidth + i + paddingTL.X);
|
||||
Array.Copy(srcData, src, rgbaData, dest, 4);
|
||||
}
|
||||
}
|
||||
|
||||
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(newWidth, newHeight), rgbaData, hotspot);
|
||||
}
|
||||
|
||||
void ClearHardwareCursors()
|
||||
{
|
||||
foreach (var c in cursors.Values)
|
||||
{
|
||||
for (var i = 0; i < c.Cursors.Length; i++)
|
||||
{
|
||||
if (c.Cursors[i] != null)
|
||||
{
|
||||
c.Cursors[i].Dispose();
|
||||
c.Cursors[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ClearHardwareCursors();
|
||||
|
||||
cursors.Clear();
|
||||
sheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ namespace OpenRA.Graphics
|
||||
{
|
||||
public readonly IReadOnlyDictionary<string, CursorSequence> Cursors;
|
||||
public readonly IReadOnlyDictionary<string, ImmutablePalette> Palettes;
|
||||
public readonly bool DoubleCursorSize;
|
||||
|
||||
public CursorProvider(ModData modData)
|
||||
{
|
||||
@@ -49,9 +48,6 @@ namespace OpenRA.Graphics
|
||||
cursors.Add(sequence.Key, new CursorSequence(frameCache, sequence.Key, s.Key, s.Value.Value, sequence.Value));
|
||||
|
||||
Cursors = cursors.AsReadOnly();
|
||||
|
||||
// Cursor size changes are applied on game start
|
||||
DoubleCursorSize = Game.Settings.Graphics.CursorDouble;
|
||||
}
|
||||
|
||||
public bool HasCursorSequence(string cursor)
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public sealed class HardwareCursor : ICursor
|
||||
{
|
||||
readonly Dictionary<string, IHardwareCursor[]> hardwareCursors = new Dictionary<string, IHardwareCursor[]>();
|
||||
readonly CursorProvider cursorProvider;
|
||||
readonly Dictionary<string, Sprite[]> sprites = new Dictionary<string, Sprite[]>();
|
||||
readonly SheetBuilder sheetBuilder;
|
||||
|
||||
CursorSequence cursor;
|
||||
bool isLocked = false;
|
||||
int2 lockedPosition;
|
||||
|
||||
public HardwareCursor(CursorProvider cursorProvider)
|
||||
{
|
||||
this.cursorProvider = cursorProvider;
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.Indexed);
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var frames = kv.Value.Frames;
|
||||
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
||||
|
||||
// Hardware cursors have a number of odd platform-specific bugs/limitations.
|
||||
// Reduce the number of edge cases by padding the individual frames such that:
|
||||
// - the hotspot is inside the frame bounds (enforced by SDL)
|
||||
// - all frames within a sequence have the same size (needed for macOS 10.15)
|
||||
// - the frame size is a multiple of 8 (needed for Windows)
|
||||
var sequenceBounds = Rectangle.FromLTRB(0, 0, 1, 1);
|
||||
var frameHotspots = new int2[frames.Length];
|
||||
for (var i = 0; i < frames.Length; i++)
|
||||
{
|
||||
// Hotspot relative to the center of the frame
|
||||
frameHotspots[i] = kv.Value.Hotspot - frames[i].Offset.ToInt2() + new int2(frames[i].Size) / 2;
|
||||
|
||||
// Bounds relative to the hotspot
|
||||
sequenceBounds = Rectangle.Union(sequenceBounds, new Rectangle(-frameHotspots[i], frames[i].Size));
|
||||
}
|
||||
|
||||
// Pad bottom-right edge to make the frame size a multiple of 8
|
||||
var paddedSize = 8 * new int2((sequenceBounds.Width + 7) / 8, (sequenceBounds.Height + 7) / 8);
|
||||
|
||||
var cursors = new IHardwareCursor[frames.Length];
|
||||
var frameSprites = new Sprite[frames.Length];
|
||||
for (var i = 0; i < frames.Length; i++)
|
||||
{
|
||||
// Software rendering is used when the cursor is locked
|
||||
// SheetBuilder expects data in BGRA
|
||||
var data = SoftwareCursor.FrameToBGRA(kv.Key, frames[i], palette);
|
||||
frameSprites[i] = sheetBuilder.Add(data, frames[i].Size, 0, frames[i].Offset);
|
||||
|
||||
// Calculate the padding to position the frame within sequenceBounds
|
||||
var paddingTL = -(sequenceBounds.Location + frameHotspots[i]);
|
||||
var paddingBR = paddedSize - new int2(frames[i].Size) - paddingTL;
|
||||
cursors[i] = CreateCursor(kv.Key, data, frames[i].Size, paddingTL, paddingBR, -sequenceBounds.Location);
|
||||
}
|
||||
|
||||
hardwareCursors.Add(kv.Key, cursors);
|
||||
sprites.Add(kv.Key, frameSprites);
|
||||
}
|
||||
|
||||
sheetBuilder.Current.ReleaseBuffer();
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
IHardwareCursor CreateCursor(string name, byte[] data, Size size, int2 paddingTL, int2 paddingBR, int2 hotspot)
|
||||
{
|
||||
// Pad the cursor and convert to RBGA
|
||||
var newWidth = paddingTL.X + size.Width + paddingBR.X;
|
||||
var newHeight = paddingTL.Y + size.Height + paddingBR.Y;
|
||||
var rgbaData = new byte[4 * newWidth * newHeight];
|
||||
for (var j = 0; j < size.Height; j++)
|
||||
{
|
||||
for (var i = 0; i < size.Width; i++)
|
||||
{
|
||||
var src = 4 * (j * size.Width + i);
|
||||
var dest = 4 * ((j + paddingTL.Y) * newWidth + i + paddingTL.X);
|
||||
|
||||
// CreateHardwareCursor expects data in RGBA
|
||||
rgbaData[dest] = data[src + 2];
|
||||
rgbaData[dest + 1] = data[src + 1];
|
||||
rgbaData[dest + 2] = data[src];
|
||||
rgbaData[dest + 3] = data[src + 3];
|
||||
}
|
||||
}
|
||||
|
||||
return Game.Renderer.Window.CreateHardwareCursor(name, new Size(newWidth, newHeight), rgbaData, hotspot);
|
||||
}
|
||||
|
||||
public void SetCursor(string cursorName)
|
||||
{
|
||||
if ((cursorName == null && cursor == null) || (cursor != null && cursorName == cursor.Name))
|
||||
return;
|
||||
|
||||
if (cursorName == null || !cursorProvider.Cursors.TryGetValue(cursorName, out cursor))
|
||||
cursor = null;
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
int frame;
|
||||
int ticks;
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (cursor == null || cursor.Length == 1)
|
||||
return;
|
||||
|
||||
if (++ticks > 2)
|
||||
{
|
||||
ticks -= 2;
|
||||
frame++;
|
||||
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (cursor != null && frame >= cursor.Length)
|
||||
frame %= cursor.Length;
|
||||
|
||||
if (cursor == null || isLocked)
|
||||
Game.Renderer.Window.SetHardwareCursor(null);
|
||||
else
|
||||
Game.Renderer.Window.SetHardwareCursor(hardwareCursors[cursor.Name][frame]);
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
if (cursor == null || !isLocked)
|
||||
return;
|
||||
|
||||
var cursorSequence = cursorProvider.GetCursorSequence(cursor.Name);
|
||||
var cursorSprite = sprites[cursor.Name][frame];
|
||||
|
||||
var cursorOffset = cursorSequence.Hotspot + (0.5f * cursorSprite.Size.XY).ToInt2();
|
||||
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
|
||||
lockedPosition - cursorOffset,
|
||||
cursorSprite.Size);
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
lockedPosition = Viewport.LastMousePos;
|
||||
Game.Renderer.Window.SetRelativeMouseMode(true);
|
||||
isLocked = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(false);
|
||||
isLocked = false;
|
||||
Update();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var cursors in hardwareCursors)
|
||||
foreach (var cursor in cursors.Value)
|
||||
cursor.Dispose();
|
||||
|
||||
sheetBuilder.Dispose();
|
||||
hardwareCursors.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
||||
* This file is part of OpenRA, which is free software. It is made
|
||||
* available to you under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenRA.Graphics
|
||||
{
|
||||
public interface ICursor : IDisposable
|
||||
{
|
||||
void Render(Renderer renderer);
|
||||
void SetCursor(string cursor);
|
||||
void Tick();
|
||||
void Lock();
|
||||
void Unlock();
|
||||
}
|
||||
|
||||
public sealed class SoftwareCursor : ICursor
|
||||
{
|
||||
readonly Dictionary<string, Sprite[]> sprites = new Dictionary<string, Sprite[]>();
|
||||
readonly CursorProvider cursorProvider;
|
||||
SheetBuilder sheetBuilder;
|
||||
|
||||
bool isLocked = false;
|
||||
int2 lockedPosition;
|
||||
|
||||
public SoftwareCursor(CursorProvider cursorProvider)
|
||||
{
|
||||
this.cursorProvider = cursorProvider;
|
||||
|
||||
sheetBuilder = new SheetBuilder(SheetType.BGRA, 1024);
|
||||
|
||||
foreach (var kv in cursorProvider.Cursors)
|
||||
{
|
||||
var palette = !string.IsNullOrEmpty(kv.Value.Palette) ? cursorProvider.Palettes[kv.Value.Palette] : null;
|
||||
var s = kv.Value.Frames.Select(f => sheetBuilder.Add(FrameToBGRA(kv.Key, f, palette), f.Size, 0, f.Offset)).ToArray();
|
||||
|
||||
sprites.Add(kv.Key, s);
|
||||
}
|
||||
|
||||
sheetBuilder.Current.ReleaseBuffer();
|
||||
|
||||
Game.Renderer.Window.SetHardwareCursor(null);
|
||||
}
|
||||
|
||||
public static byte[] FrameToBGRA(string name, ISpriteFrame frame, ImmutablePalette palette)
|
||||
{
|
||||
// Data is already in BGRA format
|
||||
if (frame.Type == SpriteFrameType.BGRA)
|
||||
return frame.Data;
|
||||
|
||||
// Cursors may be either native BGRA or Indexed.
|
||||
// Indexed sprites are converted to BGRA using the referenced palette.
|
||||
// All palettes must be explicitly referenced, even if they are embedded in the sprite.
|
||||
if (frame.Type == SpriteFrameType.Indexed && palette == null)
|
||||
throw new InvalidOperationException("Cursor sequence `{0}` attempted to load an indexed sprite but does not define Palette".F(name));
|
||||
|
||||
var width = frame.Size.Width;
|
||||
var height = frame.Size.Height;
|
||||
var data = new byte[4 * width * height];
|
||||
for (var j = 0; j < height; j++)
|
||||
{
|
||||
for (var i = 0; i < width; i++)
|
||||
{
|
||||
var bytes = BitConverter.GetBytes(palette[frame.Data[j * width + i]]);
|
||||
var c = palette[frame.Data[j * width + i]];
|
||||
var k = 4 * (j * width + i);
|
||||
|
||||
// Convert RGBA to BGRA
|
||||
data[k] = bytes[2];
|
||||
data[k + 1] = bytes[1];
|
||||
data[k + 2] = bytes[0];
|
||||
data[k + 3] = bytes[3];
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
string cursorName;
|
||||
public void SetCursor(string cursor)
|
||||
{
|
||||
cursorName = cursor;
|
||||
}
|
||||
|
||||
float cursorFrame;
|
||||
public void Tick()
|
||||
{
|
||||
cursorFrame += 0.5f;
|
||||
}
|
||||
|
||||
public void Render(Renderer renderer)
|
||||
{
|
||||
if (cursorName == null)
|
||||
return;
|
||||
|
||||
var doubleCursor = cursorProvider.DoubleCursorSize && cursorName != "default";
|
||||
var cursorSequence = cursorProvider.GetCursorSequence(cursorName);
|
||||
var cursorSprite = sprites[cursorName][(int)cursorFrame % cursorSequence.Length];
|
||||
var cursorSize = doubleCursor ? 2.0f * cursorSprite.Size : cursorSprite.Size;
|
||||
|
||||
var cursorOffset = doubleCursor ?
|
||||
(2 * cursorSequence.Hotspot) + cursorSprite.Size.XY.ToInt2() :
|
||||
cursorSequence.Hotspot + (0.5f * cursorSprite.Size.XY).ToInt2();
|
||||
|
||||
var mousePos = isLocked ? lockedPosition : Viewport.LastMousePos;
|
||||
|
||||
renderer.RgbaSpriteRenderer.DrawSprite(cursorSprite,
|
||||
mousePos - cursorOffset,
|
||||
cursorSize);
|
||||
}
|
||||
|
||||
public void Lock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(true);
|
||||
lockedPosition = Viewport.LastMousePos;
|
||||
isLocked = true;
|
||||
}
|
||||
|
||||
public void Unlock()
|
||||
{
|
||||
Game.Renderer.Window.SetRelativeMouseMode(false);
|
||||
isLocked = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
sheetBuilder.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,14 +20,12 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
|
||||
Sprite sprite;
|
||||
readonly WorldRenderer worldRenderer;
|
||||
readonly CursorProvider cursorProvider;
|
||||
string palette;
|
||||
int2 location;
|
||||
|
||||
[ObjectCreator.UseCtor]
|
||||
public MouseAttachmentWidget(ModData modData, WorldRenderer worldRenderer)
|
||||
{
|
||||
cursorProvider = modData.CursorProvider;
|
||||
this.worldRenderer = worldRenderer;
|
||||
}
|
||||
|
||||
@@ -35,9 +33,8 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
if (sprite != null && palette != null)
|
||||
{
|
||||
var scale = Game.Cursor is SoftwareCursor && cursorProvider.DoubleCursorSize ? 2 : 1;
|
||||
var directionPalette = worldRenderer.Palette(palette);
|
||||
WidgetUtils.DrawSHPCentered(sprite, ChildOrigin, directionPalette, scale);
|
||||
WidgetUtils.DrawSHPCentered(sprite, ChildOrigin, directionPalette, Game.Cursor.DoubleCursorSize ? 2 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
public class TooltipContainerWidget : Widget
|
||||
{
|
||||
static readonly Action Nothing = () => { };
|
||||
readonly CursorProvider cursorProvider;
|
||||
|
||||
public int2 CursorOffset = new int2(0, 20);
|
||||
public int BottomEdgeYOffset = -5;
|
||||
@@ -30,7 +29,6 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
|
||||
public TooltipContainerWidget()
|
||||
{
|
||||
cursorProvider = Game.ModData.CursorProvider;
|
||||
IsVisible = () => Game.RunTime > Viewport.LastMoveRunTime + TooltipDelayMilliseconds;
|
||||
}
|
||||
|
||||
@@ -54,7 +52,7 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
{
|
||||
get
|
||||
{
|
||||
var pos = Viewport.LastMousePos + (cursorProvider.DoubleCursorSize ? CursorOffset * 2 : CursorOffset);
|
||||
var pos = Viewport.LastMousePos + (Game.Cursor.DoubleCursorSize ? CursorOffset * 2 : CursorOffset);
|
||||
if (tooltip != null)
|
||||
{
|
||||
// If the tooltip overlaps the right edge of the screen, move it left until it fits
|
||||
@@ -63,7 +61,7 @@ namespace OpenRA.Mods.Common.Widgets
|
||||
|
||||
// If the tooltip overlaps the bottom edge of the screen, switch tooltip above cursor
|
||||
if (pos.Y + tooltip.Bounds.Bottom > Game.Renderer.Resolution.Height)
|
||||
pos = pos.WithY(Viewport.LastMousePos.Y + (cursorProvider.DoubleCursorSize ? 2 : 1) * BottomEdgeYOffset - tooltip.Bounds.Height);
|
||||
pos = pos.WithY(Viewport.LastMousePos.Y + (Game.Cursor.DoubleCursorSize ? 2 : 1) * BottomEdgeYOffset - tooltip.Bounds.Height);
|
||||
}
|
||||
|
||||
return pos;
|
||||
|
||||
Reference in New Issue
Block a user