// 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; using JetBrains.Annotations; namespace Avalonia.Controls { /// /// Base class for top-level widgets. /// /// /// This class acts as a base for top level widget. /// It handles scheduling layout, styling and rendering as well as /// tracking the widget's . /// 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 StyledProperty PointerOverElementProperty = AvaloniaProperty.Register(nameof(IInputRoot.PointerOverElement)); private readonly IInputManager _inputManager; private readonly IAccessKeyHandler _accessKeyHandler; private readonly IKeyboardNavigationHandler _keyboardNavigationHandler; private readonly IApplicationLifecycle _applicationLifecycle; private readonly IPlatformRenderInterface _renderInterface; private Size _clientSize; /// /// 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 styler = TryGetService(dependencyResolver); _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); _applicationLifecycle = TryGetService(dependencyResolver); _renderInterface = TryGetService(dependencyResolver); var renderLoop = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); impl.SetInputRoot(this); impl.Closed = HandleClosed; impl.Input = HandleInput; impl.Paint = HandlePaint; impl.Resized = HandleResized; impl.ScalingChanged = HandleScalingChanged; _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); styler?.ApplyStyles(this); ClientSize = impl.ClientSize; 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 closed. /// public event EventHandler Closed; /// /// Gets or sets the client size of the window. /// public Size ClientSize { get { return _clientSize; } protected set { SetAndRaise(ClientSizeProperty, ref _clientSize, value); } } /// /// Gets the platform-specific window implementation. /// [CanBeNull] public ITopLevelImpl PlatformImpl { get; private set; } /// /// Gets the renderer for the window. /// public IRenderer Renderer { get; private set; } /// /// 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); } } /// IMouseDevice IInputRoot.MouseDevice => PlatformImpl?.MouseDevice; /// /// 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 ?? 1; /// double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1; IStyleHost IStyleHost.StylingParent { get { return AvaloniaLocator.Current.GetService(); } } IRenderTarget IRenderRoot.CreateRenderTarget() => CreateRenderTarget(); /// protected virtual IRenderTarget CreateRenderTarget() { if(PlatformImpl == null) throw new InvalidOperationException("Cann't create render target, PlatformImpl is null (might be already disposed)"); return _renderInterface.CreateRenderTarget(PlatformImpl.Surfaces); } /// void IRenderRoot.Invalidate(Rect rect) { PlatformImpl?.Invalidate(rect); } /// Point IRenderRoot.PointToClient(Point p) { return PlatformImpl?.PointToClient(p) ?? default(Point); } /// Point IRenderRoot.PointToScreen(Point p) { return PlatformImpl?.PointToScreen(p) ?? default(Point); } /// /// Handles a paint notification from . /// /// The dirty area. protected virtual void HandlePaint(Rect rect) { Renderer?.Paint(rect); } /// /// Handles a closed notification from . /// protected virtual void HandleClosed() { PlatformImpl = null; Closed?.Invoke(this, EventArgs.Empty); Renderer?.Dispose(); Renderer = null; _applicationLifecycle.OnExit -= OnApplicationExiting; } /// /// Handles a resize notification from . /// /// The new client size. protected virtual void HandleResized(Size clientSize) { ClientSize = clientSize; Width = clientSize.Width; Height = clientSize.Height; LayoutManager.Instance.ExecuteLayoutPass(); Renderer?.Resized(clientSize); } /// /// Handles a window scaling change notification from /// . /// /// The window scaling. protected virtual void HandleScalingChanged(double scaling) { foreach (ILayoutable control in this.GetSelfAndVisualDescendants()) { 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 , logging a /// warning 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; } 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 input from . /// /// The event args. private void HandleInput(RawInputEventArgs e) { _inputManager.ProcessInput(e); } } }