Browse Source

Implemented cursors. Ceterum censeo StyleCopum esse delendum.

Nikita Tsukanov 10 năm trước cách đây
mục cha
commit
7f72c7763b

+ 77 - 0
src/Gtk/Perspex.Gtk/CursorFactory.cs

@@ -0,0 +1,77 @@
+namespace Perspex.Gtk
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Text;
+    using System.Threading.Tasks;
+    using Gdk;
+    using Gtk = global::Gtk;
+    using Perspex.Input;
+    using Perspex.Platform;
+    class CursorFactory : IStandardCursorFactory
+    {
+        public static CursorFactory Instance { get; } = new CursorFactory();
+
+        private CursorFactory()
+        {
+        }
+
+        private static readonly Dictionary<StandardCursorType, object> CursorTypeMapping = new Dictionary
+            <StandardCursorType, object>
+        {
+            { StandardCursorType.AppStarting, CursorType.Watch },
+            { StandardCursorType.Arrow, CursorType.LeftPtr },
+            { StandardCursorType.Cross, CursorType.Cross },
+            { StandardCursorType.Hand, CursorType.Hand1 },
+            { StandardCursorType.Ibeam, CursorType.Xterm },
+            { StandardCursorType.No, Gtk.Stock.Cancel},
+            { StandardCursorType.SizeAll, CursorType.Sizing },
+            //{ StandardCursorType.SizeNorthEastSouthWest, 32643 },
+            { StandardCursorType.SizeNorthSouth, CursorType.SbVDoubleArrow},
+            //{ StandardCursorType.SizeNorthWestSouthEast, 32642 },
+            { StandardCursorType.SizeWestEast, CursorType.SbHDoubleArrow },
+            { StandardCursorType.UpArrow, CursorType.BasedArrowUp },
+            { StandardCursorType.Wait, CursorType.Watch },
+            { StandardCursorType.Help, Gtk.Stock.Help }
+        };
+
+        private static readonly Dictionary<StandardCursorType, IPlatformHandle> Cache =
+            new Dictionary<StandardCursorType, IPlatformHandle>();
+
+        Gdk.Cursor GetCursor(object desc)
+        {
+            Gdk.Cursor rv;
+            var name = desc as string;
+            if (name != null)
+            {
+                var theme = Gtk.IconTheme.Default;
+                var icon = theme.LoadIcon(name, 32, default(Gtk.IconLookupFlags));
+                rv = icon == null ? new Gdk.Cursor(CursorType.XCursor) : new Gdk.Cursor(Gdk.Display.Default, icon, 0, 0);
+            }
+            else
+            {
+                rv = new Gdk.Cursor((CursorType) desc);
+            }
+
+            rv.Owned = false;
+            return rv;
+
+        }
+
+        public IPlatformHandle GetCursor(StandardCursorType cursorType)
+        {
+            IPlatformHandle rv;
+            if (!Cache.TryGetValue(cursorType, out rv))
+            {
+                Cache[cursorType] =
+                    rv =
+                        new PlatformHandle(
+                            GetCursor(CursorTypeMapping[cursorType]).Handle,
+                            "GTKCURSOR");
+            }
+
+            return rv;
+        }
+    }
+}

+ 1 - 0
src/Gtk/Perspex.Gtk/GtkPlatform.cs

@@ -43,6 +43,7 @@ namespace Perspex.Gtk
             locator.Register(() => new WindowImpl(), typeof(IWindowImpl));
             locator.Register(() => new WindowImpl(), typeof(IWindowImpl));
             locator.Register(() => new PopupImpl(), typeof(IPopupImpl));
             locator.Register(() => new PopupImpl(), typeof(IPopupImpl));
             locator.Register(() => new ClipboardImpl(), typeof (IClipboard));
             locator.Register(() => new ClipboardImpl(), typeof (IClipboard));
