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() + { + } + } +} +