csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
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
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|