+            locator.Register(() => CursorFactory.Instance, typeof(IStandardCursorFactory));
             locator.Register(() => GtkKeyboardDevice.Instance, typeof(IKeyboardDevice));
             locator.Register(() => GtkKeyboardDevice.Instance, typeof(IKeyboardDevice));
             locator.Register(() => instance, typeof(IPlatformSettings));
             locator.Register(() => instance, typeof(IPlatformSettings));
             locator.Register(() => instance, typeof(IPlatformThreadingInterface));
             locator.Register(() => instance, typeof(IPlatformThreadingInterface));

+ 1 - 0
src/Gtk/Perspex.Gtk/Perspex.Gtk.csproj

@@ -60,6 +60,7 @@
   <ItemGroup>
   <ItemGroup>
     <Compile Include="AssetLoader.cs" />
     <Compile Include="AssetLoader.cs" />
     <Compile Include="ClipboardImpl.cs" />
     <Compile Include="ClipboardImpl.cs" />
+    <Compile Include="CursorFactory.cs" />
     <Compile Include="GtkExtensions.cs" />
     <Compile Include="GtkExtensions.cs" />
     <Compile Include="PopupImpl.cs" />
     <Compile Include="PopupImpl.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />

+ 8 - 0
src/Gtk/Perspex.Gtk/WindowImpl.cs

@@ -29,6 +29,8 @@ namespace Perspex.Gtk
 
 
         private uint lastKeyEventTimestamp;
         private uint lastKeyEventTimestamp;
 
 
+        private static readonly Gdk.Cursor DefaultCursor = new Gdk.Cursor(CursorType.LeftPtr);
+
         public WindowImpl()
         public WindowImpl()
             : base(Gtk.WindowType.Toplevel)
             : base(Gtk.WindowType.Toplevel)
         {
         {
@@ -105,6 +107,12 @@ namespace Perspex.Gtk
             this.Title = title;
             this.Title = title;
         }
         }
 
 
+
+        public void SetCursor(IPlatformHandle cursor)
+        {
+            GdkWindow.Cursor = cursor != null ? new Gdk.Cursor(cursor.Handle) : DefaultCursor;
+        }
+
         public IDisposable ShowDialog()
         public IDisposable ShowDialog()
         {
         {
             this.Modal = true;
             this.Modal = true;

+ 6 - 0
src/Perspex.Controls/Platform/ITopLevelImpl.cs

@@ -36,5 +36,11 @@ namespace Perspex.Platform
         void SetOwner(TopLevel owner);
         void SetOwner(TopLevel owner);
 
 
         Point PointToScreen(Point point);
         Point PointToScreen(Point point);
+
+        /// <summary>
+        /// Sets the cursor associated with the window.
+        /// </summary>
+        /// <param name="cursor">The cursor. Use null for default cursor</param>
+        void SetCursor(IPlatformHandle cursor);
     }
     }
 }
 }

+ 1 - 0
src/Perspex.Controls/TextBox.cs

@@ -115,6 +115,7 @@ namespace Perspex.Controls
         protected override void OnTemplateApplied()
         protected override void OnTemplateApplied()
         {
         {
             this.presenter = this.GetTemplateChild<TextPresenter>("textPresenter");
             this.presenter = this.GetTemplateChild<TextPresenter>("textPresenter");
+            this.presenter.Cursor = new Cursor(StandardCursorType.Ibeam);
         }
         }
 
 
         protected override void OnGotFocus(GotFocusEventArgs e)
         protected override void OnGotFocus(GotFocusEventArgs e)

+ 4 - 0
src/Perspex.Controls/TopLevel.cs

@@ -162,6 +162,10 @@ namespace Perspex.Controls
             styler?.ApplyStyles(this);
             styler?.ApplyStyles(this);
 
 
             this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => this.PlatformImpl.ClientSize = x);
             this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => this.PlatformImpl.ClientSize = x);
