// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Controls { using Perspex.Input; using Perspex.Input.Raw; using Perspex.Layout; using Perspex.Platform; using Perspex.Rendering; using Perspex.Styling; using Perspex.Threading; using Splat; using System; using System.Reactive.Disposables; using System.Reactive.Linq; /// /// Base class for top-level windows. /// /// /// This class acts as a base for top level windows such as and /// . It handles scheduling layout, styling and rendering as well as /// tracking the window and state. /// public abstract class TopLevel : ContentControl, ILayoutRoot, IRenderRoot, ICloseable, IFocusScope { /// /// Defines the property. /// public static readonly PerspexProperty ClientSizeProperty = PerspexProperty.Register("ClientSize"); /// /// Defines the property. /// public static readonly PerspexProperty IsActiveProperty = PerspexProperty.Register("IsActive"); /// /// The dispatcher for the window. /// private Dispatcher dispatcher; /// /// The render manager for the window.s /// private IRenderManager renderManager; /// /// The window renderer. /// private IRenderer renderer; /// /// The input manager for the window. /// private IInputManager inputManager; private bool autoSizing; /// /// Statically initializes the class. /// static TopLevel() { TopLevel.AffectsMeasure(TopLevel.ClientSizeProperty); } /// /// Initializes a new instance of the class. /// /// The platform-specific window implementation. public TopLevel(ITopLevelImpl impl) { IPlatformRenderInterface renderInterface = Locator.Current.GetService(); this.PlatformImpl = impl; this.inputManager = Locator.Current.GetService(); this.LayoutManager = Locator.Current.GetService(); this.renderManager = Locator.Current.GetService(); if (renderInterface == null) { throw new InvalidOperationException( "Could not create an interface to the rendering subsystem: maybe no rendering subsystem was initialized?"); } if (this.PlatformImpl == 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?"); } if (this.LayoutManager == null) { throw new InvalidOperationException( "Could not create layout manager: maybe Application.RegisterServices() wasn't called?"); } if (this.renderManager == null) { throw new InvalidOperationException( "Could not create render manager: maybe Application.RegisterServices() wasn't called?"); } this.PlatformImpl.SetOwner(this); this.PlatformImpl.Activated = this.HandleActivated; this.PlatformImpl.Deactivated = this.HandleDeactivated; this.PlatformImpl.Closed = this.HandleClosed; this.PlatformImpl.Input = this.HandleInput; this.PlatformImpl.Paint = this.HandlePaint; this.PlatformImpl.Resized = this.HandleResized; Size clientSize = this.ClientSize = this.PlatformImpl.ClientSize; this.dispatcher = Dispatcher.UIThread; this.renderer = renderInterface.CreateRenderer(this.PlatformImpl.Handle, clientSize.Width, clientSize.Height); this.LayoutManager.Root = this; this.LayoutManager.LayoutNeeded.Subscribe(_ => this.HandleLayoutNeeded()); this.LayoutManager.LayoutCompleted.Subscribe(_ => this.HandleLayoutCompleted()); this.renderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded()); IStyler styler = Locator.Current.GetService(); styler.ApplyStyles(this); this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => this.PlatformImpl.ClientSize = x); } /// /// Fired when the window is activated. /// public event EventHandler Activated; /// /// Fired when the window is closed. /// public event EventHandler Closed; /// /// Fired when the window is deactivated. /// public event EventHandler Deactivated; /// /// Gets or sets the client size of the window. /// public Size ClientSize { get { return this.GetValue(ClientSizeProperty); } private set { this.SetValue(ClientSizeProperty, value); } } /// /// Gets a value that indicates whether the window is active. /// public bool IsActive { get { return this.GetValue(IsActiveProperty); } private set { this.SetValue(IsActiveProperty, value); } } /// /// Gets the layout manager for the window. /// public ILayoutManager LayoutManager { get; private set; } /// /// Gets the platform-specific window implementation. /// public ITopLevelImpl PlatformImpl { get; private set; } /// /// Gets the window renderer. /// IRenderer IRenderRoot.Renderer { get { return this.renderer; } } /// /// Gets the window render manager. /// IRenderManager IRenderRoot.RenderManager { get { return this.renderManager; } } /// /// Translates a point from window coordinates into screen coordinates. /// /// The point. /// The point in screen coordinates. Point IRenderRoot.TranslatePointToScreen(Point p) { return this.PlatformImpl.PointToScreen(p); } /// /// Activates the window. /// public void Activate() { this.PlatformImpl.Activate(); } protected IDisposable BeginAutoSizing() { this.autoSizing = true; return Disposable.Create(() => this.autoSizing = false); } /// /// Carries out the arrange pass of the window. /// /// The final window size. /// The parameter unchanged. protected override Size ArrangeOverride(Size finalSize) { using (this.BeginAutoSizing()) { this.PlatformImpl.ClientSize = finalSize; } return base.ArrangeOverride(finalSize); } /// /// Handles an activated notification from . /// private void HandleActivated() { if (this.Activated != null) { this.Activated(this, EventArgs.Empty); } FocusManager.Instance.SetFocusScope(this); this.IsActive = true; } /// /// Handles a closed notification from . /// private void HandleClosed() { if (this.Closed != null) { this.Closed(this, EventArgs.Empty); } } /// /// Handles a deactivated notification from . /// private void HandleDeactivated() { this.IsActive = false; if (this.Deactivated != null) { this.Deactivated(this, EventArgs.Empty); } } /// /// Handles input from . /// private void HandleInput(RawInputEventArgs e) { this.inputManager.Process(e); } /// /// Handles a layout request from . /// private void HandleLayoutNeeded() { this.dispatcher.InvokeAsync(LayoutManager.ExecuteLayoutPass, DispatcherPriority.Render); } /// /// Handles a layout completion request from . /// private void HandleLayoutCompleted() { this.renderManager.InvalidateRender(this); } /// /// Handles a render request from . /// private void HandleRenderNeeded() { this.dispatcher.InvokeAsync( () => this.PlatformImpl.Invalidate(new Rect(this.ClientSize)), DispatcherPriority.Render); } /// /// Handles a paint request from . /// private void HandlePaint(Rect rect, IPlatformHandle handle) { this.renderer.Render(this, handle); this.renderManager.RenderFinished(); } /// /// Handles a resize notification from . /// private void HandleResized(Size clientSize) { if (!this.autoSizing) { this.Width = clientSize.Width; this.Height = clientSize.Height; } this.ClientSize = clientSize; this.renderer.Resize((int)clientSize.Width, (int)clientSize.Height); this.LayoutManager.ExecuteLayoutPass(); this.PlatformImpl.Invalidate(new Rect(clientSize)); } } }