// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Controls { using System; using System.Reactive.Disposables; using System.Reactive.Linq; using Perspex.Input; using Perspex.Input.Raw; using Perspex.Layout; using Perspex.Platform; using Perspex.Rendering; using Perspex.Styling; using Perspex.Threading; using Perspex.Controls.Primitives; using Perspex.Interactivity; using Splat; /// /// 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, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable { /// /// 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; /// /// The access key handler for the window. /// private IAccessKeyHandler accessKeyHandler; /// /// The access keyboard navigation handler for the window. /// private IKeyboardNavigationHandler keyboardNavigationHandler; /// /// Whether an auto-size operation is in progress. /// private bool autoSizing; /// /// Initializes static members of the class. /// static TopLevel() { TopLevel.AffectsMeasure(TopLevel.ClientSizeProperty); } /// /// Initializes a new instance of the class. /// /// The platform-specific window implementation. public TopLevel(ITopLevelImpl impl) : this(impl, Locator.Current) { } /// /// Initializes a new instance of the class. /// /// The platform-specific window implementation. /// /// The dependency resolver to use. If null the default dependency resolver will be used. /// public TopLevel(ITopLevelImpl impl, IDependencyResolver dependencyResolver) { if (impl == null) { throw new InvalidOperationException( "Could not create window implementation: maybe no windowing subsystem was initialized?"); } this.PlatformImpl = impl; dependencyResolver = dependencyResolver ?? Locator.Current; var renderInterface = TryGetService(dependencyResolver); var styler = TryGetService(dependencyResolver); this.accessKeyHandler = TryGetService(dependencyResolver); this.inputManager = TryGetService(dependencyResolver); this.keyboardNavigationHandler = TryGetService(dependencyResolver); this.LayoutManager = TryGetService(dependencyResolver); this.renderManager = TryGetService(dependencyResolver); 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; if (renderInterface != null) { this.renderer = renderInterface.CreateRenderer(this.PlatformImpl.Handle, clientSize.Width, clientSize.Height); } if (this.LayoutManager != null) { this.LayoutManager.Root = this; this.LayoutManager.LayoutNeeded.Subscribe(_ => this.HandleLayoutNeeded()); this.LayoutManager.LayoutCompleted.Subscribe(_ => this.HandleLayoutCompleted()); } if (this.renderManager != null) { this.renderManager.RenderNeeded.Subscribe(_ => this.HandleRenderNeeded()); } if (this.keyboardNavigationHandler != null) { this.keyboardNavigationHandler.SetOwner(this); } if (this.accessKeyHandler != null) { this.accessKeyHandler.SetOwner(this); } styler?.ApplyStyles(this); this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => this.PlatformImpl.ClientSize = x); } private static T TryGetService(IDependencyResolver resolver) where T : class { var result = resolver.GetService(); if (result == null) { System.Diagnostics.Debug.WriteLineIf( result == null, $"Could not create {typeof(T).Name} : maybe Application.RegisterServices() wasn't called?"); } return result; } /// /// 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; } /// /// Gets the window renderer. /// IRenderer IRenderRoot.Renderer { get { return this.renderer; } } /// /// Gets the window render manager. /// IRenderManager IRenderRoot.RenderManager { get { return this.renderManager; } } /// /// Gets the access key handler for the window. /// IAccessKeyHandler IInputRoot.AccessKeyHandler { get { return this.accessKeyHandler; } } /// /// Gets or sets the keyboard navigation handler for the window. /// IKeyboardNavigationHandler IInputRoot.KeyboardNavigationHandler { get { return this.keyboardNavigationHandler; } } /// /// Gets or sets a value indicating whether access keys are shown in the window. /// bool IInputRoot.ShowAccessKeys { get { return this.GetValue(AccessText.ShowAccessKeyProperty); } set { this.SetValue(AccessText.ShowAccessKeyProperty, value); } } /// /// 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(); } /// /// Begins an auto-resize operation. /// /// A disposable used to finish the operation. /// /// When an auto-resize operation is in progress any resize events received will not be /// cause the new size to be written to the and /// properties. /// 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); } var scope = this as IFocusScope; if (scope != null) { FocusManager.Instance.SetFocusScope(scope); } 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 . /// /// The event args. private void HandleInput(RawInputEventArgs e) { this.inputManager.Process(e); } /// /// Handles a layout request from . /// private void HandleLayoutNeeded() { this.dispatcher.InvokeAsync(this.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 . /// /// The rectangle to paint. /// An optional platform-specific handle. private void HandlePaint(Rect rect, IPlatformHandle handle) { this.renderer.Render(this, handle); this.renderManager.RenderFinished(); } /// /// Handles a resize notification from . /// /// The new client size. 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)); } } }