+            this.GetObservable(TopLevel.PointerOverElementProperty)
+                .Select(
+                    x => (x as InputElement)?.GetObservable(InputElement.CursorProperty) ?? Observable.Empty<Cursor>())
+                .Switch().Subscribe(cursor => this.PlatformImpl.SetCursor(cursor?.PlatformCursor));
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 58 - 0
src/Perspex.Input/Cursors.cs

@@ -0,0 +1,58 @@
+namespace Perspex.Input
+{
+    using Splat;
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Text;
+    using System.Threading.Tasks;
+    using Perspex.Platform;
+
+    /*
+    =========================================================================================
+        NOTE: Cursors are NOT disposable and are cached in platform implementation.
+        To support loading custom cursors some measures about that should be taken beforehand
+    =========================================================================================
+    */
+
+    public enum StandardCursorType
+    {
+        Arrow,
+        Ibeam,
+        Wait,
+        Cross,
+        UpArrow,
+        SizeWestEast,
+        SizeNorthSouth,
+        SizeAll,
+        No,
+        Hand,
+        AppStarting,
+        Help
+
+        // Not available in GTK directly, see http://www.pixelbeat.org/programming/x_cursors/ 
+        // We might enable them later, preferably, by loading pixmax direclty from theme with fallback image
+        // SizeNorthWestSouthEast,
+        // SizeNorthEastSouthWest,
+
+    }
+
+    public class Cursor
+    {
+        public static Cursor Default = new Cursor(StandardCursorType.Arrow);
+
+        internal Cursor(IPlatformHandle platformCursor)
+        {
+            this.PlatformCursor = platformCursor;
+        }
+
+        public Cursor(StandardCursorType cursorType)
+            : this(
+                ((IStandardCursorFactory) Locator.Current.GetService(typeof(IStandardCursorFactory))).GetCursor(
+                    cursorType))
+        {
+        }
+
+        public IPlatformHandle PlatformCursor { get; }
+    }
+}

+ 7 - 0
src/Perspex.Input/IInputElement.cs

@@ -4,6 +4,8 @@
 // </copyright>
 // </copyright>
 // -----------------------------------------------------------------------
 // -----------------------------------------------------------------------
 
 
+using System.Diagnostics.Contracts;
+
 namespace Perspex.Input
 namespace Perspex.Input
 {
 {
     using System;
     using System;
@@ -79,6 +81,11 @@ namespace Perspex.Input
         /// </summary>
         /// </summary>
         bool IsEnabled { get; }
         bool IsEnabled { get; }
 
 
+        /// <summary>
+        /// Gets or sets assicitated mouse cursor.
+        /// </summary>
+        Cursor Cursor { get; }
+
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether the control is effectively enabled for user interaction.
         /// Gets a value indicating whether the control is effectively enabled for user interaction.
         /// </summary>
         /// </summary>

+ 15 - 0
src/Perspex.Input/InputElement.cs

@@ -35,6 +35,12 @@ namespace Perspex.Input
         public static readonly PerspexProperty<bool> IsEnabledCoreProperty =
         public static readonly PerspexProperty<bool> IsEnabledCoreProperty =
             PerspexProperty.Register<InputElement, bool>("IsEnabledCore", true);
             PerspexProperty.Register<InputElement, bool>("IsEnabledCore", true);
 
 
+        /// <summary>
+        /// Gets or sets associated mouse cursor.
+        /// </summary>
+        public static readonly PerspexProperty<Cursor> CursorProperty =
+            PerspexProperty.Register<InputElement, Cursor>("Cursor", null, true);
+
         /// <summary>
         /// <summary>
         /// Defines the <see cref="IsFocused"/> property.
         /// Defines the <see cref="IsFocused"/> property.
         /// </summary>
         /// </summary>
@@ -270,6 +276,15 @@ namespace Perspex.Input
             set { this.SetValue(IsEnabledProperty, value); }
             set { this.SetValue(IsEnabledProperty, value); }
         }
         }
 
 
