using System; using System.Reactive.Linq; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Layout; using Avalonia.Logging; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Utilities; 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, IStyleHost, ILogicalRoot, IWeakSubscriber { /// /// 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)); /// /// Defines the property. /// public static readonly StyledProperty TransparencyLevelHintProperty = AvaloniaProperty.Register(nameof(TransparencyLevelHint), WindowTransparencyLevel.None); /// /// Defines the property. /// public static readonly DirectProperty ActualTransparencyLevelProperty = AvaloniaProperty.RegisterDirect(nameof(ActualTransparencyLevel), o => o.ActualTransparencyLevel, unsetValue: WindowTransparencyLevel.None); /// /// Defines the property. /// public static readonly StyledProperty TransparencyBackgroundFallbackProperty = AvaloniaProperty.Register(nameof(TransparencyBackgroundFallback), Brushes.White); private readonly IInputManager _inputManager; private readonly IAccessKeyHandler _accessKeyHandler; private readonly IKeyboardNavigationHandler _keyboardNavigationHandler; private readonly IPlatformRenderInterface _renderInterface; private readonly IGlobalStyles _globalStyles; private Size _clientSize; private WindowTransparencyLevel _actualTransparencyLevel; private ILayoutManager _layoutManager; private Border _transparencyFallbackBorder; /// /// Initializes static members of the class. /// static TopLevel() { AffectsMeasure(ClientSizeProperty); TransparencyLevelHintProperty.Changed.AddClassHandler( (tl, e) => { if (tl.PlatformImpl != null) { tl.PlatformImpl.SetTransparencyLevelHint((WindowTransparencyLevel)e.NewValue); tl.HandleTransparencyLevelChanged(tl.PlatformImpl.TransparencyLevel); } }); } /// /// 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; _actualTransparencyLevel = PlatformImpl.TransparencyLevel; dependencyResolver = dependencyResolver ?? AvaloniaLocator.Current; var styler = TryGetService(dependencyResolver); _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); _renderInterface = TryGetService(dependencyResolver); _globalStyles = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); if (Renderer != null) { Renderer.SceneInvalidated += SceneInvalidated; } impl.SetInputRoot(this); impl.Closed = HandleClosed; impl.Input = HandleInput; impl.Paint = HandlePaint; impl.Resized = HandleResized; impl.ScalingChanged = HandleScalingChanged; impl.TransparencyLevelChanged = HandleTransparencyLevelChanged; _keyboardNavigationHandler?.SetOwner(this); _accessKeyHandler?.SetOwner(this); if (_globalStyles is object) { _globalStyles.GlobalStylesAdded += ((IStyleHost)this).StylesAdded; _globalStyles.GlobalStylesRemoved += ((IStyleHost)this).StylesRemoved; } 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 (((IStyleHost)this).StylingParent is IResourceHost applicationResources) { WeakSubscriptionManager.Subscribe( applicationResources, nameof(IResourceHost.ResourcesChanged), this); } impl.LostFocus += PlatformImpl_LostFocus; } /// /// Fired when the window is opened. /// public event EventHandler Opened; /// /// 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 or sets the that the TopLevel should use when possible. /// public WindowTransparencyLevel TransparencyLevelHint { get { return GetValue(TransparencyLevelHintProperty); } set { SetValue(TransparencyLevelHintProperty, value); } } /// /// Gets the acheived that the platform was able to provide. /// public WindowTransparencyLevel ActualTransparencyLevel { get => _actualTransparencyLevel; private set => SetAndRaise(ActualTransparencyLevelProperty, ref _actualTransparencyLevel, value); } /// /// Gets or sets the that transparency will blend with when transparency is not supported. /// By default this is a solid white brush. /// public IBrush TransparencyBackgroundFallback { get => GetValue(TransparencyBackgroundFallbackProperty); set => SetValue(TransparencyBackgroundFallbackProperty, value); } public ILayoutManager LayoutManager { get { if (_layoutManager == null) _layoutManager = CreateLayoutManager(); return _layoutManager; } } /// /// 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; void IWeakSubscriber.OnEvent(object sender, ResourcesChangedEventArgs e) { ((ILogical)this).NotifyResourcesChanged(e); } /// /// 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); } } /// double ILayoutRoot.LayoutScaling => PlatformImpl?.Scaling ?? 1; /// double IRenderRoot.RenderScaling => PlatformImpl?.Scaling ?? 1; IStyleHost IStyleHost.StylingParent => _globalStyles; IRenderTarget IRenderRoot.CreateRenderTarget() => CreateRenderTarget(); /// protected virtual IRenderTarget CreateRenderTarget() { if(PlatformImpl == null) throw new InvalidOperationException("Can'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(PixelPoint p) { return PlatformImpl?.PointToClient(p) ?? default; } /// PixelPoint IRenderRoot.PointToScreen(Point p) { return PlatformImpl?.PointToScreen(p) ?? default; } /// /// Creates the layout manager for this . /// protected virtual ILayoutManager CreateLayoutManager() => new LayoutManager(this); /// /// 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() { if (_globalStyles is object) { _globalStyles.GlobalStylesAdded -= ((IStyleHost)this).StylesAdded; _globalStyles.GlobalStylesRemoved -= ((IStyleHost)this).StylesRemoved; } Renderer?.Dispose(); Renderer = null; var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); var visualArgs = new VisualTreeAttachmentEventArgs(this, this); OnDetachedFromVisualTreeCore(visualArgs); (this as IInputRoot).MouseDevice?.TopLevelClosed(this); PlatformImpl = null; OnClosed(EventArgs.Empty); LayoutManager?.Dispose(); } /// /// Handles a resize notification from . /// /// The new client size. protected virtual void HandleResized(Size clientSize) { ClientSize = clientSize; Width = clientSize.Width; Height = clientSize.Height; LayoutManager.ExecuteLayoutPass(); Renderer?.Resized(clientSize); } /// /// Handles a window scaling change notification from /// . /// /// The window scaling. protected virtual void HandleScalingChanged(double scaling) { LayoutHelper.InvalidateSelfAndChildrenMeasure(this); } private bool TransparencyLevelsMatch (WindowTransparencyLevel requested, WindowTransparencyLevel received) { if(requested == received) { return true; } else if(requested >= WindowTransparencyLevel.Blur && received >= WindowTransparencyLevel.Blur) { return true; } return false; } protected virtual void HandleTransparencyLevelChanged(WindowTransparencyLevel transparencyLevel) { if(_transparencyFallbackBorder != null) { if(transparencyLevel == WindowTransparencyLevel.None || TransparencyLevelHint == WindowTransparencyLevel.None || !TransparencyLevelsMatch(TransparencyLevelHint, transparencyLevel)) { _transparencyFallbackBorder.Background = TransparencyBackgroundFallback; } else { _transparencyFallbackBorder.Background = Brushes.Transparent; } } ActualTransparencyLevel = transparencyLevel; } /// 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."); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); _transparencyFallbackBorder = e.NameScope.Find("PART_TransparencyFallback"); HandleTransparencyLevelChanged(PlatformImpl.TransparencyLevel); } /// /// Raises the event. /// /// The event args. protected virtual void OnOpened(EventArgs e) => Opened?.Invoke(this, e); /// /// Raises the event. /// /// The event args. protected virtual void OnClosed(EventArgs e) => Closed?.Invoke(this, e); /// /// 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.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log( this, "Could not create {Service} : maybe Application.RegisterServices() wasn't called?", typeof(T)); } return result; } /// /// Handles input from . /// /// The event args. private void HandleInput(RawInputEventArgs e) { _inputManager.ProcessInput(e); } private void SceneInvalidated(object sender, SceneInvalidatedEventArgs e) { (this as IInputRoot).MouseDevice.SceneInvalidated(this, e.DirtyRect); } void PlatformImpl_LostFocus() { var focused = (IVisual)FocusManager.Instance.Current; if (focused == null) return; while (focused.VisualParent != null) focused = focused.VisualParent; if (focused == this) KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None); } } }