A cross-platform UI framework for .NET
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

282 lines
9.0 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Diagnostics.ViewModels;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Avalonia.Themes.Simple;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.Views
{
internal class MainWindow : Window, IStyleHost
{
private readonly IDisposable? _keySubscription;
private readonly IDisposable? _pointerSubscription;
private readonly Dictionary<Popup, IDisposable> _frozenPopupStates;
private AvaloniaObject? _root;
private PixelPoint _lastPointerPosition;
public MainWindow()
{
InitializeComponent();
// Apply the SimpleTheme.Window theme; this must be done after the XAML is parsed as
// the theme is included in the MainWindow's XAML.
if (Theme is null && this.FindResource(typeof(Window)) is ControlTheme windowTheme)
Theme = windowTheme;
_keySubscription = InputManager.Instance?.Process
.OfType<RawKeyEventArgs>()
.Where(x => x.Type == RawKeyEventType.KeyDown)
.Subscribe(RawKeyDown);
_pointerSubscription = InputManager.Instance?.Process
.OfType<RawPointerEventArgs>()
.Subscribe(x => _lastPointerPosition = x.Root.PointToScreen(x.Position));
_frozenPopupStates = new Dictionary<Popup, IDisposable>();
EventHandler? lh = default;
lh = (s, e) =>
{
this.Opened -= lh;
if ((DataContext as MainViewModel)?.StartupScreenIndex is { } index)
{
var screens = this.Screens;
if (index > -1 && index < screens.ScreenCount)
{
var screen = screens.All[index];
this.Position = screen.Bounds.TopLeft;
this.WindowState = WindowState.Maximized;
}
}
};
this.Opened += lh;
}
public AvaloniaObject? Root
{
get => _root;
set
{
if (_root != value)
{
if (_root is ICloseable oldClosable)
{
oldClosable.Closed -= RootClosed;
}
_root = value;
if (_root is ICloseable newClosable)
{
newClosable.Closed += RootClosed;
DataContext = new MainViewModel(_root);
}
else
{
DataContext = null;
}
}
}
}
IStyleHost? IStyleHost.StylingParent => null;
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
_keySubscription?.Dispose();
_pointerSubscription?.Dispose();
foreach (var state in _frozenPopupStates)
{
state.Value.Dispose();
}
_frozenPopupStates.Clear();
if (_root is ICloseable cloneable)
{
cloneable.Closed -= RootClosed;
_root = null;
}
((MainViewModel?)DataContext)?.Dispose();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private IControl? GetHoveredControl(TopLevel topLevel)
{
var point = topLevel.PointToClient(_lastPointerPosition);
return (IControl?)topLevel.GetVisualsAt(point, x =>
{
if (x is AdornerLayer || !x.IsVisible)
{
return false;
}
return !(x is IInputElement ie) || ie.IsHitTestVisible;
})
.FirstOrDefault();
}
private static List<PopupRoot> GetPopupRoots(TopLevel root)
{
var popupRoots = new List<PopupRoot>();
void ProcessProperty<T>(IControl control, AvaloniaProperty<T> property)
{
if (control.GetValue(property) is IPopupHostProvider popupProvider
&& popupProvider.PopupHost is PopupRoot popupRoot)
{
popupRoots.Add(popupRoot);
}
}
foreach (var control in root.GetVisualDescendants().OfType<IControl>())
{
if (control is Popup p && p.Host is PopupRoot popupRoot)
{
popupRoots.Add(popupRoot);
}
ProcessProperty(control, ContextFlyoutProperty);
ProcessProperty(control, ContextMenuProperty);
ProcessProperty(control, FlyoutBase.AttachedFlyoutProperty);
ProcessProperty(control, ToolTipDiagnostics.ToolTipProperty);
ProcessProperty(control, Button.FlyoutProperty);
}
return popupRoots;
}
private void RawKeyDown(RawKeyEventArgs e)
{
var vm = (MainViewModel?)DataContext;
if (vm is null)
{
return;
}
var root = vm.PointerOverRoot as TopLevel;
if (root is null)
{
return;
}
if (root is PopupRoot pr && pr.ParentTopLevel != null)
{
root = pr.ParentTopLevel;
}
switch (e.Modifiers)
{
case RawInputModifiers.Control when (e.Key == Key.LeftShift || e.Key == Key.RightShift):
case RawInputModifiers.Shift when (e.Key == Key.LeftCtrl || e.Key == Key.RightCtrl):
case RawInputModifiers.Shift | RawInputModifiers.Control:
{
IControl? control = null;
foreach (var popupRoot in GetPopupRoots(root))
{
control = GetHoveredControl(popupRoot);
if (control != null)
{
break;
}
}
control ??= GetHoveredControl(root);
if (control != null)
{
vm.SelectControl(control);
}
break;
}
case RawInputModifiers.Control | RawInputModifiers.Alt when e.Key == Key.F:
{
vm.FreezePopups = !vm.FreezePopups;
foreach (var popupRoot in GetPopupRoots(root))
{
if (popupRoot.Parent is Popup popup)
{
if (vm.FreezePopups)
{
var lightDismissEnabledState = popup.SetValue(
Popup.IsLightDismissEnabledProperty,
!vm.FreezePopups,
BindingPriority.Animation);
if (lightDismissEnabledState != null)
{
_frozenPopupStates[popup] = lightDismissEnabledState;
}
}
else
{
//TODO Use Dictionary.Remove(Key, out Value) in netstandard 2.1
if (_frozenPopupStates.ContainsKey(popup))
{
_frozenPopupStates[popup].Dispose();
_frozenPopupStates.Remove(popup);
}
}
}
}
break;
}
case RawInputModifiers.Alt when e.Key == Key.S || e.Key == Key.D:
{
vm.EnableSnapshotStyles(e.Key == Key.S);
break;
}
}
}
private void RootClosed(object? sender, EventArgs e) => Close();
public void SetOptions(DevToolsOptions options)
{
(DataContext as MainViewModel)?.SetOptions(options);
if (options.UseDarkMode)
{
if (Styles[0] is SimpleTheme st)
{
st.Mode = SimpleThemeMode.Dark;
}
}
}
internal void SelectedControl(IControl? control)
{
if (control is { })
{
(DataContext as MainViewModel)?.SelectControl(control);
}
}
}
}