using System; using System.ComponentModel; using System.Reactive.Linq; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; using Avalonia.Metadata; using Avalonia.Threading; using System.Linq; namespace Avalonia.Diagnostics.ViewModels { internal class MainViewModel : ViewModelBase, IDisposable { private readonly AvaloniaObject _root; private readonly TreePageViewModel _logicalTree; private readonly TreePageViewModel _visualTree; private readonly EventsPageViewModel _events; private readonly IDisposable _pointerOverSubscription; private ViewModelBase? _content; private int _selectedTab; private string? _focusedControl; private IInputElement? _pointerOverElement; private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; private bool _freezePopups; private string? _pointerOverElementName; private IInputRoot? _pointerOverRoot; private IScreenshotHandler? _screenshotHandler; private bool _showPropertyType; private bool _showImplementedInterfaces; public MainViewModel(AvaloniaObject root) { _root = root; _logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root)); _visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root)); _events = new EventsPageViewModel(this); UpdateFocusedControl(); if (KeyboardDevice.Instance is not null) KeyboardDevice.Instance.PropertyChanged += KeyboardPropertyChanged; SelectedTab = 0; if (root is TopLevel topLevel) { _pointerOverSubscription = topLevel.GetObservable(TopLevel.PointerOverElementProperty) .Subscribe(x => PointerOverElement = x); } else { #nullable disable _pointerOverSubscription = InputManager.Instance.PreProcess .OfType() .Subscribe(e => { PointerOverRoot = e.Root; PointerOverElement = e.Root.InputHitTest(e.Position); }); #nullable restore } Console = new ConsoleViewModel(UpdateConsoleContext); } public bool FreezePopups { get => _freezePopups; set => RaiseAndSetIfChanged(ref _freezePopups, value); } public bool ShouldVisualizeMarginPadding { get => _shouldVisualizeMarginPadding; set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value); } public bool ShouldVisualizeDirtyRects { get => _shouldVisualizeDirtyRects; set { var changed = true; if (_root is TopLevel topLevel && topLevel.Renderer is { }) { topLevel.Renderer.DrawDirtyRects = value; } else if (_root is Controls.Application app && app.RendererRoot is { }) { app.RendererRoot.DrawDirtyRects = value; } else { changed = false; } if (changed) RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value); } } public void ToggleVisualizeDirtyRects() { ShouldVisualizeDirtyRects = !ShouldVisualizeDirtyRects; } public void ToggleVisualizeMarginPadding() { ShouldVisualizeMarginPadding = !ShouldVisualizeMarginPadding; } public bool ShowFpsOverlay { get => _showFpsOverlay; set { var changed = true; if (_root is TopLevel topLevel && topLevel.Renderer is { }) { topLevel.Renderer.DrawFps = value; } else if (_root is Controls.Application app && app.RendererRoot is { }) { app.RendererRoot.DrawFps = value; } else { changed = false; } if(changed) RaiseAndSetIfChanged(ref _showFpsOverlay, value); } } public void ToggleFpsOverlay() { ShowFpsOverlay = !ShowFpsOverlay; } public ConsoleViewModel Console { get; } public ViewModelBase? Content { get { return _content; } private set { if (_content is TreePageViewModel oldTree && value is TreePageViewModel newTree && oldTree?.SelectedNode?.Visual is IControl control) { // HACK: We want to select the currently selected control in the new tree, but // to select nested nodes in TreeView, currently the TreeView has to be able to // expand the parent nodes. Because at this point the TreeView isn't visible, // this will fail unless we schedule the selection to run after layout. DispatcherTimer.RunOnce( () => { try { newTree.SelectControl(control); } catch { } }, TimeSpan.FromMilliseconds(0), DispatcherPriority.ApplicationIdle); } RaiseAndSetIfChanged(ref _content, value); } } public int SelectedTab { get { return _selectedTab; } // [MemberNotNull(nameof(_content))] set { _selectedTab = value; switch (value) { case 1: Content = _visualTree; break; case 2: Content = _events; break; default: Content = _logicalTree; break; } RaisePropertyChanged(); } } public string? FocusedControl { get { return _focusedControl; } private set { RaiseAndSetIfChanged(ref _focusedControl, value); } } public IInputRoot? PointerOverRoot { get => _pointerOverRoot; private set => RaiseAndSetIfChanged( ref _pointerOverRoot , value); } public IInputElement? PointerOverElement { get { return _pointerOverElement; } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); PointerOverElementName = value?.GetType()?.Name; } } public string? PointerOverElementName { get => _pointerOverElementName; private set => RaiseAndSetIfChanged(ref _pointerOverElementName, value); } private void UpdateConsoleContext(ConsoleContext context) { context.root = _root; if (Content is TreePageViewModel tree) { context.e = tree.SelectedNode?.Visual; } } public void SelectControl(IControl control) { var tree = Content as TreePageViewModel; tree?.SelectControl(control); } public void EnableSnapshotStyles(bool enable) { if (Content is TreePageViewModel treeVm && treeVm.Details != null) { treeVm.Details.SnapshotStyles = enable; } } public void Dispose() { if (KeyboardDevice.Instance is not null) KeyboardDevice.Instance.PropertyChanged -= KeyboardPropertyChanged; _pointerOverSubscription.Dispose(); _logicalTree.Dispose(); _visualTree.Dispose(); if (_root is TopLevel top) { top.Renderer.DrawDirtyRects = false; top.Renderer.DrawFps = false; } } private void UpdateFocusedControl() { FocusedControl = KeyboardDevice.Instance?.FocusedElement?.GetType().Name; } private void KeyboardPropertyChanged(object? sender, PropertyChangedEventArgs e) { if (e.PropertyName == nameof(KeyboardDevice.Instance.FocusedElement)) { UpdateFocusedControl(); } } public void RequestTreeNavigateTo(IControl control, bool isVisualTree) { var tree = isVisualTree ? _visualTree : _logicalTree; var node = tree.FindNode(control); if (node != null) { SelectedTab = isVisualTree ? 1 : 0; tree.SelectControl(control); } } public int? StartupScreenIndex { get; private set; } = default; [DependsOn(nameof(TreePageViewModel.SelectedNode))] [DependsOn(nameof(Content))] bool CanShot(object? parameter) { return Content is TreePageViewModel tree && tree.SelectedNode != null && tree.SelectedNode.Visual is VisualTree.IVisual visual && visual.VisualRoot != null; } async void Shot(object? parameter) { if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control && _screenshotHandler is { } ) { try { await _screenshotHandler.Take(control); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); //TODO: Notify error } } } public void SetOptions(DevToolsOptions options) { _screenshotHandler = options.ScreenshotHandler; StartupScreenIndex = options.StartupScreenIndex; ShowImplementedInterfaces = options.ShowImplementedInterfaces; } public bool ShowImplementedInterfaces { get => _showImplementedInterfaces; private set => RaiseAndSetIfChanged(ref _showImplementedInterfaces , value); } public void ToggleShowImplementedInterfaces(object parametr) { ShowImplementedInterfaces = !ShowImplementedInterfaces; if (Content is TreePageViewModel viewModel) { viewModel.UpdatePropertiesView(); } } public bool ShowDettailsPropertyType { get => _showPropertyType; private set => RaiseAndSetIfChanged(ref _showPropertyType , value); } public void ToggleShowDettailsPropertyType(object paramter) { ShowDettailsPropertyType = !ShowDettailsPropertyType; } } }