using Dock.Avalonia.Controls; using Dock.Model.Core; using Mesen.Debugger.ViewModels; using Mesen.Debugger.ViewModels.DebuggerDock; using System; using System.Collections.Generic; using System.Linq; using Dock.Model.Controls; using Mesen.Debugger.StatusViews; using Dock.Model.Mvvm; using Dock.Model.Mvvm.Controls; using Dock.Model.Mvvm.Core; namespace Mesen.Debugger { public class DebuggerDockFactory : Factory { public ToolContainerViewModel DisassemblyTool { get; private set; } public ToolContainerViewModel SourceViewTool { get; private set; } public ToolContainerViewModel StatusTool { get; private set; } public ToolContainerViewModel BreakpointListTool { get; private set; } public ToolContainerViewModel WatchListTool { get; private set; } public ToolContainerViewModel CallStackTool { get; private set; } public ToolContainerViewModel LabelListTool { get; private set; } public ToolContainerViewModel FunctionListTool { get; private set; } public ToolContainerViewModel FindResultListTool { get; private set; } public ToolContainerViewModel ControllerListTool { get; private set; } private DockEntryDefinition? _savedRootDef; public DebuggerDockFactory(DockEntryDefinition? savedRootDef) { DisassemblyTool = new("Disassembly"); DisassemblyTool.CanClose = false; SourceViewTool = new("Source View"); SourceViewTool.CanClose = false; StatusTool = new("Status"); BreakpointListTool = new("Breakpoints"); WatchListTool = new("Watch"); CallStackTool = new("Call Stack"); LabelListTool = new("Labels"); FunctionListTool = new("Functions"); FindResultListTool = new("Find Results"); ControllerListTool = new("Controllers"); _savedRootDef = savedRootDef; } public override IRootDock CreateLayout() { if(_savedRootDef != null) { //Restore previous layout try { if(FromDockDefinition(_savedRootDef) is IRootDock savedRootLayout) { return savedRootLayout; } } catch { //Reset layout if any error occurs } } return GetDefaultLayout(); } public IRootDock GetDefaultLayout() { var mainLayout = new ProportionalDock { Orientation = Orientation.Vertical, VisibleDockables = CreateList( new ProportionalDock { Proportion = 0.75, Orientation = Orientation.Horizontal, ActiveDockable = null, VisibleDockables = CreateList( new ToolDock { Proportion = 0.60, VisibleDockables = CreateList(DisassemblyTool, SourceViewTool) }, new MesenProportionalDockSplitter(), new ProportionalDock { Proportion = 0.40, Orientation = Orientation.Vertical, VisibleDockables = CreateList( new ToolDock { Proportion = 0.5, VisibleDockables = CreateList(StatusTool) }, new MesenProportionalDockSplitter(), new ToolDock { Proportion = 0.5, VisibleDockables = CreateList(LabelListTool, FunctionListTool, FindResultListTool, ControllerListTool) } ) } ) }, new MesenProportionalDockSplitter(), new ProportionalDock { Proportion = 0.25, Orientation = Orientation.Horizontal, VisibleDockables = CreateList( new ToolDock { Proportion = 0.33, VisibleDockables = CreateList(WatchListTool) }, new MesenProportionalDockSplitter(), new ToolDock { Proportion = 0.33, VisibleDockables = CreateList(BreakpointListTool) }, new MesenProportionalDockSplitter(), new ToolDock { Proportion = 0.33, VisibleDockables = CreateList(CallStackTool) } ) } ) }; var root = CreateRootDock(); root.ActiveDockable = mainLayout; root.DefaultDockable = mainLayout; root.VisibleDockables = CreateList(mainLayout); return root; } public override IProportionalDockSplitter CreateProportionalDockSplitter() { return new MesenProportionalDockSplitter(); } public override void InitLayout(IDockable layout) { this.ContextLocator = new Dictionary> { }; this.HostWindowLocator = new Dictionary> { [nameof(IDockWindow)] = () => new HostWindow() }; this.DockableLocator = new Dictionary> { }; base.InitLayout(layout); } public DockEntryDefinition ToDockDefinition(IDockable dockable) { DockEntryDefinition entry = new(); if(dockable is MesenProportionalDockSplitter) { entry.Type = DockEntryType.Splitter; } else if(dockable is IDock dock) { if(dock is IRootDock) { entry.Type = DockEntryType.Root; } else if(dock is IProportionalDock propDock) { entry.Type = DockEntryType.ProportionalDock; entry.Orientation = propDock.Orientation; } else { entry.Type = DockEntryType.ToolDock; } entry.Name = dock.Title; entry.Proportion = double.IsNaN(dock.Proportion) ? 0 : dock.Proportion; entry.Children = new(); if(dock.VisibleDockables != null) { if(dock is IProportionalDock propDock && dock.VisibleDockables.Count == 1) { //Remove empty proportional docks (these seem to get created when moving things around) DockEntryDefinition innerEntry = ToDockDefinition(dock.VisibleDockables[0]); //Keep the outer layer's proportion (inner will be set to 1, which will cause issues) innerEntry.Proportion = entry.Proportion; return innerEntry; } if(dock.ActiveDockable != null) { int index = dock.VisibleDockables.IndexOf(dock.ActiveDockable); if(index >= 0) { entry.SelectedIndex = index; } } foreach(IDockable child in dock.VisibleDockables) { entry.Children.Add(ToDockDefinition(child)); } } } else if(dockable is ITool tool) { entry.Type = DockEntryType.Tool; entry.Name = tool.Title; entry.ToolTypeName = tool.GetType().GetGenericArguments()[0].Name; } return entry; } public IDockable? FromDockDefinition(DockEntryDefinition def) { IDockable? dockable = null; switch(def.Type) { case DockEntryType.Splitter: return CreateProportionalDockSplitter(); case DockEntryType.Root: dockable = CreateRootDock(); break; case DockEntryType.ToolDock: dockable = CreateToolDock(); break; case DockEntryType.ProportionalDock: { IProportionalDock propDock = CreateProportionalDock(); propDock.Orientation = def.Orientation; dockable = propDock; break; } case DockEntryType.Tool: switch(def.ToolTypeName) { case nameof(DisassemblyViewModel): return DisassemblyTool; case nameof(SourceViewViewModel): return SourceViewTool; case nameof(BaseConsoleStatusViewModel): return StatusTool; case nameof(BreakpointListViewModel): return BreakpointListTool; case nameof(WatchListViewModel): return WatchListTool; case nameof(CallStackViewModel): return CallStackTool; case nameof(LabelListViewModel): return LabelListTool; case nameof(FunctionListViewModel): return FunctionListTool; case nameof(FindResultListViewModel): return FindResultListTool; case nameof(ControllerListViewModel): return ControllerListTool; } break; } IDock? dock = dockable as IDock; if(dock != null && def.Proportion != 0) { dock.Proportion = def.Proportion; } if(dock != null && def.Children != null) { dock.VisibleDockables = CreateList(); foreach(DockEntryDefinition childDef in def.Children) { IDockable? child = FromDockDefinition(childDef); if(child != null) { dock.VisibleDockables.Add(child); } } dock.ActiveDockable = def.SelectedIndex < dock.VisibleDockables.Count ? dock.VisibleDockables[def.SelectedIndex] : dock.VisibleDockables[0]; dock.DefaultDockable = dock.VisibleDockables[0]; } return dockable; } } public enum DockEntryType { Root, ProportionalDock, ToolDock, Splitter, Tool } public class DockEntryDefinition { public DockEntryType Type { get; set; } public double Proportion { get; set; } = 0; public Orientation Orientation { get; set; } = Orientation.Horizontal; public string Name { get; set; } = ""; public string ToolTypeName { get; set; } = ""; public int SelectedIndex { get; set; } = 0; public List? Children { get; set; } } } public class MesenProportionalDockSplitter : DockBase, IProportionalDockSplitter { //The regular ProportionalDockSplitter in Dock.Model.Mvvm.Controls inherits from DockableBase, which causes an exception when styles are applied }