+        /// <summary>
+        /// Gets or sets associated mouse cursor.
+        /// </summary>
+        public Cursor Cursor
+        {
+            get { return this.GetValue(CursorProperty); }
+            set { this.SetValue(CursorProperty, value); }
+        }
+
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether the control is focused.
         /// Gets or sets a value indicating whether the control is focused.
         /// </summary>
         /// </summary>

+ 2 - 0
src/Perspex.Input/Perspex.Input.csproj

@@ -64,9 +64,11 @@
     <Compile Include="..\Shared\SharedAssemblyInfo.cs">
     <Compile Include="..\Shared\SharedAssemblyInfo.cs">
       <Link>Properties\SharedAssemblyInfo.cs</Link>
       <Link>Properties\SharedAssemblyInfo.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="Cursors.cs" />
     <Compile Include="GlobalSuppressions.cs" />
     <Compile Include="GlobalSuppressions.cs" />
     <Compile Include="AccessKeyHandler.cs" />
     <Compile Include="AccessKeyHandler.cs" />
     <Compile Include="Platform\IClipboard.cs" />
     <Compile Include="Platform\IClipboard.cs" />
+    <Compile Include="Platform\IStandardCursorFactory.cs" />
     <Compile Include="Raw\RawTextInputEventArgs.cs" />
     <Compile Include="Raw\RawTextInputEventArgs.cs" />
     <Compile Include="NavigationMethod.cs" />
     <Compile Include="NavigationMethod.cs" />
     <Compile Include="IInputRoot.cs" />
     <Compile Include="IInputRoot.cs" />

+ 14 - 0
src/Perspex.Input/Platform/IStandardCursorFactory.cs

@@ -0,0 +1,14 @@
+namespace Perspex.Platform
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Text;
+    using System.Threading.Tasks;
+    using Perspex.Input;
+
+    public interface IStandardCursorFactory
+    {
+        IPlatformHandle GetCursor(StandardCursorType cursorType);
+    }
+}

+ 61 - 0
src/Windows/Perspex.Win32/CursorFactory.cs

@@ -0,0 +1,61 @@
+using Perspex.Win32.Interop;
+
+namespace Perspex.Win32
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Text;
+    using System.Threading.Tasks;
+    using Perspex.Input;
+    using Perspex.Platform;
+
+    class CursorFactory : IStandardCursorFactory
+    {
+        public static CursorFactory Instance { get; } = new CursorFactory();
+
+        private CursorFactory()
+        {
+        }
+
+        private static readonly Dictionary<StandardCursorType, int> CursorTypeMapping = new Dictionary
+            <StandardCursorType, int>
+        {
+
+            { StandardCursorType.AppStarting, 32650 },
+            { StandardCursorType.Arrow, 32512 },
+            { StandardCursorType.Cross, 32515 },
+            { StandardCursorType.Hand, 32649 },
+            { StandardCursorType.Help, 32651 },
+            { StandardCursorType.Ibeam, 32513 },
+            { StandardCursorType.No, 32648 },
+            { StandardCursorType.SizeAll, 32646 },
+
+            // { StandardCursorType.SizeNorthEastSouthWest, 32643 },
+            { StandardCursorType.SizeNorthSouth, 32645 },
+
+            // { StandardCursorType.SizeNorthWestSouthEast, 32642 },
+            { StandardCursorType.SizeWestEast, 32644 },
+            { StandardCursorType.UpArrow, 32516 },
+            { StandardCursorType.Wait, 32514 }
+        };
+
+        private static readonly Dictionary<StandardCursorType, IPlatformHandle> Cache =
+            new Dictionary<StandardCursorType, IPlatformHandle>();
+
+        public IPlatformHandle GetCursor(StandardCursorType cursorType)
+        {
+            IPlatformHandle rv;
+            if (!Cache.TryGetValue(cursorType, out rv))
+            {
+                Cache[cursorType] =
+                    rv =
+                        new PlatformHandle(
+                            UnmanagedMethods.LoadCursor(IntPtr.Zero, new IntPtr(CursorTypeMapping[cursorType])),
+                            PlatformConstants.CursorHandleType);
+            }
+
+            return rv;
+        }
+    }
+}

