diff --git a/OpenRA.Game/Graphics/HardwareCursor.cs b/OpenRA.Game/Graphics/HardwareCursor.cs new file mode 100644 index 0000000000..925c1c2a07 --- /dev/null +++ b/OpenRA.Game/Graphics/HardwareCursor.cs @@ -0,0 +1,138 @@ +#region Copyright & License Information +/* +* Copyright 2007-2014 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. For more information, +* see COPYING. +*/ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenRA.Graphics; +using OpenRA.Primitives; + +namespace OpenRA.Graphics +{ + public class HardwareCursor : ICursor + { + readonly Dictionary hardwareCursors = new Dictionary(); + readonly CursorProvider cursorProvider; + CursorSequence cursor; + + public HardwareCursor(CursorProvider cursorProvider) + { + this.cursorProvider = cursorProvider; + + foreach (var kv in cursorProvider.Cursors) + { + var palette = cursorProvider.Palettes[kv.Value.Palette]; + var hc = kv.Value.Frames + .Select(f => CreateCursor(f, palette, kv.Key, kv.Value)) + .ToArray(); + + hardwareCursors.Add(kv.Key, hc); + } + + Update(); + } + + IHardwareCursor CreateCursor(ISpriteFrame f, ImmutablePalette palette, string name, CursorSequence sequence) + { + var hotspot = sequence.Hotspot - f.Offset.ToInt2() + new int2(f.Size) / 2; + + // Expand the frame if required to include the hotspot + var frameWidth = f.Size.Width; + var dataWidth = f.Size.Width; + var dataX = 0; + if (hotspot.X < 0) + { + dataX = -hotspot.X; + dataWidth += dataX; + hotspot.X = 0; + } + else if (hotspot.X >= frameWidth) + dataWidth = hotspot.X + 1; + + var frameHeight = f.Size.Height; + var dataHeight = f.Size.Height; + var dataY = 0; + if (hotspot.Y < 0) + { + dataY = -hotspot.Y; + dataHeight += dataY; + hotspot.Y = 0; + } + else if (hotspot.Y >= frameHeight) + dataHeight = hotspot.Y + 1; + + var data = new byte[4 * dataWidth * dataHeight]; + for (var j = 0; j < frameHeight; j++) + { + for (var i = 0; i < frameWidth; i++) + { + var bytes = BitConverter.GetBytes(palette[f.Data[j * frameWidth + i]]); + var start = 4 * ((j + dataY) * dataWidth + dataX + i); + for (var k = 0; k < 4; k++) + data[start + k] = bytes[k]; + } + } + + return Game.Renderer.Device.CreateHardwareCursor(name, new Size(dataWidth, dataHeight), data, 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) + Game.Renderer.Device.SetHardwareCursor(null); + else + { + if (frame >= cursor.Length) + frame = frame % cursor.Length; + + Game.Renderer.Device.SetHardwareCursor(hardwareCursors[cursor.Name][frame]); + } + } + + public void Render(Renderer renderer) { } + + public void Dispose() + { + foreach (var cursors in hardwareCursors) + foreach (var cursor in cursors.Value) + cursor.Dispose(); + + hardwareCursors.Clear(); + } + } +} \ No newline at end of file diff --git a/OpenRA.Game/Graphics/IGraphicsDevice.cs b/OpenRA.Game/Graphics/IGraphicsDevice.cs index c3ec766649..23c000bce6 100755 --- a/OpenRA.Game/Graphics/IGraphicsDevice.cs +++ b/OpenRA.Game/Graphics/IGraphicsDevice.cs @@ -32,6 +32,8 @@ namespace OpenRA IGraphicsDevice Create(Size size, WindowMode windowMode); } + public interface IHardwareCursor : IDisposable { } + public enum BlendMode : byte { None, Alpha, Additive, Subtractive, Multiply } public interface IGraphicsDevice : IDisposable @@ -61,6 +63,9 @@ namespace OpenRA void GrabWindowMouseFocus(); void ReleaseWindowMouseFocus(); + + IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot); + void SetHardwareCursor(IHardwareCursor cursor); } public interface IVertexBuffer : IDisposable diff --git a/OpenRA.Game/Graphics/SoftwareCursor.cs b/OpenRA.Game/Graphics/SoftwareCursor.cs index 5a4de6c9e5..cbff0c227f 100644 --- a/OpenRA.Game/Graphics/SoftwareCursor.cs +++ b/OpenRA.Game/Graphics/SoftwareCursor.cs @@ -50,6 +50,8 @@ namespace OpenRA.Graphics } sheetBuilder.Current.ReleaseBuffer(); + + Game.Renderer.Device.SetHardwareCursor(null); } PaletteReference CreatePaletteReference(string name) diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj index a1381d9978..6ab4068dc5 100644 --- a/OpenRA.Game/OpenRA.Game.csproj +++ b/OpenRA.Game/OpenRA.Game.csproj @@ -253,6 +253,7 @@ + diff --git a/OpenRA.Renderer.Null/NullGraphicsDevice.cs b/OpenRA.Renderer.Null/NullGraphicsDevice.cs index 299fa0ddea..265aafb19e 100644 --- a/OpenRA.Renderer.Null/NullGraphicsDevice.cs +++ b/OpenRA.Renderer.Null/NullGraphicsDevice.cs @@ -66,6 +66,9 @@ namespace OpenRA.Renderer.Null public ITexture CreateTexture(Bitmap bitmap) { return new NullTexture(); } public IFrameBuffer CreateFrameBuffer(Size s) { return new NullFrameBuffer(); } public IShader CreateShader(string name) { return new NullShader(); } + + public IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot) { return null; } + public void SetHardwareCursor(IHardwareCursor cursor) { } } public class NullShader : IShader diff --git a/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs b/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs index dce2c1bd73..084e10073c 100755 --- a/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs +++ b/OpenRA.Renderer.Sdl2/Sdl2GraphicsDevice.cs @@ -10,6 +10,8 @@ using System; using System.Drawing; +using System.IO; +using System.Runtime.InteropServices; using OpenRA; using OpenRA.Graphics; using OpenTK.Graphics.OpenGL; @@ -80,7 +82,6 @@ namespace OpenRA.Renderer.Sdl2 SDL.SDL_SetHint(SDL.SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0"); } - SDL.SDL_ShowCursor(0); context = SDL.SDL_GL_CreateContext(window); SDL.SDL_GL_MakeCurrent(window, context); GL.LoadAll(); @@ -103,10 +104,53 @@ namespace OpenRA.Renderer.Sdl2 input = new Sdl2Input(); } + public IHardwareCursor CreateHardwareCursor(string name, Size size, byte[] data, int2 hotspot) + { + var c = new SDL2HardwareCursor(size, data, hotspot); + if (c.Cursor == IntPtr.Zero) + throw new InvalidDataException("Failed to create hardware cursor `{0}`: {1}".F(name, SDL.SDL_GetError())); + + return c; + } + + public void SetHardwareCursor(IHardwareCursor cursor) + { + var c = cursor as SDL2HardwareCursor; + if (c == null) + SDL.SDL_ShowCursor(0); + else + { + SDL.SDL_ShowCursor(1); + SDL.SDL_SetCursor(c.Cursor); + } + } + + class SDL2HardwareCursor : IHardwareCursor + { + public readonly IntPtr Cursor; + readonly IntPtr surface; + + public SDL2HardwareCursor(Size size, byte[] data, int2 hotspot) + { + surface = SDL.SDL_CreateRGBSurface(0, size.Width, size.Height, 32, 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000); + + var sur = (SDL2.SDL.SDL_Surface)Marshal.PtrToStructure(surface, typeof(SDL2.SDL.SDL_Surface)); + Marshal.Copy(data, 0, sur.pixels, data.Length); + Cursor = SDL.SDL_CreateColorCursor(surface, hotspot.X, hotspot.Y); + } + + public void Dispose() + { + SDL.SDL_FreeCursor(Cursor); + SDL.SDL_FreeSurface(surface); + } + } + public void Dispose() { if (disposed) return; + disposed = true; if (context != IntPtr.Zero) {