using System; using System.ComponentModel; using Avalonia.Reactive; using Avalonia.Controls.Metadata; using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Data; using Avalonia.Input; using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Interactivity; 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.Input.Platform; using System.Linq; 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); /// /// Defines the event. /// public static readonly RoutedEvent BackRequestedEvent = RoutedEvent.Register(nameof(BackRequested), RoutingStrategies.Bubble); 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 IGlobalStyles? _globalStyles; private readonly IGlobalThemeVariantProvider? _applicationThemeHost; private readonly PointerOverPreProcessor? _pointerOverPreProcessor; private readonly IDisposable? _pointerOverPreProcessorSubscription; private readonly IDisposable? _backGestureSubscription; private Size _clientSize; private Size? _frameSize; private WindowTransparencyLevel _actualTransparencyLevel; private ILayoutManager? _layoutManager; private Border? _transparencyFallbackBorder; private TargetWeakEventSubscriber? _resourcesChangesSubscriber; private IStorageProvider? _storageProvider; private LayoutDiagnosticBridge? _layoutDiagnosticBridge; /// /// Initializes static members of the class. /// static TopLevel() { KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(KeyboardNavigationMode.Cycle); 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) { PlatformImpl = impl ?? throw new InvalidOperationException( "Could not create window implementation: maybe no windowing subsystem was initialized?"); _actualTransparencyLevel = PlatformImpl.TransparencyLevel; dependencyResolver ??= AvaloniaLocator.Current; _accessKeyHandler = TryGetService(dependencyResolver); _inputManager = TryGetService(dependencyResolver); _keyboardNavigationHandler = TryGetService(dependencyResolver); _globalStyles = TryGetService(dependencyResolver); _applicationThemeHost = TryGetService(dependencyResolver); Renderer = impl.CreateRenderer(this); 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; } if (_applicationThemeHost is { }) { SetValue(ActualThemeVariantProperty, _applicationThemeHost.ActualThemeVariant, BindingPriority.Template); _applicationThemeHost.ActualThemeVariantChanged += GlobalActualThemeVariantChanged; } 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); if(impl.TryGetFeature() is {} systemNavigationManager) { systemNavigationManager.BackRequested += (_, e) => { e.RoutedEvent = BackRequestedEvent; RaiseEvent(e); }; } _backGestureSubscription = _inputManager?.PreProcess.Subscribe(e => { bool backRequested = false; if (e is RawKeyEventArgs rawKeyEventArgs && rawKeyEventArgs.Type == RawKeyEventType.KeyDown) { var keymap = AvaloniaLocator.Current.GetService()?.Back; if (keymap != null) { var keyEvent = new KeyEventArgs() { KeyModifiers = (KeyModifiers)rawKeyEventArgs.Modifiers, Key = rawKeyEventArgs.Key }; backRequested = keymap.Any( key => key.Matches(keyEvent)); } } else if(e is RawPointerEventArgs pointerEventArgs) { backRequested = pointerEventArgs.Type == RawPointerEventType.XButton1Down; } if (backRequested) { var backRequestedEventArgs = new RoutedEventArgs(BackRequestedEvent); RaiseEvent(backRequestedEventArgs); e.Handled = backRequestedEventArgs.Handled; } }); } /// /// 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 ThemeVariant? RequestedThemeVariant { get => GetValue(RequestedThemeVariantProperty); set => SetValue(RequestedThemeVariantProperty, value); } /// /// Occurs when physical Back Button is pressed or a back navigation has been requested. /// public event EventHandler BackRequested { add { AddHandler(BackRequestedEvent, value); } remove { RemoveHandler(BackRequestedEvent, value); } } public ILayoutManager LayoutManager { get { if (_layoutManager is null) { _layoutManager = CreateLayoutManager(); if (_layoutManager is LayoutManager typedLayoutManager) { _layoutDiagnosticBridge = new LayoutDiagnosticBridge(Renderer.Diagnostics, typedLayoutManager); _layoutDiagnosticBridge.SetupBridge(); } } return _layoutManager; } } /// /// Gets the platform-specific window implementation. /// public ITopLevelImpl? PlatformImpl { get; private set; } /// /// Gets the renderer for the window. /// public IRenderer Renderer { get; } internal PixelPoint? LastPointerPosition => _pointerOverPreProcessor?.LastPosition; /// /// 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); } } /// /// 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?.TryGetFeature() ?? throw new InvalidOperationException("StorageProvider platform implementation is not available."); /// Point IRenderRoot.PointToClient(PixelPoint p) { return PlatformImpl?.PointToClient(p) ?? default; } /// PixelPoint IRenderRoot.PointToScreen(Point p) { return PlatformImpl?.PointToScreen(p) ?? default; } /// /// Gets the for which the given is hosted in. /// /// The visual to query its TopLevel /// The TopLevel public static TopLevel? GetTopLevel(Visual? visual) { return visual == null ? null : visual.VisualRoot as TopLevel; } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == TransparencyLevelHintProperty) { if (PlatformImpl != null) { PlatformImpl.SetTransparencyLevelHint(change.GetNewValue()); HandleTransparencyLevelChanged(PlatformImpl.TransparencyLevel); } } else if (change.Property == ActualThemeVariantProperty) { PlatformImpl?.SetFrameThemeVariant((PlatformThemeVariant?)change.GetNewValue() ?? PlatformThemeVariant.Light); } } /// /// 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; } if (_applicationThemeHost is { }) { _applicationThemeHost.ActualThemeVariantChanged -= GlobalActualThemeVariantChanged; } Renderer.SceneInvalidated -= SceneInvalidated; Renderer.Dispose(); _layoutDiagnosticBridge?.Dispose(); _layoutDiagnosticBridge = null; _pointerOverPreProcessor?.OnCompleted(); _pointerOverPreProcessorSubscription?.Dispose(); _backGestureSubscription?.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(); } /// /// 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 static 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 (PlatformImpl != null) { if (e is RawPointerEventArgs pointerArgs) { pointerArgs.InputHitTestResult = this.InputHitTest(pointerArgs.Position); } _inputManager?.ProcessInput(e); } else { Logger.TryGet(LogEventLevel.Warning, LogArea.Control)?.Log( this, "PlatformImpl is null, couldn't handle input."); } } private void GlobalActualThemeVariantChanged(object? sender, EventArgs e) { SetValue(ActualThemeVariantProperty, ((IGlobalThemeVariantProvider)sender!).ActualThemeVariant, BindingPriority.Template); } private void SceneInvalidated(object? sender, SceneInvalidatedEventArgs e) { _pointerOverPreProcessor?.SceneInvalidated(e.DirtyRect); } void PlatformImpl_LostFocus() { var focused = (Visual?)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); } protected override bool BypassFlowDirectionPolicies => true; public override void InvalidateMirrorTransform() { // Do nothing becuase TopLevel should't apply MirrorTransform on himself. } ITextInputMethodImpl? ITextInputMethodRoot.InputMethod => PlatformImpl?.TryGetFeature(); /// /// Provides layout pass timing from the layout manager to the renderer, for diagnostics purposes. /// private sealed class LayoutDiagnosticBridge : IDisposable { private readonly RendererDiagnostics _diagnostics; private readonly LayoutManager _layoutManager; private bool _isHandling; public LayoutDiagnosticBridge(RendererDiagnostics diagnostics, LayoutManager layoutManager) { _diagnostics = diagnostics; _layoutManager = layoutManager; diagnostics.PropertyChanged += OnDiagnosticsPropertyChanged; } public void SetupBridge() { var needsHandling = (_diagnostics.DebugOverlays & RendererDebugOverlays.LayoutTimeGraph) != 0; if (needsHandling != _isHandling) { _isHandling = needsHandling; _layoutManager.LayoutPassTimed = needsHandling ? timing => _diagnostics.LastLayoutPassTiming = timing : null; } } private void OnDiagnosticsPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(RendererDiagnostics.DebugOverlays)) { SetupBridge(); } } public void Dispose() { _diagnostics.PropertyChanged -= OnDiagnosticsPropertyChanged; _layoutManager.LayoutPassTimed = null; } } } }