using System; using System.Reactive.Linq; using Avalonia.Controls.Metadata; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Layout; using Avalonia.Logging; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Platform.Storage; using Avalonia.Rendering; using Avalonia.Styling; using Avalonia.Utilities; using Avalonia.VisualTree; 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 . /// [TemplatePart("PART_TransparencyFallback", typeof(Border))] public abstract class TopLevel : ContentControl, IInputRoot, ILayoutRoot, IRenderRoot, ICloseable, IStyleHost, ILogicalRoot, ITextInputMethodRoot { /// /// Defines the property. /// public static readonly DirectProperty ClientSizeProperty = AvaloniaProperty.RegisterDirect(nameof(ClientSize), o => o.ClientSize); /// /// Defines the property. /// public static readonly DirectProperty FrameSizeProperty = AvaloniaProperty.RegisterDirect(nameof(FrameSize), o => o.FrameSize); /// /// 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 static readonly WeakEvent ResourcesChangedWeakEvent = WeakEvent.Register( (s, h) => s.ResourcesChanged += h, (s, h) => s.ResourcesChanged -= h ); private readonly IInputManager? _inputManager; private readonly IAccessKeyHandler? _accessKeyHandler; private readonly IKeyboardNavigationHandler? _keyboardNavigationHandler; private readonly IPlatformRenderInterface? _renderInterface; private readonly IGlobalStyles? _globalStyles; private readonly PointerOverPreProcessor? _pointerOverPreProcessor; private readonly IDisposable? _pointerOverPreProcessorSubscription; private Size _clientSize; private Size? _frameSize; private WindowTransparencyLevel _actualTransparencyLevel; private ILayoutManager? _layoutManager; private Border? _transparencyFallbackBorder; private TargetWeakEventSubscriber? _resourcesChangesSubscriber; private IStorageProvider? _storageProvider; /// /// Initializes static members of the class. /// static TopLevel() { KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(KeyboardNavigationMode.Cycle); 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; } else { // Prevent nullable error. Renderer = null!; } 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; FrameSize = impl.FrameSize; this.GetObservable(PointerOverElementProperty) .Select( x => (x as InputElement)?.GetObservable(CursorProperty) ?? Observable.Empty()) .Switch().Subscribe(cursor => PlatformImpl?.SetCursor(cursor?.PlatformImpl)); if (((IStyleHost)this).StylingParent is IResourceHost applicationResources) { _resourcesChangesSubscriber = new TargetWeakEventSubscriber( this, static (target, _, _, e) => { ((ILogical)target).NotifyResourcesChanged(e); }); ResourcesChangedWeakEvent.Subscribe(applicationResources, _resourcesChangesSubscriber); } impl.LostFocus += PlatformImpl_LostFocus; _pointerOverPreProcessor = new PointerOverPreProcessor(this); _pointerOverPreProcessorSubscription = _inputManager?.PreProcess.Subscribe(_pointerOverPreProcessor); } /// /// 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 total size of the window. /// public Size? FrameSize { get { return _frameSize; } protected set { SetAndRaise(FrameSizeProperty, ref _frameSize, 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 achieved 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. /// 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!; /// 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); } } /// double ILayoutRoot.LayoutScaling => PlatformImpl?.RenderScaling ?? 1; /// double IRenderRoot.RenderScaling => PlatformImpl?.RenderScaling ?? 1; IStyleHost IStyleHost.StylingParent => _globalStyles!; public IStorageProvider StorageProvider => _storageProvider ??= AvaloniaLocator.Current.GetService()?.CreateProvider(this) ?? (PlatformImpl as ITopLevelImplWithStorageProvider)?.StorageProvider ?? throw new InvalidOperationException("StorageProvider platform implementation is not available."); 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); public override void InvalidateMirrorTransform() { } protected override bool BypassFlowDirectionPolicies => true; /// /// 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!; _pointerOverPreProcessor?.OnCompleted(); _pointerOverPreProcessorSubscription?.Dispose(); PlatformImpl = null; var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); var visualArgs = new VisualTreeAttachmentEventArgs(this, this); OnDetachedFromVisualTreeCore(visualArgs); OnClosed(EventArgs.Empty); LayoutManager?.Dispose(); } [Obsolete("Use HandleResized(Size, PlatformResizeReason)")] protected virtual void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified); /// /// Handles a resize notification from . /// /// The new client size. /// The reason for the resize. protected virtual void HandleResized(Size clientSize, PlatformResizeReason reason) { ClientSize = clientSize; FrameSize = PlatformImpl!.FrameSize; 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 = null; } } 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); if (PlatformImpl is null) return; _transparencyFallbackBorder = e.NameScope.Find("PART_TransparencyFallback"); HandleTransparencyLevelChanged(PlatformImpl.TransparencyLevel); } /// /// Raises the event. /// /// The event args. protected virtual void OnOpened(EventArgs e) { FrameSize = PlatformImpl?.FrameSize; 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) { if (e is RawPointerEventArgs pointerArgs) { pointerArgs.InputHitTestResult = this.InputHitTest(pointerArgs.Position); } _inputManager?.ProcessInput(e); } private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) { _pointerOverPreProcessor?.SceneInvalidated(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); } ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => (PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod; } }