+ 24 - 1
src/Windows/Perspex.Win32/Interop/UnmanagedMethods.cs

@@ -572,7 +572,9 @@ namespace Perspex.Win32.Interop
         public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);
         public static extern bool KillTimer(IntPtr hWnd, IntPtr uIDEvent);
 
 
         [DllImport("user32.dll")]
         [DllImport("user32.dll")]
-        public static extern IntPtr LoadCursor(IntPtr hInstance, int lpCursorName);
+        public static extern IntPtr LoadCursor(IntPtr hInstance, IntPtr lpCursorName);
+
+
 
 
         [DllImport("user32.dll")]
         [DllImport("user32.dll")]
         public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
         public static extern bool PeekMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg);
@@ -627,6 +629,27 @@ namespace Perspex.Win32.Interop
         [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
         [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
         public static extern bool SetWindowText(IntPtr hwnd, string lpString);
         public static extern bool SetWindowText(IntPtr hwnd, string lpString);
 
 
+        public enum ClassLongIndex : int
+        {
+            GCL_HCURSOR = -12,
+            GCL_HICON = -14
+        }
+
+        [DllImport("user32.dll", EntryPoint = "SetClassLongPtr")]
+        private static extern IntPtr SetClassLong64(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong);
+
+        [DllImport("user32.dll", EntryPoint = "SetClassLong")]
+        private static extern IntPtr SetClassLong32(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong);
+
+        public static IntPtr SetClassLong(IntPtr hWnd, ClassLongIndex nIndex, IntPtr dwNewLong)
+        {
+            if (IntPtr.Size == 4)
+            {
+                return SetClassLong32(hWnd, nIndex, dwNewLong);
+            }
+
+            return SetClassLong64(hWnd, nIndex, dwNewLong);
+        }
 
 
         [DllImport("user32.dll", SetLastError = true)]
         [DllImport("user32.dll", SetLastError = true)]
         public static extern bool OpenClipboard(IntPtr hWndOwner);
         public static extern bool OpenClipboard(IntPtr hWndOwner);

+ 2 - 0
src/Windows/Perspex.Win32/Perspex.Win32.csproj

@@ -67,10 +67,12 @@
     </Compile>
     </Compile>
     <Compile Include="AssetLoader.cs" />
     <Compile Include="AssetLoader.cs" />
     <Compile Include="ClipboardImpl.cs" />
     <Compile Include="ClipboardImpl.cs" />
+    <Compile Include="CursorFactory.cs" />
     <Compile Include="Input\KeyInterop.cs" />
     <Compile Include="Input\KeyInterop.cs" />
     <Compile Include="Input\WindowsKeyboardDevice.cs" />
     <Compile Include="Input\WindowsKeyboardDevice.cs" />
     <Compile Include="Input\WindowsMouseDevice.cs" />
     <Compile Include="Input\WindowsMouseDevice.cs" />
     <Compile Include="Interop\UnmanagedMethods.cs" />
     <Compile Include="Interop\UnmanagedMethods.cs" />
+    <Compile Include="PlatformConstants.cs" />
     <Compile Include="PopupImpl.cs" />
     <Compile Include="PopupImpl.cs" />
     <Compile Include="WindowImpl.cs" />
     <Compile Include="WindowImpl.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />

+ 14 - 0
src/Windows/Perspex.Win32/PlatformConstants.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Perspex.Win32
+{
+    public static class PlatformConstants
+    {
+        public const string WindowHandleType = "HWND";
+        public const string CursorHandleType = "HCURSOR";
+    }
+}

+ 1 - 0
src/Windows/Perspex.Win32/Win32Platform.cs

@@ -57,6 +57,7 @@ namespace Perspex.Win32
             locator.Register(() => new ClipboardImpl(), typeof(IClipboard));
             locator.Register(() => new ClipboardImpl(), typeof(IClipboard));
             locator.Register(() => WindowsKeyboardDevice.Instance, typeof(IKeyboardDevice));
             locator.Register(() => WindowsKeyboardDevice.Instance, typeof(IKeyboardDevice));
             locator.Register(() => WindowsMouseDevice.Instance, typeof(IMouseDevice));
             locator.Register(() => WindowsMouseDevice.Instance, typeof(IMouseDevice));
+            locator.Register(() => CursorFactory.Instance, typeof(IStandardCursorFactory));
             locator.Register(() => instance, typeof(IPlatformSettings));
             locator.Register(() => instance, typeof(IPlatformSettings));
             locator.Register(() => instance, typeof(IPlatformThreadingInterface));
             locator.Register(() => instance, typeof(IPlatformThreadingInterface));
             locator.RegisterConstant(new AssetLoader(), typeof(IAssetLoader));
             locator.RegisterConstant(new AssetLoader(), typeof(IAssetLoader));

+ 12 - 2
src/Windows/Perspex.Win32/WindowImpl.cs

@@ -26,6 +26,9 @@ namespace Perspex.Win32
     {
     {
         private static List<WindowImpl> instances = new List<WindowImpl>();
         private static List<WindowImpl> instances = new List<WindowImpl>();
 
 
+        private static readonly IntPtr DefaultCursor = UnmanagedMethods.LoadCursor(
+            IntPtr.Zero, new IntPtr((int) UnmanagedMethods.Cursor.IDC_ARROW));
+
         private UnmanagedMethods.WndProc wndProcDelegate;
         private UnmanagedMethods.WndProc wndProcDelegate;
 
 
         private string className;
         private string className;
@@ -185,6 +188,12 @@ namespace Perspex.Win32
             });
             });
         }
         }
 
 
