diff --git a/Source/Examples/Data/Textures/cursor.png b/Source/Examples/Data/Textures/cursor.png
new file mode 100644
index 00000000..d0dc4a48
Binary files /dev/null and b/Source/Examples/Data/Textures/cursor.png differ
diff --git a/Source/Examples/OpenTK.Examples.csproj b/Source/Examples/OpenTK.Examples.csproj
index 3c9f126d..d0724a0a 100644
--- a/Source/Examples/OpenTK.Examples.csproj
+++ b/Source/Examples/OpenTK.Examples.csproj
@@ -146,6 +146,7 @@
Code
+
Code
@@ -547,10 +548,14 @@
Always
+
+
+ Always
+
@@ -643,4 +648,4 @@
-
\ No newline at end of file
+
diff --git a/Source/Examples/OpenTK/GameWindow/MouseCursorSimple.cs b/Source/Examples/OpenTK/GameWindow/MouseCursorSimple.cs
new file mode 100644
index 00000000..00c47b26
--- /dev/null
+++ b/Source/Examples/OpenTK/GameWindow/MouseCursorSimple.cs
@@ -0,0 +1,171 @@
+// This code was written for the OpenTK library and has been released
+// to the Public Domain.
+// It is provided "as is" without express or implied warranty of any kind.
+
+using System;
+using System.Drawing;
+using OpenTK;
+using OpenTK.Graphics.OpenGL;
+using OpenTK.Input;
+
+namespace Examples.Tutorial
+{
+ ///
+ /// Demonstrates the MouseCursor class.
+ ///
+ [Example("MouseCursor Simple", ExampleCategory.OpenTK, "GameWindow", 1, Documentation = "MouseCursorSimple")]
+ public class MouseCursorSimple : GameWindow
+ {
+ public MouseCursorSimple()
+ : base(800, 600)
+ {
+ Keyboard.KeyDown += Keyboard_KeyDown;
+
+ Bitmap bitmap = new Bitmap("Data/Textures/cursor.png");
+
+ var rgba = new byte[bitmap.Width * bitmap.Height * 4];
+ var data = bitmap.LockBits(
+ new Rectangle(0, 0, bitmap.Width, bitmap.Height),
+ System.Drawing.Imaging.ImageLockMode.ReadOnly,
+ System.Drawing.Imaging.PixelFormat.Format32bppArgb);
+
+ for (int y = 0; y < bitmap.Height; ++y)
+ {
+ var offset = new IntPtr(data.Scan0.ToInt64() + (data.Stride * y));
+ var stride = bitmap.Width * 4;
+ System.Runtime.InteropServices.Marshal.Copy(
+ offset, rgba, y * stride, stride);
+ }
+
+ //this.Cursor = new OpenTK.MouseCursor(rgba, bitmap.Width, bitmap.Height, 0, 0);
+ this.Cursor = MouseCursor.Default;
+ }
+
+ #region Keyboard_KeyDown
+
+ ///
+ /// Occurs when a key is pressed.
+ ///
+ /// The KeyboardDevice which generated this event.
+ /// The key that was pressed.
+ void Keyboard_KeyDown(object sender, KeyboardKeyEventArgs e)
+ {
+ if (e.Key == Key.Escape)
+ {
+ this.Exit();
+ }
+
+ if (e.Key == Key.Enter && e.Alt)
+ {
+ if (this.WindowState == WindowState.Fullscreen)
+ this.WindowState = WindowState.Normal;
+ else
+ this.WindowState = WindowState.Fullscreen;
+ }
+
+ if (e.Key == Key.Space)
+ {
+ if (Cursor == MouseCursor.Default)
+ {
+ Cursor = MouseCursor.Empty;
+ }
+ else
+ {
+ Cursor = MouseCursor.Default;
+ }
+ }
+ }
+
+ #endregion
+
+ #region OnLoad
+
+ ///
+ /// Setup OpenGL and load resources here.
+ ///
+ /// Not used.
+ protected override void OnLoad(EventArgs e)
+ {
+ GL.ClearColor(Color.MidnightBlue);
+ }
+
+ #endregion
+
+ #region OnResize
+
+ ///
+ /// Respond to resize events here.
+ ///
+ /// Contains information on the new GameWindow size.
+ /// There is no need to call the base implementation.
+ protected override void OnResize(EventArgs e)
+ {
+ GL.Viewport(0, 0, Width, Height);
+
+ GL.MatrixMode(MatrixMode.Projection);
+ GL.LoadIdentity();
+ GL.Ortho(-1.0, 1.0, -1.0, 1.0, 0.0, 4.0);
+ }
+
+ #endregion
+
+ #region OnUpdateFrame
+
+ ///
+ /// Add your game logic here.
+ ///
+ /// Contains timing information.
+ /// There is no need to call the base implementation.
+ protected override void OnUpdateFrame(FrameEventArgs e)
+ {
+ // Nothing to do!
+ }
+
+ #endregion
+
+ #region OnRenderFrame
+
+ ///
+ /// Add your game rendering code here.
+ ///
+ /// Contains timing information.
+ /// There is no need to call the base implementation.
+ protected override void OnRenderFrame(FrameEventArgs e)
+ {
+ GL.Clear(ClearBufferMask.ColorBufferBit);
+
+ GL.Begin(PrimitiveType.Triangles);
+
+ GL.Color3(Color.MidnightBlue);
+ GL.Vertex2(-1.0f, 1.0f);
+ GL.Color3(Color.SpringGreen);
+ GL.Vertex2(0.0f, -1.0f);
+ GL.Color3(Color.Ivory);
+ GL.Vertex2(1.0f, 1.0f);
+
+ GL.End();
+
+ this.SwapBuffers();
+ }
+
+ #endregion
+
+ #region public static void Main()
+
+ ///
+ /// Entry point of this example.
+ ///
+ [STAThread]
+ public static void Main()
+ {
+ using (MouseCursorSimple example = new MouseCursorSimple())
+ {
+ // Get the title and category of this example using reflection.
+ Utilities.SetWindowTitle(example);
+ example.Run(30.0, 0.0);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Source/Examples/OpenTK/GameWindow/MouseCursorSimple.rtf b/Source/Examples/OpenTK/GameWindow/MouseCursorSimple.rtf
new file mode 100644
index 00000000..18c0bdb6
Binary files /dev/null and b/Source/Examples/OpenTK/GameWindow/MouseCursorSimple.rtf differ
diff --git a/Source/OpenTK/INativeWindow.cs b/Source/OpenTK/INativeWindow.cs
index b1df51fa..5cad3d6b 100644
--- a/Source/OpenTK/INativeWindow.cs
+++ b/Source/OpenTK/INativeWindow.cs
@@ -132,6 +132,12 @@ namespace OpenTK
[Obsolete("Use OpenTK.Input.Mouse/Keybord/Joystick/GamePad instead.")]
OpenTK.Input.IInputDriver InputDriver { get; }
+ ///
+ /// Gets or sets the for this window.
+ ///
+ /// The cursor.
+ MouseCursor Cursor { get; set; }
+
///
/// Gets or sets a value, indicating whether the mouse cursor is visible.
///
diff --git a/Source/OpenTK/Input/KeyboardDevice.cs b/Source/OpenTK/Input/KeyboardDevice.cs
index cc7d5319..9544f420 100644
--- a/Source/OpenTK/Input/KeyboardDevice.cs
+++ b/Source/OpenTK/Input/KeyboardDevice.cs
@@ -211,12 +211,14 @@ namespace OpenTK.Input
{
args.Key = key;
args.ScanCode = scancode;
+ args.Modifiers = GetModifiers();
KeyDown(this, args);
}
else if (!state && KeyUp != null)
{
args.Key = key;
args.ScanCode = scancode;
+ args.Modifiers = GetModifiers();
KeyUp(this, args);
}
}
diff --git a/Source/OpenTK/MouseCursor.cs b/Source/OpenTK/MouseCursor.cs
new file mode 100644
index 00000000..47f8fcc6
--- /dev/null
+++ b/Source/OpenTK/MouseCursor.cs
@@ -0,0 +1,101 @@
+#region License
+//
+// Cursor.cs
+//
+// Author:
+// Stefanos A.
+//
+// Copyright (c) 2006-2014 Stefanos Apostolopoulos
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+#endregion
+
+using System;
+
+namespace OpenTK
+{
+ ///
+ /// Represents a predefined or custom mouse cursor.
+ ///
+ public sealed class MouseCursor : WindowIcon
+ {
+ static readonly MouseCursor default_cursor = new MouseCursor();
+ static readonly MouseCursor empty_cursor = new MouseCursor(
+ new byte[16 * 16 * 4], 16, 16, 0, 0);
+
+ byte[] rgba;
+ int width;
+ int height;
+ int x;
+ int y;
+
+ MouseCursor()
+ {
+ }
+
+ // Todo: make public when byte-order issues are resolved
+ internal MouseCursor(byte[] rgba, int width, int height, int x, int y)
+ {
+ if (rgba == null)
+ throw new ArgumentNullException();
+ if (width < 0 || width > 256 || height < 0 || height > 256)
+ throw new ArgumentOutOfRangeException();
+ if (rgba.Length < width * height * 4)
+ throw new ArgumentOutOfRangeException();
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ throw new ArgumentOutOfRangeException();
+
+ this.rgba = rgba;
+ this.width = width;
+ this.height = height;
+ this.x = x;
+ this.y = y;
+ }
+
+ internal byte[] Rgba { get { return rgba; } }
+ internal int Width { get { return width; } }
+ internal int Height { get { return height; } }
+ internal int X { get { return x; } }
+ internal int Y { get { return y; } }
+
+ ///
+ /// Gets the default mouse cursor for this platform.
+ ///
+ public static MouseCursor Default
+ {
+ get
+ {
+ return default_cursor;
+ }
+ }
+
+ ///
+ /// Gets an empty (invisible) mouse cursor.
+ ///
+ public static MouseCursor Empty
+ {
+ get
+ {
+ return empty_cursor;
+ }
+ }
+ }
+}
+
diff --git a/Source/OpenTK/NativeWindow.cs b/Source/OpenTK/NativeWindow.cs
index 28c51019..795b67ef 100644
--- a/Source/OpenTK/NativeWindow.cs
+++ b/Source/OpenTK/NativeWindow.cs
@@ -259,6 +259,31 @@ namespace OpenTK
#endregion
+ #region Cursor
+
+ ///
+ /// Gets or sets the for this window.
+ ///
+ public MouseCursor Cursor
+ {
+ get
+ {
+ EnsureUndisposed();
+ return implementation.Cursor;
+ }
+ set
+ {
+ EnsureUndisposed();
+ if (value == null)
+ {
+ value = MouseCursor.Empty;
+ }
+ implementation.Cursor = value;
+ }
+ }
+
+ #endregion
+
#region Exists
///
diff --git a/Source/OpenTK/OpenTK.csproj b/Source/OpenTK/OpenTK.csproj
index bfca0b29..4df5b952 100644
--- a/Source/OpenTK/OpenTK.csproj
+++ b/Source/OpenTK/OpenTK.csproj
@@ -1,4 +1,4 @@
-
+
Local
@@ -759,6 +759,7 @@
+
@@ -794,6 +795,7 @@
Code
+
@@ -823,5 +825,4 @@
-
\ No newline at end of file
diff --git a/Source/OpenTK/Platform/MacOS/CarbonGLNative.cs b/Source/OpenTK/Platform/MacOS/CarbonGLNative.cs
index c1334e2e..97063e5d 100644
--- a/Source/OpenTK/Platform/MacOS/CarbonGLNative.cs
+++ b/Source/OpenTK/Platform/MacOS/CarbonGLNative.cs
@@ -83,6 +83,8 @@ namespace OpenTK.Platform.MacOS
float mouse_rel_x;
float mouse_rel_y;
+ MouseCursor cursor = MouseCursor.Default;
+
#endregion
#region AGL Device Hack
@@ -935,6 +937,18 @@ namespace OpenTK.Platform.MacOS
}
}
+ public MouseCursor Cursor
+ {
+ get
+ {
+ return cursor;
+ }
+ set
+ {
+ Debug.Print("[Warning] CarbonGLNative.Cursor property not implemented");
+ }
+ }
+
public bool CursorVisible
{
get { return CG.CursorIsVisible(); }
diff --git a/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs b/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs
index 71f0ded6..e6669390 100644
--- a/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs
+++ b/Source/OpenTK/Platform/MacOS/CocoaNativeWindow.cs
@@ -112,6 +112,7 @@ namespace OpenTK.Platform.MacOS
//static readonly IntPtr selIsInFullScreenMode = Selector.Get("isInFullScreenMode");
//static readonly IntPtr selExitFullScreenModeWithOptions = Selector.Get("exitFullScreenModeWithOptions:");
//static readonly IntPtr selEnterFullScreenModeWithOptions = Selector.Get("enterFullScreenMode:withOptions:");
+ static readonly IntPtr selArrowCursor = Selector.Get("arrowCursor");
static readonly IntPtr NSDefaultRunLoopMode;
static readonly IntPtr NSCursor;
@@ -142,8 +143,10 @@ namespace OpenTK.Platform.MacOS
private int normalLevel;
private bool shouldClose;
private int suppressResize;
- private const float scrollFactor = 120.0f;
+ private bool cursorInsideWindow = true;
+ private MouseCursor selectedCursor = MouseCursor.Default; // user-selected cursor
+ private const float scrollFactor = 120.0f;
private const bool exclusiveFullscreen = false;
public CocoaNativeWindow(int x, int y, int width, int height, string title, GraphicsMode mode, GameWindowFlags options, DisplayDevice device)
@@ -412,11 +415,12 @@ namespace OpenTK.Platform.MacOS
var trackingAreaOwner = Cocoa.SendIntPtr(eventTrackingArea, selOwner);
if (trackingAreaOwner == windowInfo.ViewHandle)
{
- if (!cursorVisible)
+ if (selectedCursor != MouseCursor.Default)
{
- SetCursorVisible(false);
+ SetCursor(selectedCursor);
}
+ cursorInsideWindow = true;
MouseEnter(this, EventArgs.Empty);
}
}
@@ -428,11 +432,12 @@ namespace OpenTK.Platform.MacOS
var trackingAreaOwner = Cocoa.SendIntPtr(eventTrackingArea, selOwner);
if (trackingAreaOwner == windowInfo.ViewHandle)
{
- if (!cursorVisible)
+ if (selectedCursor != MouseCursor.Default)
{
- SetCursorVisible(true);
+ SetCursor(MouseCursor.Default);
}
+ cursorInsideWindow = false;
MouseLeave(this, EventArgs.Empty);
}
}
@@ -885,6 +890,27 @@ namespace OpenTK.Platform.MacOS
}
}
+ public MouseCursor Cursor
+ {
+ get
+ {
+ return selectedCursor;
+ }
+ set
+ {
+ // We only modify the cursor when it is
+ // inside the window and visible.
+ // If it is outside the window or invisible,
+ // we store the selected cursor and change it
+ // in the MouseEnter event.
+ if (CursorVisible && cursorInsideWindow)
+ {
+ SetCursor(value);
+ }
+ selectedCursor = value;
+ }
+ }
+
public bool CursorVisible
{
get { return cursorVisible; }
@@ -966,11 +992,26 @@ namespace OpenTK.Platform.MacOS
private void SetCursorVisible(bool visible)
{
- // Problem: Unlike the PC version, you can move the mouse out of the window.
- // Perhaps use CG.WarpMouseCursorPosition to clamp mouse?
+ Carbon.CG.AssociateMouseAndMouseCursorPosition(visible);
Cocoa.SendVoid(NSCursor, visible ? selUnhide : selHide);
}
+ private void SetCursor(MouseCursor cursor)
+ {
+ if (cursor == MouseCursor.Default)
+ {
+ Cocoa.SendVoid(NSCursor, selUnhide);
+ }
+ else if (cursor == MouseCursor.Empty)
+ {
+ Cocoa.SendVoid(NSCursor, selHide);
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+
private void SetMenuVisible(bool visible)
{
var options = (NSApplicationPresentationOptions)Cocoa.SendInt(NSApplication.Handle, selPresentationOptions);
diff --git a/Source/OpenTK/Platform/SDL2/Sdl2.cs b/Source/OpenTK/Platform/SDL2/Sdl2.cs
index 12199e73..11c8c468 100644
--- a/Source/OpenTK/Platform/SDL2/Sdl2.cs
+++ b/Source/OpenTK/Platform/SDL2/Sdl2.cs
@@ -33,6 +33,8 @@ using System.Runtime.InteropServices;
namespace OpenTK.Platform.SDL2
{
+ using Surface = IntPtr;
+ using Cursor = IntPtr;
partial class SDL
{
@@ -77,6 +79,26 @@ namespace OpenTK.Platform.SDL2
// strlen++;
}
+ #region Cursor
+
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_CreateColorCursor", ExactSpelling = true)]
+ public static extern Cursor CreateColorCursor(Surface surface, int hot_x, int hot_y);
+
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_FreeCursor", ExactSpelling = true)]
+ public static extern void FreeCursor(Cursor cursor);
+
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_GetDefaultCursor", ExactSpelling = true)]
+ public static extern IntPtr GetDefaultCursor();
+
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_SetCursor", ExactSpelling = true)]
+ public static extern void SetCursor(Cursor cursor);
+
+ #endregion
+
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_AddEventWatch", ExactSpelling = true)]
public static extern void AddEventWatch(EventFilter filter, IntPtr userdata);
@@ -220,6 +242,10 @@ namespace OpenTK.Platform.SDL2
[DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_GetModState", ExactSpelling = true)]
public static extern Keymod GetModState();
+ [SuppressUnmanagedCodeSecurity]
+ [DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_GetMouseState", ExactSpelling = true)]
+ public static extern ButtonFlags GetMouseState(out int hx, out int hy);
+
[SuppressUnmanagedCodeSecurity]
[DllImport(lib, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SDL_GetNumDisplayModes", ExactSpelling = true)]
public static extern int GetNumDisplayModes(int displayIndex);
diff --git a/Source/OpenTK/Platform/SDL2/Sdl2NativeWindow.cs b/Source/OpenTK/Platform/SDL2/Sdl2NativeWindow.cs
index e90f8d8b..b6013f65 100644
--- a/Source/OpenTK/Platform/SDL2/Sdl2NativeWindow.cs
+++ b/Source/OpenTK/Platform/SDL2/Sdl2NativeWindow.cs
@@ -58,6 +58,8 @@ namespace OpenTK.Platform.SDL2
WindowState previous_window_state = WindowState.Normal;
WindowBorder window_border = WindowBorder.Resizable;
Icon icon;
+ MouseCursor cursor = MouseCursor.Default;
+ IntPtr sdl_cursor = IntPtr.Zero;
string window_title;
// Used in KeyPress event to decode SDL UTF8 text strings
@@ -458,6 +460,86 @@ namespace OpenTK.Platform.SDL2
public event EventHandler MouseEnter = delegate { };
public event EventHandler MouseLeave = delegate { };
+ public MouseCursor Cursor
+ {
+ get
+ {
+ return cursor;
+ }
+ set
+ {
+ lock (sync)
+ {
+ if (value != MouseCursor.Default)
+ {
+ // Free the previous cursor,
+ // if one has been set.
+ if (sdl_cursor != IntPtr.Zero)
+ {
+ SDL.FreeCursor(sdl_cursor);
+ sdl_cursor = IntPtr.Zero;
+ cursor = MouseCursor.Default;
+ }
+
+ // Set the new cursor
+ if (value == MouseCursor.Default)
+ {
+ // Reset to default cursor
+ SDL.SetCursor(SDL.GetDefaultCursor());
+ }
+ else
+ {
+ // Create and set a new cursor using
+ // the rgba values supplied by the user
+ unsafe
+ {
+ fixed (byte* pixels = value.Rgba)
+ {
+ IntPtr cursor_surface =
+ SDL.CreateRGBSurfaceFrom(
+ new IntPtr(pixels),
+ value.Width,
+ value.Height,
+ 32,
+ value.Width * 4,
+ 0xff000000,
+ 0x00ff0000,
+ 0x0000ff00,
+ 0x000000ff);
+
+ if (cursor_surface == IntPtr.Zero)
+ {
+ Debug.Print("[SDL2] Failed to create cursor surface. Error: {0}",
+ SDL.GetError());
+ return;
+ }
+
+ sdl_cursor = SDL.CreateColorCursor(cursor_surface, value.X, value.Y);
+ if (sdl_cursor == IntPtr.Zero)
+ {
+ Debug.Print("[SDL2] Failed to create cursor. Error: {0}",
+ SDL.GetError());
+ return;
+ }
+
+ if (sdl_cursor != IntPtr.Zero)
+ {
+ SDL.SetCursor(sdl_cursor);
+ cursor = value;
+ }
+
+ if (cursor_surface != IntPtr.Zero)
+ {
+ SDL.FreeSurface(cursor_surface);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
public void Close()
{
lock (sync)
diff --git a/Source/OpenTK/Platform/Windows/API.cs b/Source/OpenTK/Platform/Windows/API.cs
index d73025b6..454d2aa0 100644
--- a/Source/OpenTK/Platform/Windows/API.cs
+++ b/Source/OpenTK/Platform/Windows/API.cs
@@ -856,6 +856,98 @@ namespace OpenTK.Platform.Windows
#endregion
+ #region CreateIconIndirect
+
+ ///
+ /// Creates an icon or cursor from an IconInfo structure.
+ ///
+ ///
+ /// A pointer to an IconInfo structure the function uses to create the
+ /// icon or cursor.
+ ///
+ ///
+ /// If the function succeeds, the return value is a handle to the icon
+ /// or cursor that is created.
+ ///
+ /// If the function fails, the return value is null. To get extended
+ /// error information, call Marshal.GetLastWin32Error.
+ ///
+ ///
+ /// The system copies the bitmaps in the IconInfo structure before
+ /// creating the icon or cursor. Because the system may temporarily
+ /// select the bitmaps in a device context, the hbmMask and hbmColor
+ /// members of the IconInfo structure should not already be selected
+ /// into a device context. The application must continue to manage the
+ /// original bitmaps and delete them when they are no longer necessary.
+ /// When you are finished using the icon, destroy it using the
+ /// DestroyIcon function.
+ ///
+ [DllImport("user32.dll", SetLastError=true)]
+ public static extern HICON CreateIconIndirect(ref IconInfo iconInfo);
+
+ #endregion
+
+ #region GetIconInfo
+
+ ///
+ /// Retrieves information about the specified icon or cursor.
+ ///
+ /// A handle to the icon or cursor.
+ ///
+ /// A pointer to an IconInfo structure. The function fills in the
+ /// structure's members.
+ ///
+ ///
+ /// If the function succeeds, the return value is nonzero and the
+ /// function fills in the members of the specified IconInfo structure.
+ ///
+ /// If the function fails, the return value is zero. To get extended
+ /// error information, call Marshal.GetLastWin32Error.
+ ///
+ ///
+ /// GetIconInfo creates bitmaps for the hbmMask and hbmColor members
+ /// of IconInfo. The calling application must manage these bitmaps and
+ /// delete them when they are no longer necessary.
+ ///
+ [DllImport("user32.dll", SetLastError=true)]
+ public static extern BOOL GetIconInfo(HICON hIcon, out IconInfo pIconInfo);
+
+ #endregion
+
+ #region DestroyIcon
+
+ ///
+ /// Destroys an icon and frees any memory the icon occupied.
+ ///
+ ///
+ /// A handle to the icon to be destroyed. The icon must not be in use.
+ ///
+ ///
+ /// If the function succeeds, the return value is nonzero.
+ ///
+ /// If the function fails, the return value is zero. To get extended
+ /// error information, call Marshal.GetLastWin32Error.
+ ///
+ ///
+ /// It is only necessary to call DestroyIcon for icons and cursors
+ /// created with the following functions: CreateIconFromResourceEx
+ /// (if called without the LR_SHARED flag), CreateIconIndirect, and
+ /// CopyIcon. Do not use this function to destroy a shared icon. A
+ /// shared icon is valid as long as the module from which it was loaded
+ /// remains in memory. The following functions obtain a shared icon.
+ ///
+ /// LoadIcon
+ /// LoadImage (if you use the LR_SHARED flag)
+ /// CopyImage (if you use the LR_COPYRETURNORG flag and the hImage parameter is a shared icon)
+ /// CreateIconFromResource
+ /// CreateIconFromResourceEx (if you use the LR_SHARED flag)
+ ///
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern BOOL DestroyIcon(HICON hIcon);
+
+ #endregion
+
+
[DllImport("user32.dll", SetLastError = true)]
public static extern BOOL SetForegroundWindow(HWND hWnd);
@@ -1043,6 +1135,53 @@ namespace OpenTK.Platform.Windows
uint cbSize, MouseMovePoint* pointsIn,
MouseMovePoint* pointsBufferOut, int nBufPoints, uint resolution);
+ ///
+ /// Sets the cursor shape.
+ ///
+ ///
+ /// A handle to the cursor. The cursor must have been created by the
+ /// CreateCursor function or loaded by the LoadCursor or LoadImage
+ /// function. If this parameter is IntPtr.Zero, the cursor is removed
+ /// from the screen.
+ ///
+ ///
+ /// The return value is the handle to the previous cursor, if there was one.
+ ///
+ /// If there was no previous cursor, the return value is null.
+ ///
+ ///
+ /// The cursor is set only if the new cursor is different from the
+ /// previous cursor; otherwise, the function returns immediately.
+ ///
+ /// The cursor is a shared resource. A window should set the cursor
+ /// shape only when the cursor is in its client area or when the window
+ /// is capturing mouse input. In systems without a mouse, the window
+ /// should restore the previous cursor before the cursor leaves the
+ /// client area or before it relinquishes control to another window.
+ ///
+ /// If your application must set the cursor while it is in a window,
+ /// make sure the class cursor for the specified window's class is set
+ /// to NULL. If the class cursor is not NULL, the system restores the
+ /// class cursor each time the mouse is moved.
+ ///
+ /// The cursor is not shown on the screen if the internal cursor
+ /// display count is less than zero. This occurs if the application
+ /// uses the ShowCursor function to hide the cursor more times than to
+ /// show the cursor.
+ ///
+ [DllImport("user32.dll")]
+ public static extern HCURSOR SetCursor(HCURSOR hCursor);
+
+ ///
+ /// Retrieves a handle to the current cursor.
+ ///
+ ///
+ /// The return value is the handle to the current cursor. If there is
+ /// no cursor, the return value is null.
+ ///
+ [DllImport("user32.dll")]
+ public static extern HCURSOR GetCursor();
+
#region Async input
#region GetCursorPos
@@ -3007,6 +3146,57 @@ namespace OpenTK.Platform.Windows
#endregion
+ #region IconInfo
+
+ /// \internal
+ ///
+ /// Contains information about an icon or a cursor.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ struct IconInfo
+ {
+ ///
+ /// Specifies whether this structure defines an icon or a cursor. A
+ /// value of TRUE specifies an icon; FALSE specifies a cursor
+ ///
+ public bool fIcon;
+
+ ///
+ /// The x-coordinate of a cursor's hot spot. If this structure defines
+ /// an icon, the hot spot is always in the center of the icon, and
+ /// this member is ignored.
+ ///
+ public Int32 xHotspot;
+
+ ///
+ /// The y-coordinate of a cursor's hot spot. If this structure defines
+ /// an icon, the hot spot is always in the center of the icon, and
+ /// this member is ignored.
+ ///
+ public Int32 yHotspot;
+
+ ///
+ /// The icon bitmask bitmap. If this structure defines a black and
+ /// white icon, this bitmask is formatted so that the upper half is
+ /// the icon AND bitmask and the lower half is the icon XOR bitmask.
+ /// Under this condition, the height should be an even multiple of
+ /// two. If this structure defines a color icon, this mask only
+ /// defines the AND bitmask of the icon.
+ ///
+ public IntPtr hbmMask;
+
+ ///
+ /// A handle to the icon color bitmap. This member can be optional if
+ /// this structure defines a black and white icon. The AND bitmask of
+ /// hbmMask is applied with the SRCAND flag to the destination;
+ /// subsequently, the color bitmap is applied (using XOR) to the
+ /// destination by using the SRCINVERT flag.
+ ///
+ public IntPtr hbmColor;
+ }
+
+ #endregion
+
#endregion
#region --- Enums ---
diff --git a/Source/OpenTK/Platform/Windows/WinGLNative.cs b/Source/OpenTK/Platform/Windows/WinGLNative.cs
index c12cae77..7846707a 100644
--- a/Source/OpenTK/Platform/Windows/WinGLNative.cs
+++ b/Source/OpenTK/Platform/Windows/WinGLNative.cs
@@ -101,6 +101,8 @@ namespace OpenTK.Platform.Windows
KeyboardKeyEventArgs key_up = new KeyboardKeyEventArgs();
KeyPressEventArgs key_press = new KeyPressEventArgs((char)0);
+ MouseCursor cursor = MouseCursor.Default;
+ IntPtr curson_handle = IntPtr.Zero;
int cursor_visible_count = 0;
static readonly object SyncRoot = new object();
@@ -386,6 +388,17 @@ namespace OpenTK.Platform.Windows
}
}
+ private IntPtr? HandleSetCursor(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
+ {
+ if (cursor != MouseCursor.Default)
+ {
+ Functions.SetCursor(curson_handle);
+ return new IntPtr(1);
+ }
+
+ return null;
+ }
+
void HandleChar(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
{
char c;
@@ -653,6 +666,8 @@ namespace OpenTK.Platform.Windows
IntPtr WindowProcedure(IntPtr handle, WindowMessage message, IntPtr wParam, IntPtr lParam)
{
+ IntPtr? result = null;
+
switch (message)
{
#region Size / Move / Style events
@@ -686,6 +701,10 @@ namespace OpenTK.Platform.Windows
HandleSize(handle, message, wParam, lParam);
break;
+ case WindowMessage.SETCURSOR:
+ result = HandleSetCursor(handle, message, wParam, lParam);
+ break;
+
#endregion
#region Input events
@@ -772,7 +791,14 @@ namespace OpenTK.Platform.Windows
#endregion
}
- return Functions.DefWindowProc(handle, message, wParam, lParam);
+ if (result.HasValue)
+ {
+ return result.Value;
+ }
+ else
+ {
+ return Functions.DefWindowProc(handle, message, wParam, lParam);
+ }
}
private void EnableMouseTracking()
@@ -1164,7 +1190,78 @@ namespace OpenTK.Platform.Windows
public bool Exists { get { return exists; } }
#endregion
-
+
+ #region Cursor
+
+ public MouseCursor Cursor
+ {
+ get
+ {
+ return cursor;
+ }
+ set
+ {
+ if (value != cursor)
+ {
+ bool destoryOld = cursor != MouseCursor.Default;
+ IntPtr oldCursor = IntPtr.Zero;
+
+ if (value == MouseCursor.Default)
+ {
+ oldCursor = Functions.SetCursor(Functions.LoadCursor(CursorName.Arrow));
+ cursor = value;
+ }
+ else
+ {
+ var stride = value.Width *
+ (Bitmap.GetPixelFormatSize(System.Drawing.Imaging.PixelFormat.Format32bppArgb) / 8);
+
+ Bitmap bmp;
+ unsafe
+ {
+ fixed (byte* pixels = value.Rgba)
+ {
+ bmp = new Bitmap(value.Width, value.Height, stride,
+ System.Drawing.Imaging.PixelFormat.Format32bppArgb,
+ new IntPtr(pixels));
+ }
+ }
+ using (bmp)
+ {
+ var iconInfo = new IconInfo();
+ var bmpIcon = bmp.GetHicon();
+ var success = Functions.GetIconInfo(bmpIcon, out iconInfo);
+
+ if (success)
+ {
+ iconInfo.xHotspot = value.X;
+ iconInfo.yHotspot = value.Y;
+ iconInfo.fIcon = false;
+
+ var icon = Functions.CreateIconIndirect(ref iconInfo);
+
+ if (icon != IntPtr.Zero)
+ {
+ // Currently using a custom cursor so destroy it
+ // once replaced
+ cursor = value;
+ curson_handle = icon;
+ oldCursor = Functions.SetCursor(icon);
+ }
+ }
+ }
+ }
+
+ if (destoryOld && oldCursor != IntPtr.Zero)
+ {
+ Functions.DestroyIcon(oldCursor);
+ }
+ }
+ }
+ }
+
+ #endregion
+
#region CursorVisible
public bool CursorVisible
diff --git a/Source/OpenTK/Platform/X11/API.cs b/Source/OpenTK/Platform/X11/API.cs
index dd7a5d25..370d674a 100644
--- a/Source/OpenTK/Platform/X11/API.cs
+++ b/Source/OpenTK/Platform/X11/API.cs
@@ -38,6 +38,11 @@ namespace OpenTK.Platform.X11
using Display = System.IntPtr;
using XPointer = System.IntPtr;
+ using XcursorBool = System.Int32;
+ using XcursorUInt = System.UInt32;
+ using XcursorDim = System.UInt32;
+ using XcursorPixel = System.UInt32;
+
// Randr and Xrandr
using Bool = System.Boolean;
using XRRScreenConfiguration = System.IntPtr; // opaque datatype
@@ -579,6 +584,47 @@ XF86VidModeGetGammaRampSize(
#region X11 Structures
+ #region Xcursor
+
+ [StructLayout(LayoutKind.Sequential)]
+ unsafe struct XcursorImage
+ {
+ public XcursorUInt version;
+ public XcursorDim size;
+ public XcursorDim width;
+ public XcursorDim height;
+ public XcursorDim xhot;
+ public XcursorDim yhot;
+ public XcursorUInt delay;
+ public XcursorPixel* pixels;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ unsafe struct XcursorImages
+ {
+ public int nimage;
+ public XcursorImage **images;
+ public char *name;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ unsafe struct XcursorCursors
+ {
+ public Display dpy;
+ public int refcount;
+ public int ncursor;
+ public Cursor *cursors;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ unsafe struct XcursorAnimate
+ {
+ public XcursorCursors *cursors;
+ public int sequence;
+ }
+
+ #endregion
+
#region internal class XVisualInfo
[StructLayout(LayoutKind.Sequential)]
@@ -1388,6 +1434,7 @@ XF86VidModeGetGammaRampSize(
internal static partial class Functions
{
internal const string X11Library = "libX11";
+ internal const string XcursorLibrary = "libXcursor.so.1";
#region XCreateWindow
@@ -1434,6 +1481,19 @@ XF86VidModeGetGammaRampSize(
#endregion
+ #region Xcursor
+
+ [DllImport(XcursorLibrary)]
+ internal static unsafe extern XcursorImage* XcursorImageCreate(int width, int height);
+
+ [DllImport(XcursorLibrary)]
+ internal static unsafe extern void XcursorImageDestroy(XcursorImage* image);
+
+ [DllImport(XcursorLibrary)]
+ internal static unsafe extern Cursor XcursorImageLoadCursor(Display dpy, XcursorImage* image);
+
+ #endregion
+
#region XQueryKeymap
/*
diff --git a/Source/OpenTK/Platform/X11/X11GLNative.cs b/Source/OpenTK/Platform/X11/X11GLNative.cs
index 5c32458a..0ff9e9bb 100644
--- a/Source/OpenTK/Platform/X11/X11GLNative.cs
+++ b/Source/OpenTK/Platform/X11/X11GLNative.cs
@@ -115,6 +115,9 @@ namespace OpenTK.Platform.X11
bool isExiting;
bool _decorations_hidden = false;
+
+ MouseCursor cursor = MouseCursor.Default;
+ IntPtr cursorHandle;
bool cursor_visible = true;
int mouse_rel_x, mouse_rel_y;
@@ -1460,6 +1463,48 @@ namespace OpenTK.Platform.X11
#endregion
+ #region Cursor
+
+ public MouseCursor Cursor
+ {
+ get
+ {
+ return cursor;
+ }
+ set
+ {
+ unsafe
+ {
+ using (new XLock(window.Display))
+ {
+ if (value == MouseCursor.Default)
+ {
+ Functions.XUndefineCursor(window.Display, window.Handle);
+ }
+ else
+ {
+ fixed(byte* pixels = value.Rgba)
+ {
+ var xcursorimage = Functions.XcursorImageCreate(value.Width, value.Height);
+ xcursorimage->xhot = (uint)value.X;
+ xcursorimage->yhot = (uint)value.Y;
+ xcursorimage->pixels = (uint*)pixels;
+ xcursorimage->delay = 0;
+ cursorHandle = Functions.XcursorImageLoadCursor(window.Display, xcursorimage);
+ Functions.XDefineCursor(window.Display, window.Handle, cursorHandle);
+ Functions.XcursorImageDestroy(xcursorimage);
+ }
+ }
+ cursor = value;
+ }
+ }
+ }
+ }
+
+ #endregion
+
+ #region CursorVisible
+
public bool CursorVisible
{
get { return cursor_visible; }
@@ -1469,7 +1514,7 @@ namespace OpenTK.Platform.X11
{
using (new XLock(window.Display))
{
- Functions.XUndefineCursor(window.Display, window.Handle);
+ Functions.XDefineCursor(window.Display, window.Handle, cursorHandle);
cursor_visible = true;
}
}
@@ -1486,6 +1531,8 @@ namespace OpenTK.Platform.X11
#endregion
+ #endregion
+
#region --- INativeGLWindow Members ---
#region public IInputDriver InputDriver
@@ -1704,6 +1751,10 @@ namespace OpenTK.Platform.X11
{
using (new XLock(window.Display))
{
+ if(cursorHandle != IntPtr.Zero)
+ {
+ Functions.XFreeCursor(window.Display, cursorHandle);
+ }
Functions.XFreeCursor(window.Display, EmptyCursor);
Functions.XDestroyWindow(window.Display, window.Handle);
}
diff --git a/Source/OpenTK/WindowIcon.cs b/Source/OpenTK/WindowIcon.cs
new file mode 100644
index 00000000..94cfde4f
--- /dev/null
+++ b/Source/OpenTK/WindowIcon.cs
@@ -0,0 +1,45 @@
+#region License
+//
+// WindowIcon.cs
+//
+// Author:
+// Stefanos A.
+//
+// Copyright (c) 2006-2014 Stefanos Apostolopoulos
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+#endregion
+
+using System;
+
+namespace OpenTK
+{
+ ///
+ /// Stores a window icon. A window icon is defined
+ /// as a 2-dimensional buffer of RGBA values.
+ ///
+ public class WindowIcon
+ {
+ internal protected WindowIcon()
+ {
+ }
+ }
+}
+