From c999d45e94c3ba7b8f04ef973c17fe10be7ea9d1 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 2 Dec 2014 13:51:51 +0100 Subject: [PATCH] WIP refactoring Window. To make it x-platform. Currently breaks input. --- Perspex.Base/Perspex.Base.csproj | 1 + Perspex.Base/Platform/IPlatformHandle.cs | 26 +++ Perspex.Controls/Perspex.Controls.csproj | 2 + Perspex.Controls/Platform/IWindowImpl.cs | 33 ++++ Perspex.Controls/Window.cs | 156 +++++++++++++++ Perspex.Input/Perspex.Input.csproj | 1 + Perspex.Input/Raw/RawInputEventArgs.cs | 5 - Perspex.Input/Raw/RawMouseEventArgs.cs | 1 + Perspex.Input/Raw/RawSizeEventArgs.cs | 25 +++ .../Platform/IPlatformRenderInterface.cs | 2 +- Perspex.Themes.Default/DefaultTheme.cs | 1 + .../Perspex.Themes.Default.csproj | 1 + Perspex.Themes.Default/WindowStyle.cs | 43 +++++ .../Perspex.Direct2D1/Direct2D1Platform.cs | 13 +- .../Input/WindowsKeyboardDevice.cs | 1 + .../Perspex.Win32/Input/WindowsMouseDevice.cs | 4 +- Windows/Perspex.Win32/Perspex.Win32.csproj | 2 +- Windows/Perspex.Win32/Win32Platform.cs | 1 + .../{Window.cs => WindowImpl.cs} | 179 +++++------------- 19 files changed, 357 insertions(+), 140 deletions(-) create mode 100644 Perspex.Base/Platform/IPlatformHandle.cs create mode 100644 Perspex.Controls/Platform/IWindowImpl.cs create mode 100644 Perspex.Controls/Window.cs create mode 100644 Perspex.Input/Raw/RawSizeEventArgs.cs create mode 100644 Perspex.Themes.Default/WindowStyle.cs rename Windows/Perspex.Win32/{Window.cs => WindowImpl.cs} (53%) diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj index 5ebb0ac957..cbffed72d5 100644 --- a/Perspex.Base/Perspex.Base.csproj +++ b/Perspex.Base/Perspex.Base.csproj @@ -45,6 +45,7 @@ + diff --git a/Perspex.Base/Platform/IPlatformHandle.cs b/Perspex.Base/Platform/IPlatformHandle.cs new file mode 100644 index 0000000000..f0144dc990 --- /dev/null +++ b/Perspex.Base/Platform/IPlatformHandle.cs @@ -0,0 +1,26 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + using System; + + /// + /// Represents a platform-specific handle. + /// + public interface IPlatformHandle + { + /// + /// Gets the handle. + /// + IntPtr Handle { get; } + + /// + /// Gets an optional string that describes what represents. + /// + string HandleDescriptor { get; } + } +} diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj index 9f67148d6e..221f2731c3 100644 --- a/Perspex.Controls/Perspex.Controls.csproj +++ b/Perspex.Controls/Perspex.Controls.csproj @@ -41,6 +41,7 @@ + @@ -69,6 +70,7 @@ + diff --git a/Perspex.Controls/Platform/IWindowImpl.cs b/Perspex.Controls/Platform/IWindowImpl.cs new file mode 100644 index 0000000000..c877eb3f06 --- /dev/null +++ b/Perspex.Controls/Platform/IWindowImpl.cs @@ -0,0 +1,33 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Platform +{ + using System; + using Perspex.Controls; + using Perspex.Input.Raw; + + public interface IWindowImpl + { + event EventHandler Activated; + + event EventHandler Closed; + + event EventHandler Input; + + event EventHandler Resized; + + Size ClientSize { get; } + + IPlatformHandle Handle { get; } + + void SetTitle(string title); + + void SetOwner(Window window); + + void Show(); + } +} diff --git a/Perspex.Controls/Window.cs b/Perspex.Controls/Window.cs new file mode 100644 index 0000000000..f0e03be3d7 --- /dev/null +++ b/Perspex.Controls/Window.cs @@ -0,0 +1,156 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System; + using System.Reactive.Linq; + using Perspex.Input; + using Perspex.Layout; + using Perspex.Media; + using Perspex.Platform; + using Perspex.Rendering; + using Perspex.Styling; + using Perspex.Threading; + using Splat; + + public class Window : ContentControl, ILayoutRoot, IRenderRoot, ICloseable + { + public static readonly PerspexProperty ClientSizeProperty = + PerspexProperty.Register("ClientSize"); + + public static readonly PerspexProperty TitleProperty = + PerspexProperty.Register("Title", "Window"); + + private IWindowImpl impl; + + private Dispatcher dispatcher; + + private IRenderer renderer; + + private IInputManager inputManager; + + public event EventHandler Activated; + + public event EventHandler Closed; + + static Window() + { + BackgroundProperty.OverrideDefaultValue(typeof(Window), Brushes.White); + } + + public Window() + { + IPlatformRenderInterface renderInterface = Locator.Current.GetService(); + + this.impl = Locator.Current.GetService(); + this.inputManager = Locator.Current.GetService(); + + if (this.impl == null) + { + throw new InvalidOperationException( + "Could not create window implementation: maybe no windowing subsystem was initialized?"); + } + + if (this.inputManager == null) + { + throw new InvalidOperationException( + "Could not create input manager: maybe Application.RegisterServices() wasn't called?"); + } + + this.impl.SetOwner(this); + this.impl.Activated += this.HandleActivated; + this.impl.Closed += this.HandleClosed; + + Size clientSize = this.ClientSize = this.impl.ClientSize; + this.dispatcher = Dispatcher.UIThread; + this.renderer = renderInterface.CreateRenderer(this.impl.Handle, clientSize.Width, clientSize.Height); + + this.LayoutManager = new LayoutManager(this); + this.LayoutManager.LayoutNeeded.Subscribe(_ => this.HandleLayoutNeeded()); + + this.RenderManager = new RenderManager(); + this.RenderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded()); + + this.GetObservable(TitleProperty).Subscribe(s => this.impl.SetTitle(s)); + + IStyler styler = Locator.Current.GetService(); + styler.ApplyStyles(this); + } + + public Size ClientSize + { + get { return this.GetValue(ClientSizeProperty); } + set { this.SetValue(ClientSizeProperty, value); } + } + + public string Title + { + get { return this.GetValue(TitleProperty); } + set { this.SetValue(TitleProperty, value); } + } + + public ILayoutManager LayoutManager + { + get; + private set; + } + + public IRenderManager RenderManager + { + get; + private set; + } + + public void Show() + { + this.impl.Show(); + this.LayoutPass(); + } + + private void HandleActivated(object sender, EventArgs e) + { + if (this.Activated != null) + { + this.Activated(this, EventArgs.Empty); + } + } + + private void HandleClosed(object sender, EventArgs e) + { + if (this.Closed != null) + { + this.Closed(this, EventArgs.Empty); + } + } + + private void HandleLayoutNeeded() + { + this.dispatcher.InvokeAsync(this.LayoutPass, DispatcherPriority.Render); + } + + private void HandleRenderNeeded() + { + this.dispatcher.InvokeAsync(this.RenderVisualTree, DispatcherPriority.Render); + } + + private void LayoutPass() + { + this.LayoutManager.ExecuteLayoutPass(); + this.renderer.Render(this); + this.RenderManager.RenderFinished(); + } + + private void RenderVisualTree() + { + if (!this.LayoutManager.LayoutQueued) + { + this.renderer.Render(this); + this.RenderManager.RenderFinished(); + } + } + } +} diff --git a/Perspex.Input/Perspex.Input.csproj b/Perspex.Input/Perspex.Input.csproj index 02125ec8e6..ec3798df40 100644 --- a/Perspex.Input/Perspex.Input.csproj +++ b/Perspex.Input/Perspex.Input.csproj @@ -66,6 +66,7 @@ + diff --git a/Perspex.Input/Raw/RawInputEventArgs.cs b/Perspex.Input/Raw/RawInputEventArgs.cs index 0e9a08c6fe..6c4266818e 100644 --- a/Perspex.Input/Raw/RawInputEventArgs.cs +++ b/Perspex.Input/Raw/RawInputEventArgs.cs @@ -7,11 +7,6 @@ namespace Perspex.Input.Raw { using System; - using System.Collections.Generic; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using Perspex.Layout; public class RawInputEventArgs : EventArgs { diff --git a/Perspex.Input/Raw/RawMouseEventArgs.cs b/Perspex.Input/Raw/RawMouseEventArgs.cs index 41da9ec88a..c13f41d564 100644 --- a/Perspex.Input/Raw/RawMouseEventArgs.cs +++ b/Perspex.Input/Raw/RawMouseEventArgs.cs @@ -33,6 +33,7 @@ namespace Perspex.Input.Raw this.Type = type; } + //TODO: This should probably be IInputRoot or something. public ILayoutRoot Root { get; private set; } public Point Position { get; private set; } diff --git a/Perspex.Input/Raw/RawSizeEventArgs.cs b/Perspex.Input/Raw/RawSizeEventArgs.cs new file mode 100644 index 0000000000..69f06c6964 --- /dev/null +++ b/Perspex.Input/Raw/RawSizeEventArgs.cs @@ -0,0 +1,25 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2013 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Input.Raw +{ + using System; + + public class RawSizeEventArgs : EventArgs + { + public RawSizeEventArgs(Size size) + { + this.Size = size; + } + + public RawSizeEventArgs(double width, double height) + { + this.Size = new Size(width, height); + } + + public Size Size { get; private set; } + } +} diff --git a/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs b/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs index a5bb3be06c..b5ade8f65e 100644 --- a/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs +++ b/Perspex.SceneGraph/Platform/IPlatformRenderInterface.cs @@ -17,7 +17,7 @@ namespace Perspex.Platform IStreamGeometryImpl CreateStreamGeometry(); - IRenderer CreateRenderer(IntPtr handle, double width, double height); + IRenderer CreateRenderer(IPlatformHandle handle, double width, double height); IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height); diff --git a/Perspex.Themes.Default/DefaultTheme.cs b/Perspex.Themes.Default/DefaultTheme.cs index 0d4e986785..0a184873ab 100644 --- a/Perspex.Themes.Default/DefaultTheme.cs +++ b/Perspex.Themes.Default/DefaultTheme.cs @@ -25,6 +25,7 @@ namespace Perspex.Themes.Default this.Add(new TextBoxStyle()); this.Add(new TreeViewStyle()); this.Add(new TreeViewItemStyle()); + this.Add(new WindowStyle()); } } } diff --git a/Perspex.Themes.Default/Perspex.Themes.Default.csproj b/Perspex.Themes.Default/Perspex.Themes.Default.csproj index ad58628f48..abb1b7a2b0 100644 --- a/Perspex.Themes.Default/Perspex.Themes.Default.csproj +++ b/Perspex.Themes.Default/Perspex.Themes.Default.csproj @@ -66,6 +66,7 @@ + diff --git a/Perspex.Themes.Default/WindowStyle.cs b/Perspex.Themes.Default/WindowStyle.cs new file mode 100644 index 0000000000..479fc5f19c --- /dev/null +++ b/Perspex.Themes.Default/WindowStyle.cs @@ -0,0 +1,43 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Themes.Default +{ + using System.Linq; + using Perspex.Controls; + using Perspex.Controls.Presenters; + using Perspex.Media; + using Perspex.Styling; + + public class WindowStyle : Styles + { + public WindowStyle() + { + this.AddRange(new[] + { + new Style(x => x.OfType()) + { + Setters = new[] + { + new Setter(Window.TemplateProperty, ControlTemplate.Create(this.Template)), + }, + }, + }); + } + + private Control Template(Window control) + { + return new Border + { + [~Border.BackgroundProperty] = control[~Window.BackgroundProperty], + Content = new ContentPresenter + { + [~ContentPresenter.ContentProperty] = control[~Window.ContentProperty], + } + }; + } + } +} diff --git a/Windows/Perspex.Direct2D1/Direct2D1Platform.cs b/Windows/Perspex.Direct2D1/Direct2D1Platform.cs index e89e721207..9199583e6b 100644 --- a/Windows/Perspex.Direct2D1/Direct2D1Platform.cs +++ b/Windows/Perspex.Direct2D1/Direct2D1Platform.cs @@ -44,9 +44,18 @@ namespace Perspex.Direct2D1 return new BitmapImpl(imagingFactory, width, height); } - public IRenderer CreateRenderer(IntPtr handle, double width, double height) + public IRenderer CreateRenderer(IPlatformHandle handle, double width, double height) { - return new Renderer(handle, width, height); + if (handle.HandleDescriptor == "HWND") + { + return new Renderer(handle.Handle, width, height); + } + else + { + throw new NotSupportedException(string.Format( + "Don't know how to create a Direct2D1 renderer from a '{0}' handle", + handle.HandleDescriptor)); + } } public IRenderTargetBitmapImpl CreateRenderTargetBitmap(int width, int height) diff --git a/Windows/Perspex.Win32/Input/WindowsKeyboardDevice.cs b/Windows/Perspex.Win32/Input/WindowsKeyboardDevice.cs index df190b0a9d..c5bee8c6a8 100644 --- a/Windows/Perspex.Win32/Input/WindowsKeyboardDevice.cs +++ b/Windows/Perspex.Win32/Input/WindowsKeyboardDevice.cs @@ -7,6 +7,7 @@ namespace Perspex.Win32.Input { using System.Text; + using Perspex.Controls; using Perspex.Input; using Perspex.Win32.Interop; diff --git a/Windows/Perspex.Win32/Input/WindowsMouseDevice.cs b/Windows/Perspex.Win32/Input/WindowsMouseDevice.cs index ade33bbd01..abf36d40a3 100644 --- a/Windows/Perspex.Win32/Input/WindowsMouseDevice.cs +++ b/Windows/Perspex.Win32/Input/WindowsMouseDevice.cs @@ -19,7 +19,7 @@ namespace Perspex.Win32.Input get { return instance; } } - public Window CurrentWindow + public WindowImpl CurrentWindow { get; set; @@ -35,7 +35,7 @@ namespace Perspex.Win32.Input { UnmanagedMethods.POINT p; UnmanagedMethods.GetCursorPos(out p); - UnmanagedMethods.ScreenToClient(this.CurrentWindow.Handle, ref p); + UnmanagedMethods.ScreenToClient(this.CurrentWindow.Handle.Handle, ref p); return new Point(p.X, p.Y); } } diff --git a/Windows/Perspex.Win32/Perspex.Win32.csproj b/Windows/Perspex.Win32/Perspex.Win32.csproj index fae0a97eee..b21e8ea241 100644 --- a/Windows/Perspex.Win32/Perspex.Win32.csproj +++ b/Windows/Perspex.Win32/Perspex.Win32.csproj @@ -60,7 +60,7 @@ - + diff --git a/Windows/Perspex.Win32/Win32Platform.cs b/Windows/Perspex.Win32/Win32Platform.cs index a0de44a705..0ba4cb8589 100644 --- a/Windows/Perspex.Win32/Win32Platform.cs +++ b/Windows/Perspex.Win32/Win32Platform.cs @@ -24,6 +24,7 @@ namespace Perspex.Win32 public static void Initialize() { var locator = Locator.CurrentMutable; + locator.Register(() => new WindowImpl(), typeof(IWindowImpl)); locator.Register(() => WindowsKeyboardDevice.Instance, typeof(IKeyboardDevice)); locator.Register(() => instance, typeof(IPlatformThreadingInterface)); } diff --git a/Windows/Perspex.Win32/Window.cs b/Windows/Perspex.Win32/WindowImpl.cs similarity index 53% rename from Windows/Perspex.Win32/Window.cs rename to Windows/Perspex.Win32/WindowImpl.cs index f97479b273..aae7099aa4 100644 --- a/Windows/Perspex.Win32/Window.cs +++ b/Windows/Perspex.Win32/WindowImpl.cs @@ -1,5 +1,5 @@ // ----------------------------------------------------------------------- -// +// // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- @@ -24,134 +24,58 @@ namespace Perspex.Win32 using Perspex.Win32.Interop; using Splat; - public class Window : ContentControl, ILayoutRoot, IRenderRoot, ICloseable + public class WindowImpl : IWindowImpl { - public static readonly PerspexProperty TitleProperty = PerspexProperty.Register("Title"); - private UnmanagedMethods.WndProc wndProcDelegate; private string className; - private Dispatcher dispatcher; - - private IRenderer renderer; + private IntPtr hwnd; - private IInputManager inputManager; + private Window owner; - public Window() + public WindowImpl() { - IPlatformRenderInterface factory = Locator.Current.GetService(); - this.CreateWindow(); - Size clientSize = this.ClientSize; - this.dispatcher = Dispatcher.UIThread; - this.LayoutManager = new LayoutManager(this); - this.RenderManager = new RenderManager(); - this.renderer = factory.CreateRenderer(this.Handle, (int)clientSize.Width, (int)clientSize.Height); - this.inputManager = Locator.Current.GetService(); - this.Template = ControlTemplate.Create(this.DefaultTemplate); - - this.LayoutManager.LayoutNeeded.Subscribe(x => - { - this.dispatcher.InvokeAsync( - () => - { - this.LayoutManager.ExecuteLayoutPass(); - this.renderer.Render(this); - this.RenderManager.RenderFinished(); - }, - DispatcherPriority.Render); - }); - - this.GetObservable(TitleProperty).Subscribe(s => UnmanagedMethods.SetWindowText(Handle, s)); - - this.RenderManager.RenderNeeded - .Where(_ => !this.LayoutManager.LayoutQueued) - .Subscribe(x => - { - this.dispatcher.InvokeAsync( - () => - { - if (!this.LayoutManager.LayoutQueued) - { - this.renderer.Render(this); - this.RenderManager.RenderFinished(); - } - }, - DispatcherPriority.Render); - }); } public event EventHandler Activated; public event EventHandler Closed; - public string Title - { - get { return this.GetValue(TitleProperty); } - set { this.SetValue(TitleProperty, value); } - } + public event EventHandler Input; + + public event EventHandler Resized; public Size ClientSize { get { UnmanagedMethods.RECT rect; - UnmanagedMethods.GetClientRect(this.Handle, out rect); + UnmanagedMethods.GetClientRect(this.hwnd, out rect); return new Size(rect.right, rect.bottom); } } - public IntPtr Handle + public IPlatformHandle Handle { get; private set; } - public ILayoutManager LayoutManager + public void SetOwner(Window owner) { - get; - private set; + this.owner = owner; } - public IRenderManager RenderManager + public void SetTitle(string title) { - get; - private set; + UnmanagedMethods.SetWindowText(this.hwnd, title); } public void Show() { - UnmanagedMethods.ShowWindow(this.Handle, 1); - } - - protected override void OnPreviewKeyDown(KeyEventArgs e) - { - if (e.Key == Key.F12) - { - Window window = new Window - { - Content = new DevTools - { - Root = this, - }, - }; - - window.Show(); - } - } - - private Control DefaultTemplate(Window c) - { - Border border = new Border(); - border.Background = new Perspex.Media.SolidColorBrush(0xffffffff); - ContentPresenter contentPresenter = new ContentPresenter(); - contentPresenter.Bind( - ContentPresenter.ContentProperty, - this.GetObservable(Window.ContentProperty), - BindingPriority.Style); - border.Content = contentPresenter; - return border; + UnmanagedMethods.ShowWindow(this.hwnd, 1); } private void CreateWindow() @@ -181,7 +105,7 @@ namespace Perspex.Win32 throw new Win32Exception(); } - this.Handle = UnmanagedMethods.CreateWindowEx( + this.hwnd = UnmanagedMethods.CreateWindowEx( 0, atom, null, @@ -195,36 +119,12 @@ namespace Perspex.Win32 IntPtr.Zero, IntPtr.Zero); - if (this.Handle == IntPtr.Zero) + if (this.hwnd == IntPtr.Zero) { throw new Win32Exception(); } - } - - private void OnActivated() - { - WindowsKeyboardDevice.Instance.WindowActivated(this); - - if (this.Activated != null) - { - this.Activated(this, EventArgs.Empty); - } - } - private void OnResized(int width, int height) - { - this.renderer.Resize(width, height); - this.LayoutManager.ExecuteLayoutPass(); - this.renderer.Render(this); - this.RenderManager.RenderFinished(); - } - - private void OnClosed() - { - if (this.Closed != null) - { - this.Closed(this, EventArgs.Empty); - } + this.Handle = new PlatformHandle(this.hwnd); } [SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")] @@ -237,11 +137,17 @@ namespace Perspex.Win32 switch ((UnmanagedMethods.WindowsMessage)msg) { case UnmanagedMethods.WindowsMessage.WM_ACTIVATE: - this.OnActivated(); + if (this.Activated != null) + { + this.Activated(this, EventArgs.Empty); + } break; case UnmanagedMethods.WindowsMessage.WM_DESTROY: - this.OnClosed(); + if (this.Closed != null) + { + this.Closed(this, EventArgs.Empty); + } break; case UnmanagedMethods.WindowsMessage.WM_KEYDOWN: @@ -255,39 +161,54 @@ namespace Perspex.Win32 case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN: e = new RawMouseEventArgs( - WindowsMouseDevice.Instance, - this, + WindowsMouseDevice.Instance, + this.owner, RawMouseEventType.LeftButtonDown, new Point((uint)lParam & 0xffff, (uint)lParam >> 16)); break; case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP: e = new RawMouseEventArgs( - WindowsMouseDevice.Instance, - this, + WindowsMouseDevice.Instance, + this.owner, RawMouseEventType.LeftButtonUp, new Point((uint)lParam & 0xffff, (uint)lParam >> 16)); break; case UnmanagedMethods.WindowsMessage.WM_MOUSEMOVE: e = new RawMouseEventArgs( - WindowsMouseDevice.Instance, - this, + WindowsMouseDevice.Instance, + this.owner, RawMouseEventType.Move, new Point((uint)lParam & 0xffff, (uint)lParam >> 16)); break; case UnmanagedMethods.WindowsMessage.WM_SIZE: - this.OnResized((int)lParam & 0xffff, (int)lParam >> 16); + if (this.Resized != null) + { + this.Resized(this, new RawSizeEventArgs((int)lParam & 0xffff, (int)lParam >> 16)); + } return IntPtr.Zero; } - if (e != null) + if (e != null && this.Input != null) { - this.inputManager.Process(e); + this.Input(this, e); } return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); } + + private class PlatformHandle : IPlatformHandle + { + public PlatformHandle(IntPtr hwnd) + { + this.Handle = hwnd; + } + + public IntPtr Handle { get; private set; } + + public string HandleDescriptor { get { return "HWND"; } } + } } }