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;
}
}
}
}