using Avalonia.Controls; using Avalonia.Input; using Avalonia.Threading; using Mesen.Config; using Mesen.Config.Shortcuts; using Mesen.Controls; using Mesen.Interop; using Mesen.Localization; using Mesen.Utilities; using Mesen.ViewModels; using ReactiveUI; using ReactiveUI.Fody.Helpers; using System; using System.Collections.Generic; using System.Reactive; using System.Threading.Tasks; namespace Mesen.Debugger.Utilities { public abstract class BaseMenuAction : ViewModelBase, IDisposable { public ActionType ActionType; public string? CustomText { get; set; } public Func? DynamicText { get; set; } public Func? DynamicIcon { get; set; } public virtual string Name { get { if(ActionType == ActionType.Custom) { if(DynamicText != null) { return DynamicText(); } else { return CustomText ?? ""; } } string label = ResourceHelper.GetEnumText(ActionType); if(HintText != null) { string hint = HintText(); if(!string.IsNullOrWhiteSpace(hint)) { label += " (" + hint + ")"; } } return label; } } public Image? Icon { get { IconFileAttribute? attr = ActionType.GetAttribute(); if(!string.IsNullOrEmpty(attr?.Icon)) { return ImageUtilities.FromAsset(attr.Icon); } else if(DynamicIcon != null) { return ImageUtilities.FromAsset("Assets/" + DynamicIcon() + ".png"); } else if(IsSelected?.Invoke() == true) { return ImageUtilities.FromAsset("Assets/MenuItemChecked.png"); } return null; } } List? _subActions; public List? SubActions { get => _subActions; set { _subActions = value; if(_subActions != null) { Func? isEnabled = IsEnabled; IsEnabled = () => { if(isEnabled != null && !isEnabled()) { return false; } foreach(object subAction in _subActions) { if(subAction is BaseMenuAction act) { if(act.IsEnabled == null || act.IsEnabled()) { return true; } } } return false; }; } } } public Func? HintText { get; set; } public Func? IsEnabled { get; set; } public Func? IsSelected { get; set; } public Func? IsVisible { get; set; } public bool AllowedWhenHidden { get; set; } public abstract string ShortcutText { get; } [Reactive] public string ActionName { get; set; } = ""; [Reactive] public Image? ActionIcon { get; set; } [Reactive] public bool Enabled { get; set; } [Reactive] public bool Visible { get; set; } private ReactiveCommand? _clickCommand; public ReactiveCommand? ClickCommand { get { Update(); return _clickCommand; } } private Action _onClick = () => { }; public Action OnClick { get => _onClick; set { _onClick = () => { if((IsVisible == null || AllowedWhenHidden || IsVisible()) && (IsEnabled == null || IsEnabled())) { if(ActionType == ActionType.Exit) { //When using exit, the command is disposed while the command is running, which causes a crash //Run the code in a posted action to prevent the crash Dispatcher.UIThread.Post(() => { value(); }); } else { value(); } } }; _clickCommand = ReactiveCommand.Create(_onClick, this.WhenAnyValue(x => x.Enabled)); } } public void Update() { ActionName = Name; ActionIcon = Icon; Enabled = IsEnabled?.Invoke() ?? true; Visible = IsVisible?.Invoke() ?? true; } public virtual void Dispose() { _onClick = () => { }; _clickCommand?.Dispose(); _clickCommand = null; IsSelected = null; IsEnabled = null; IsVisible = null; if(_subActions != null) { foreach(object subAction in _subActions) { if(subAction is BaseMenuAction action) { action.Dispose(); } } } } } public class MainMenuAction : BaseMenuAction { public EmulatorShortcut? Shortcut { get; set; } public uint ShortcutParam { get; set; } public Func? CustomShortcutText { get; set; } public MainMenuAction() { } public MainMenuAction(EmulatorShortcut? shortcut) { if(shortcut.HasValue) { Shortcut = shortcut.Value; IsEnabled = () => EmuApi.IsShortcutAllowed(shortcut.Value, ShortcutParam); OnClick = () => { //Run outside the UI thread to avoid deadlocks, etc. Task.Run(() => { EmuApi.ExecuteShortcut(new ExecuteShortcutParams() { Shortcut = shortcut.Value, Param = ShortcutParam }); }); }; } } public override string ShortcutText { get { if(CustomShortcutText != null) { return CustomShortcutText(); } else { return Shortcut.HasValue ? Shortcut.Value.GetShortcutKeys()?.ToString() ?? "" : ""; } } } } public class ContextMenuAction : BaseMenuAction { public Func? Shortcut { get; set; } public override string ShortcutText => Shortcut?.Invoke().ToString() ?? ""; public override void Dispose() { base.Dispose(); Shortcut = null; } } public class ContextMenuSeparator : ContextMenuAction { public override string Name => "-"; } public enum ActionType { Custom, [IconFile("Copy")] Copy, [IconFile("Paste")] Paste, [IconFile("SelectAll")] SelectAll, [IconFile("EditLabel")] EditLabel, [IconFile("Add")] AddWatch, [IconFile("BreakpointEnableDisable")] EditBreakpoint, MarkSelectionAs, [IconFile("Accept")] MarkAsCode, [IconFile("CheatCode")] MarkAsData, [IconFile("Help")] MarkAsUnidentified, [IconFile("Add")] Add, [IconFile("Edit")] Edit, [IconFile("Close")] Delete, [IconFile("Breakpoint")] AddBreakpoint, [IconFile("MoveUp")] MoveUp, [IconFile("MoveDown")] MoveDown, WatchDecimalDisplay, WatchHexDisplay, WatchBinaryDisplay, RowDisplayFormat, RowFormatBinary, RowFormatHex8Bits, RowFormatHex16Bits, RowFormatHex24Bits, RowFormatSigned8Bits, RowFormatSigned16Bits, RowFormatSigned24Bits, RowFormatUnsigned, [IconFile("Close")] ClearFormat, [IconFile("Import")] Import, [IconFile("Export")] Export, ShowConsoleStatus, ShowBreakpointList, ShowWatchList, ShowLabelList, ShowCallStack, ShowSettingsPanel, ShowMemoryMappings, [IconFile("Settings")] Preferences, [IconFile("MediaPlay")] Continue, [IconFile("MediaPause")] Break, [IconFile("StepInto")] StepInto, [IconFile("StepOver")] StepOver, [IconFile("StepOut")] StepOut, [IconFile("StepBack")] StepBack, [IconFile("RunPpuCycle")] RunPpuCycle, [IconFile("RunPpuScanline")] RunPpuScanline, [IconFile("RunPpuFrame")] RunPpuFrame, [IconFile("StepNmi")] RunToNmi, [IconFile("StepIrq")] RunToIrq, BreakIn, BreakOn, [IconFile("Refresh")] Reset, [IconFile("PowerCycle")] PowerCycle, [IconFile("Script")] NewScript, [IconFile("MediaPlay")] RunScript, [IconFile("MediaStop")] StopScript, [IconFile("Folder")] Open, [IconFile("SaveFloppy")] Save, SaveAs, [IconFile("Exit")] Exit, [IconFile("Help")] HelpApiReference, RecentScripts, [IconFile("Refresh")] Refresh, EnableAutoRefresh, RefreshOnBreakPause, ZoomIn, ZoomOut, [IconFile("Expand")] GoToLocation, [IconFile("Export")] ExportToPng, [IconFile("VerticalLayout")] ViewInTileViewer, [IconFile("CheatCode")] ViewInMemoryViewer, LoadTblFile, ResetTblMappings, GoToAddress, GoToAll, [IconFile("Debugger")] OpenDebugger, [IconFile("EventViewer")] OpenEventViewer, [IconFile("CheatCode")] OpenMemoryTools, [IconFile("RegisterIcon")] OpenRegisterViewer, [IconFile("LogWindow")] OpenTraceLogger, [IconFile("VideoOptions")] OpenTilemapViewer, [IconFile("VerticalLayout")] OpenTileViewer, [IconFile("PerfTracker")] OpenSpriteViewer, [IconFile("VideoFilter")] OpenPaletteViewer, [IconFile("Chip")] OpenAssembler, [IconFile("LogWindow")] OpenDebugLog, [IconFile("Speed")] OpenProfiler, [IconFile("Script")] OpenScriptWindow, [IconFile("Settings")] OpenDebugSettings, [IconFile("SpcDebugger")] OpenSpcDebugger, [IconFile("Cx4Debugger")] OpenCx4Debugger, [IconFile("NecDspDebugger")] OpenNecDspDebugger, [IconFile("GsuDebugger")] OpenGsuDebugger, [IconFile("Sa1Debugger")] OpenSa1Debugger, [IconFile("GameboyDebugger")] OpenGameboyDebugger, [IconFile("CheatCode")] Cheats, [IconFile("HistoryViewer")] HistoryViewer, [IconFile("Movie")] Movies, [IconFile("MediaPlay")] Play, [IconFile("Record")] Record, [IconFile("MediaStop")] Stop, [IconFile("Network")] NetPlay, Connect, Disconnect, StartServer, StopServer, SelectController, [IconFile("Microphone")] SoundRecorder, [IconFile("VideoRecorder")] VideoRecorder, [IconFile("HdPack")] HdPacks, [IconFile("Import")] InstallHdPack, [IconFile("HdPack")] HdPackBuilder, [IconFile("LogWindow")] LogWindow, [IconFile("Camera")] TakeScreenshot, [IconFile("Help")] OnlineHelp, [IconFile("Update")] CheckForUpdates, [IconFile("Exclamation")] About, [IconFile("Comment")] ReportBug, [IconFile("Speed")] Speed, NormalSpeed, DoubleSpeed, TripleSpeed, MaximumSpeed, HalfSpeed, QuarterSpeed, IncreaseSpeed, DecreaseSpeed, ShowFps, [IconFile("Fullscreen")] VideoScale, Fullscreen, [IconFile("VideoFilter")] VideoFilter, [IconFile("WebBrowser")] Region, [IconFile("Audio")] Audio, [IconFile("DipSwitches")] Emulation, [IconFile("Controller")] Input, [IconFile("VideoOptions")] Video, [IconFile("NesIcon")] Nes, [IconFile("SnesIcon")] Snes, [IconFile("GameboyIcon")] Gameboy, [IconFile("MediaPause")] Pause, [IconFile("MediaStop")] PowerOff, [IconFile("MediaPlay")] Resume, [IconFile("SaveFloppy")] SelectDisk, [IconFile("MediaEject")] EjectDisk, [IconFile("Coins")] InsertCoin1, [IconFile("Coins")] InsertCoin2, [IconFile("Coins")] InsertCoin3, [IconFile("Coins")] InsertCoin4, SaveState, LoadState, [IconFile("SplitView")] SaveStateDialog, [IconFile("SaveFloppy")] SaveStateToFile, [IconFile("SplitView")] LoadStateDialog, [IconFile("Folder")] LoadStateFromFile, RecentFiles, LoadLastSession, [IconFile("SaveFloppy")] SaveRomAs, [IconFile("CheatCode")] SaveEditsAsIps, [IconFile("Breakpoint")] ToggleBreakpoint, [IconFile("Edit")] EditSelectedCode, [IconFile("Debugger")] ViewInDebugger, [IconFile("Breakpoint")] SetBreakpoint, [IconFile("Close")] RemoveBreakpoint, [IconFile("BreakpointEnableDisable")] EnableBreakpoint, [IconFile("BreakpointEnableDisable")] DisableBreakpoint, [IconFile("Edit")] CodeWindowEditBreakpoint, CodeDataLogger, [IconFile("Refresh")] ResetCdl, [IconFile("Folder")] LoadCdl, [IconFile("SaveFloppy")] SaveCdl, MoveProgramCounter, ShowToolbar, [IconFile("Refresh")] ResetWorkspace, [IconFile("TabContent")] Workspace, [IconFile("Import")] ImportLabels, [IconFile("Export")] ExportLabels, [IconFile("Import")] ImportWatchEntries, [IconFile("Export")] ExportWatchEntries, } }