// Copyright (c) The Perspex Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; using System.Reactive.Disposables; using System.Reactive.Linq; using Perspex.Controls.Platform; using Perspex.Controls.Primitives; using Perspex.Input; using Perspex.Input.Raw; using Perspex.Layout; using Perspex.Platform; using Perspex.Rendering; using Perspex.Styling; using Perspex.VisualTree; namespace Perspex.Controls { /// /// 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, IStyleRoot { /// /// Defines the property. /// public static readonly DirectProperty ClientSizeProperty = PerspexProperty.RegisterDirect(nameof(ClientSize), o => o.ClientSize); /// /// Defines the property. /// public static readonly DirectProperty IsActiveProperty = PerspexProperty.RegisterDirect(nameof(IsActive), o => o.IsActive); /// /// Defines the property. /// public static readonly StyledProperty PointerOverElementProperty = PerspexProperty.Register(nameof(IInputRoot.PointerOverElement)); private readonly IRenderQueueManager _renderQueueManager; private readonly IInputManager _inputManager; private readonly IAccessKeyHandler _accessKeyHandler; private readonly IKeyboardNavigationHandler _keyboardNavigationHandler; private Size _clientSize; private bool _isActive; /// /// Initializes static members of the class. /// static TopLevel() { AffectsMeasure(ClientSizeProperty); } /// /// Initializes a new instance of the class. /// /// The platform-specific window implementation. public TopLevel(ITopLevelImpl impl) : this(impl, PerspexLocator.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, IPerspexDependencyResolver dependencyResolver) { if (impl == null) { throw new InvalidOperationException( "Could not create window implementation: maybe no windowing subsystem was initialized?"); } PlatformImpl = impl; dependencyResolver = dependencyResolver ?? PerspexLocator.Current; var renderInterface = TryGetService(dependencyResolver); var styler = TryGetService(dependencyResolver); _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); _renderQueueManager = TryGetService(dependencyResolver); (TryGetService(dependencyResolver) ?? new DefaultTopLevelRenderer()).Attach(this); PlatformImpl.SetInputRoot(this); PlatformImpl.Activated = HandleActivated; PlatformImpl.Deactivated = HandleDeactivated; PlatformImpl.Closed = HandleClosed; PlatformImpl.Input = HandleInput; PlatformImpl.Resized = HandleResized; PlatformImpl.ScalingChanged = HandleScalingChanged; _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); styler?.ApplyStyles(this); ClientSize = PlatformImpl.ClientSize; this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl.ClientSize = x); this.GetObservable(PointerOverElementProperty) .Select( x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) .Switch().Subscribe(cursor => PlatformImpl.SetCursor(cursor?.PlatformCursor)); } /// /// 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 _clientSize; } private set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } } /// /// Gets a value that indicates whether the window is active. /// public bool IsActive { get { return _isActive; } private set { SetAndRaise(IsActiveProperty, ref _isActive, value); } } /// /// Gets or sets the window position in screen coordinates. /// public Point Position { get { return PlatformImpl.Position; } set { PlatformImpl.Position = value; } } /// /// Gets the platform-specific window implementation. /// public ITopLevelImpl PlatformImpl { get; } /// /// Gets the window render manager. /// IRenderQueueManager IRenderRoot.RenderQueueManager => _renderQueueManager; /// /// Gets the access key handler for the window. /// IAccessKeyHandler IInputRoot.AccessKeyHandler => _accessKeyHandler; /// /// Gets or sets the keyboard navigation handler for the window. /// IKeyboardNavigationHandler IInputRoot.KeyboardNavigationHandler => _keyboardNavigationHandler; /// /// Gets or sets the input element that the pointer is currently over. /// IInputElement IInputRoot.PointerOverElement { get { return GetValue(PointerOverElementProperty); } set { SetValue(PointerOverElementProperty, value); } } /// /// Gets or sets a value indicating whether access keys are shown in the window. /// bool IInputRoot.ShowAccessKeys { get { return GetValue(AccessText.ShowAccessKeyProperty); } set { SetValue(AccessText.ShowAccessKeyProperty, value); } } /// Size ILayoutRoot.MaxClientSize => Size.Infinity; /// double ILayoutRoot.LayoutScaling => PlatformImpl.Scaling; IStyleHost IStyleHost.StylingParent { get { return PerspexLocator.Current.GetService(); } } /// /// Whether an auto-size operation is in progress. /// protected bool AutoSizing { get; private set; } /// Point IRenderRoot.PointToClient(Point p) { return PlatformImpl.PointToClient(p); } /// Point IRenderRoot.PointToScreen(Point p) { return PlatformImpl.PointToScreen(p); } /// /// Activates the window. /// public void Activate() { 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() { AutoSizing = true; return Disposable.Create(() => AutoSizing = false); } /// /// Carries out the arrange pass of the window. /// /// The final window size. /// The parameter unchanged. protected override Size ArrangeOverride(Size finalSize) { using (BeginAutoSizing()) { PlatformImpl.ClientSize = finalSize; } return base.ArrangeOverride(PlatformImpl.ClientSize); } /// /// Handles a resize notification from . /// /// The new client size. protected virtual void HandleResized(Size clientSize) { if (!AutoSizing) { Width = clientSize.Width; Height = clientSize.Height; } ClientSize = clientSize; LayoutManager.Instance.ExecuteLayoutPass(); PlatformImpl.Invalidate(new Rect(clientSize)); } /// /// Handles a window scaling change notification from /// . /// /// The window scaling. protected virtual void HandleScalingChanged(double scaling) { foreach (ILayoutable control in this.GetSelfAndVisualDescendents()) { control.InvalidateMeasure(); } } /// protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) { base.OnAttachedToVisualTree(e); throw new InvalidOperationException( $"Control '{GetType().Name}' is a top level control and cannot be added as a child."); } /// /// Tries to get a service from an , throwing an /// exception if not found. /// /// The service type. /// The resolver. /// The service. private static T TryGetService(IPerspexDependencyResolver resolver) where T : class { var result = resolver.GetService(); System.Diagnostics.Debug.WriteLineIf( result == null, $"Could not create {typeof(T).Name} : maybe Application.RegisterServices() wasn't called?"); return result; } /// /// Handles an activated notification from . /// private void HandleActivated() { Activated?.Invoke(this, EventArgs.Empty); var scope = this as IFocusScope; if (scope != null) { FocusManager.Instance.SetFocusScope(scope); } IsActive = true; } /// /// Handles a closed notification from . /// private void HandleClosed() { Closed?.Invoke(this, EventArgs.Empty); } /// /// Handles a deactivated notification from . /// private void HandleDeactivated() { IsActive = false; Deactivated?.Invoke(this, EventArgs.Empty); } /// /// Handles input from . /// /// The event args. private void HandleInput(RawInputEventArgs e) { _inputManager.Process(e); } /// /// Starts moving a window with left button being held. Should be called from left mouse button press event handler /// public void BeginMoveDrag() => PlatformImpl.BeginMoveDrag(); /// /// Starts resizing a window. This function is used if an application has window resizing controls. /// Should be called from left mouse button press event handler /// public void BeginResizeDrag(WindowEdge edge) => PlatformImpl.BeginResizeDrag(edge); } }