+        public void SetCursor(IPlatformHandle cursor)
+        {
+            UnmanagedMethods.SetClassLong(this.hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR,
+                cursor?.Handle ?? DefaultCursor);
+        }
+
         protected virtual IntPtr CreateWindowOverride(ushort atom)
         protected virtual IntPtr CreateWindowOverride(ushort atom)
         {
         {
             return UnmanagedMethods.CreateWindowEx(
             return UnmanagedMethods.CreateWindowEx(
@@ -244,6 +253,7 @@ namespace Perspex.Win32
                 case UnmanagedMethods.WindowsMessage.WM_DESTROY:
                 case UnmanagedMethods.WindowsMessage.WM_DESTROY:
                     if (this.Closed != null)
                     if (this.Closed != null)
                     {
                     {
+                        UnmanagedMethods.UnregisterClass(this.className, Marshal.GetHINSTANCE(this.GetType().Module));
                         this.Closed();
                         this.Closed();
                     }
                     }
 
 
@@ -384,7 +394,7 @@ namespace Perspex.Win32
                 style = 0,
                 style = 0,
                 lpfnWndProc = this.wndProcDelegate,
                 lpfnWndProc = this.wndProcDelegate,
                 hInstance = Marshal.GetHINSTANCE(this.GetType().Module),
                 hInstance = Marshal.GetHINSTANCE(this.GetType().Module),
-                hCursor = UnmanagedMethods.LoadCursor(IntPtr.Zero, (int)UnmanagedMethods.Cursor.IDC_ARROW),
+                hCursor = DefaultCursor,
                 hbrBackground = (IntPtr)5,
                 hbrBackground = (IntPtr)5,
                 lpszClassName = this.className,
                 lpszClassName = this.className,
             };
             };
@@ -403,7 +413,7 @@ namespace Perspex.Win32
                 throw new Win32Exception();
                 throw new Win32Exception();
             }
             }
 
 
-            this.Handle = new PlatformHandle(this.hwnd, "HWND");
+            this.Handle = new PlatformHandle(this.hwnd, PlatformConstants.WindowHandleType);
         }
         }
 
 
         private Point ScreenToClient(uint x, uint y)
         private Point ScreenToClient(uint x, uint y)