// Copyright (c) The Avalonia 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 Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.Logging; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.VisualTree; namespace Avalonia.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 = AvaloniaProperty.RegisterDirect(nameof(ClientSize), o => o.ClientSize); /// /// Defines the property. /// public static readonly DirectProperty IsActiveProperty = AvaloniaProperty.RegisterDirect(nameof(IsActive), o => o.IsActive); /// /// Defines the property. /// public static readonly StyledProperty PointerOverElementProperty = AvaloniaProperty.Register(nameof(IInputRoot.PointerOverElement)); private readonly IRenderQueueManager _renderQueueManager; private readonly IInputManager _inputManager; private readonly IAccessKeyHandler _accessKeyHandler; private readonly IKeyboardNavigationHandler _keyboardNavigationHandler; private readonly IApplicationLifecycle _applicationLifecycle; 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, AvaloniaLocator.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, IAvaloniaDependencyResolver dependencyResolver) { if (impl == null) { throw new InvalidOperationException( "Could not create window implementation: maybe no windowing subsystem was initialized?"); } PlatformImpl = impl; dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current; var renderInterface = TryGetService(dependencyResolver); var styler = TryGetService(dependencyResolver); _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); _renderQueueManager = TryGetService(dependencyResolver); _applicationLifecycle = TryGetService(dependencyResolver); (dependencyResolver.GetService() ?? 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; PlatformImpl.PositionChanged = HandlePositionChanged; _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)); if (_applicationLifecycle != null) { _applicationLifecycle.OnExit += OnApplicationExiting; } } /// /// 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; /// /// Fired when the window position is changed. /// public event EventHandler PositionChanged; /// /// 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 AvaloniaLocator.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 T TryGetService(IAvaloniaDependencyResolver resolver) where T : class { var result = resolver.GetService(); if (result == null) { Logger.Warning( LogArea.Control, this, "Could not create {Service} : maybe Application.RegisterServices() wasn't called?", typeof(T)); } 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); _applicationLifecycle.OnExit -= OnApplicationExiting; } private void OnApplicationExiting(object sender, EventArgs args) { HandleApplicationExiting(); } /// /// Handles the application exiting, either from the last window closing, or a call to . /// protected virtual void HandleApplicationExiting() { } /// /// 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.ProcessInput(e); } /// /// Handles a window position change notification from /// . /// /// The window position. private void HandlePositionChanged(Point pos) { PositionChanged?.Invoke(this, new PointEventArgs(pos)); } /// /// 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); } }