diff --git a/NewUI/Debugger/Utilities/ContextMenuAction.cs b/NewUI/Debugger/Utilities/ContextMenuAction.cs index c48871be..4a938d25 100644 --- a/NewUI/Debugger/Utilities/ContextMenuAction.cs +++ b/NewUI/Debugger/Utilities/ContextMenuAction.cs @@ -35,13 +35,15 @@ namespace Mesen.Debugger.Utilities IconFileAttribute? attr = ActionType.GetAttribute(); if(!string.IsNullOrEmpty(attr?.Icon)) { return ImageUtilities.FromAsset(attr.Icon); + } else if(IsSelected?.Invoke() == true) { + return ImageUtilities.FromAsset("Assets/MenuItemChecked.png"); } return null; } } - List? _subActions; - public List? SubActions + List? _subActions; + public List? SubActions { get => _subActions; set @@ -50,9 +52,11 @@ namespace Mesen.Debugger.Utilities if(_subActions != null) { IsEnabled = () => { - foreach(ContextMenuAction subAction in _subActions) { - if(subAction.IsEnabled == null || subAction.IsEnabled()) { - return true; + foreach(object subAction in _subActions) { + if(subAction is ContextMenuAction act) { + if(act.IsEnabled == null || act.IsEnabled()) { + return true; + } } } return false; @@ -63,6 +67,7 @@ namespace Mesen.Debugger.Utilities public Func? HintText { get; set; } public Func? IsEnabled { get; set; } + public Func? IsSelected { get; set; } public Func? Shortcut { get; set; } public string ShortcutText => Shortcut?.Invoke().ToString() ?? ""; @@ -141,6 +146,29 @@ namespace Mesen.Debugger.Utilities MoveUp, [IconFile("MoveDown")] - MoveDown + MoveDown, + + WatchDecimalDisplay, + WatchHexDisplay, + WatchBinaryDisplay, + + RowDisplayFormat, + RowFormatBinary, + RowFormatHex8Bits, + RowFormatHex16Bits, + RowFormatHex24Bits, + RowFormatSigned8Bits, + RowFormatSigned16Bits, + RowFormatSigned24Bits, + RowFormatUnsigned, + + [IconFile("Close")] + ClearFormat, + + [IconFile("Import")] + Import, + + [IconFile("Export")] + Export } } diff --git a/NewUI/Debugger/Utilities/DebugShortcutManager.cs b/NewUI/Debugger/Utilities/DebugShortcutManager.cs index faa42ac5..53308f5b 100644 --- a/NewUI/Debugger/Utilities/DebugShortcutManager.cs +++ b/NewUI/Debugger/Utilities/DebugShortcutManager.cs @@ -64,7 +64,7 @@ namespace Mesen.Debugger.Utilities } } } else { - focusParent.RemoveHandler(InputElement.KeyDownEvent, handler!); + elem.RemoveHandler(InputElement.KeyDownEvent, handler!); } } }; diff --git a/NewUI/Debugger/ViewModels/WatchListViewModel.cs b/NewUI/Debugger/ViewModels/WatchListViewModel.cs index f80a0e25..8440822b 100644 --- a/NewUI/Debugger/ViewModels/WatchListViewModel.cs +++ b/NewUI/Debugger/ViewModels/WatchListViewModel.cs @@ -15,7 +15,7 @@ namespace Mesen.Debugger.ViewModels [Reactive] public List WatchEntries { get; private set; } = new List(); [Reactive] public int SelectedIndex { get; set; } = 0; - private WatchManager _manager; + public WatchManager Manager { get; } public WatchListViewModel() : this(CpuType.Cpu) { } @@ -23,41 +23,47 @@ namespace Mesen.Debugger.ViewModels { Id = "WatchList"; Title = "Watch"; - _manager = WatchManager.GetWatchManager(cpuType); - _manager.WatchChanged += WatchListViewModel_WatchChanged; + Manager = WatchManager.GetWatchManager(cpuType); + Manager.WatchChanged += WatchListViewModel_WatchChanged; UpdateWatch(); } public void UpdateWatch() { - WatchEntries = _manager.GetWatchContent(WatchEntries); + int selection = SelectedIndex; + WatchEntries = Manager.GetWatchContent(WatchEntries); + if(selection < WatchEntries.Count) { + SelectedIndex = selection; + } else { + SelectedIndex = WatchEntries.Count - 1; + } } public void EditWatch(int index, string expression) { - _manager.UpdateWatch(index, expression); + Manager.UpdateWatch(index, expression); } public void MoveUp(int index) { - List entries = _manager.WatchEntries; + List entries = Manager.WatchEntries; if(index > 0 && index < entries.Count) { string currentEntry = entries[index]; string entryAbove = entries[index - 1]; - _manager.UpdateWatch(index - 1, currentEntry); - _manager.UpdateWatch(index, entryAbove); + Manager.UpdateWatch(index - 1, currentEntry); + Manager.UpdateWatch(index, entryAbove); SelectedIndex = index - 1; } } public void MoveDown(int index) { - List entries = _manager.WatchEntries; + List entries = Manager.WatchEntries; if(index < entries.Count - 1) { string currentEntry = entries[index]; string entryBelow = entries[index + 1]; - _manager.UpdateWatch(index + 1, currentEntry); - _manager.UpdateWatch(index, entryBelow); + Manager.UpdateWatch(index + 1, currentEntry); + Manager.UpdateWatch(index, entryBelow); SelectedIndex = index + 1; } } @@ -67,10 +73,24 @@ namespace Mesen.Debugger.ViewModels UpdateWatch(); } + private int[] GetIndexes(List items) + { + return items.Select(x => WatchEntries.IndexOf(x)).ToArray(); + } + internal void DeleteWatch(List items) { - int[] indexes = items.Select(x => WatchEntries.IndexOf(x)).ToArray(); - _manager.RemoveWatch(indexes); + Manager.RemoveWatch(GetIndexes(items)); + } + + internal void SetSelectionFormat(WatchFormatStyle format, int byteLength, List items) + { + Manager.SetSelectionFormat(format, byteLength, GetIndexes(items)); + } + + internal void ClearSelectionFormat(List items) + { + Manager.ClearSelectionFormat(GetIndexes(items)); } } } diff --git a/NewUI/Debugger/Views/WatchListView.axaml b/NewUI/Debugger/Views/WatchListView.axaml index 4c75bdbb..3edac40d 100644 --- a/NewUI/Debugger/Views/WatchListView.axaml +++ b/NewUI/Debugger/Views/WatchListView.axaml @@ -29,10 +29,12 @@ CellEditEnded="OnCellEditEnded" KeyDown="OnGridKeyDown" SelectedIndex="{CompiledBinding SelectedIndex}" + CanUserSortColumns="False" + CanUserReorderColumns="False" IsReadOnly="False" > - + { _model!.MoveDown(grid.SelectedIndex); } - } + }, + + new Separator(), + + new ContextMenuAction() { + ActionType = ActionType.RowDisplayFormat, + SubActions = new() { + new ContextMenuAction() { + ActionType = ActionType.RowFormatBinary, + OnClick = () => _model!.SetSelectionFormat(WatchFormatStyle.Binary, 1, grid.SelectedItems.Cast().ToList()) + }, + new Separator(), + new ContextMenuAction() { + ActionType = ActionType.RowFormatHex8Bits, + OnClick = () => _model!.SetSelectionFormat(WatchFormatStyle.Hex, 1, grid.SelectedItems.Cast().ToList()) + }, + new ContextMenuAction() { + ActionType = ActionType.RowFormatHex16Bits, + OnClick = () => _model!.SetSelectionFormat(WatchFormatStyle.Hex, 2, grid.SelectedItems.Cast().ToList()) + }, + new ContextMenuAction() { + ActionType = ActionType.RowFormatHex24Bits, + OnClick = () => _model!.SetSelectionFormat(WatchFormatStyle.Hex, 3, grid.SelectedItems.Cast().ToList()) + }, + new Separator(), + new ContextMenuAction() { + ActionType = ActionType.RowFormatSigned8Bits, + OnClick = () => _model!.SetSelectionFormat(WatchFormatStyle.Signed, 1, grid.SelectedItems.Cast().ToList()) + }, + new ContextMenuAction() { + ActionType = ActionType.RowFormatSigned16Bits, + OnClick = () => _model!.SetSelectionFormat(WatchFormatStyle.Signed, 2, grid.SelectedItems.Cast().ToList()) + }, + new ContextMenuAction() { + ActionType = ActionType.RowFormatSigned24Bits, + OnClick = () => _model!.SetSelectionFormat(WatchFormatStyle.Signed, 3, grid.SelectedItems.Cast().ToList()) + }, + new Separator(), + new ContextMenuAction() { + ActionType = ActionType.RowFormatUnsigned, + OnClick = () => _model!.SetSelectionFormat(WatchFormatStyle.Unsigned, 1, grid.SelectedItems.Cast().ToList()) + }, + new Separator(), + new ContextMenuAction() { + ActionType = ActionType.ClearFormat, + OnClick = () => _model!.ClearSelectionFormat(grid.SelectedItems.Cast().ToList()) + } + } + }, + + + new Separator(), + + new ContextMenuAction() { + ActionType = ActionType.WatchDecimalDisplay, + IsSelected = () => ConfigManager.Config.Debug.Debugger.WatchFormat == WatchFormatStyle.Unsigned, + OnClick = () => { + ConfigManager.Config.Debug.Debugger.WatchFormat = WatchFormatStyle.Unsigned; + } + }, + + new ContextMenuAction() { + ActionType = ActionType.WatchHexDisplay, + IsSelected = () => ConfigManager.Config.Debug.Debugger.WatchFormat == WatchFormatStyle.Hex, + OnClick = () => { + ConfigManager.Config.Debug.Debugger.WatchFormat = WatchFormatStyle.Hex; + } + }, + + new ContextMenuAction() { + ActionType = ActionType.WatchBinaryDisplay, + IsSelected = () => ConfigManager.Config.Debug.Debugger.WatchFormat == WatchFormatStyle.Binary, + OnClick = () => { + ConfigManager.Config.Debug.Debugger.WatchFormat = WatchFormatStyle.Binary; + } + }, + + new Separator(), + + new ContextMenuAction() { + ActionType = ActionType.Import, + OnClick = async () => { + string? filename = await FileDialogHelper.OpenFile(null, VisualRoot, FileDialogHelper.WatchFileExt); + if(filename !=null) { + _model!.Manager.Import(filename); + } + } + }, + + new ContextMenuAction() { + ActionType = ActionType.Export, + OnClick = async () => { + string? filename = await FileDialogHelper.SaveFile(null, null, VisualRoot, FileDialogHelper.WatchFileExt); + if(filename != null) { + _model!.Manager.Export(filename); + } + } + }, }); } @@ -86,11 +184,16 @@ namespace Mesen.Debugger.Views } } + private static bool IsTextKey(Key key) + { + return key >= Key.A && key <= Key.Z || key >= Key.D0 && key <= Key.D9 || key >= Key.NumPad0 && key <= Key.Divide || key >= Key.OemSemicolon && key <= Key.Oem102; + } + private void OnGridKeyDown(object sender, KeyEventArgs e) { if(e.Key == Key.Escape) { ((DataGrid)sender).CancelEdit(); - } else if(e.Key >= Key.A && e.Key <= Key.Z || e.Key >= Key.D0 && e.Key <= Key.D9) { + } else if(IsTextKey(e.Key)) { ((DataGrid)sender).CurrentColumn = ((DataGrid)sender).Columns[0]; ((DataGrid)sender).BeginEdit(); } diff --git a/NewUI/Debugger/WatchManager.cs b/NewUI/Debugger/WatchManager.cs index 0bc3339d..6d25522a 100644 --- a/NewUI/Debugger/WatchManager.cs +++ b/NewUI/Debugger/WatchManager.cs @@ -9,7 +9,7 @@ using System.Text.RegularExpressions; namespace Mesen.Debugger { - class WatchManager + public class WatchManager { public static Regex FormatSuffixRegex = new Regex(@"^(.*),\s*([B|H|S|U])([\d]){0,1}$", RegexOptions.Compiled); private static Regex _arrayWatchRegex = new Regex(@"\[((\$[0-9A-Fa-f]+)|(\d+)|([@_a-zA-Z0-9]+))\s*,\s*(\d+)\]", RegexOptions.Compiled); @@ -223,6 +223,47 @@ namespace Mesen.Debugger { File.WriteAllLines(filename, WatchEntries); } + + private string GetFormatString(WatchFormatStyle format, int byteLength) + { + string formatString = ", "; + switch(format) { + case WatchFormatStyle.Binary: formatString += "B"; break; + case WatchFormatStyle.Hex: formatString += "H"; break; + case WatchFormatStyle.Signed: formatString += "S"; break; + case WatchFormatStyle.Unsigned: formatString += "U"; break; + default: throw new Exception("Unsupported type"); + } + if(byteLength > 1) { + formatString += byteLength.ToString(); + } + return formatString; + } + + internal void ClearSelectionFormat(int[] indexes) + { + SetSelectionFormat("", indexes); + } + + public void SetSelectionFormat(WatchFormatStyle format, int byteLength, int[] indexes) + { + string formatString = GetFormatString(format, byteLength); + SetSelectionFormat(formatString, indexes); + } + + private void SetSelectionFormat(string formatString, int[] indexes) + { + foreach(int i in indexes) { + if(i < _watchEntries.Count) { + Match match = WatchManager.FormatSuffixRegex.Match(_watchEntries[i]); + if(match.Success) { + UpdateWatch(i, match.Groups[1].Value + formatString); + } else { + UpdateWatch(i, _watchEntries[i] + formatString); + } + } + } + } } public class WatchValueInfo diff --git a/NewUI/Debugger/Windows/MemoryToolsWindow.axaml.cs b/NewUI/Debugger/Windows/MemoryToolsWindow.axaml.cs index e157ba78..d02a7f6e 100644 --- a/NewUI/Debugger/Windows/MemoryToolsWindow.axaml.cs +++ b/NewUI/Debugger/Windows/MemoryToolsWindow.axaml.cs @@ -223,7 +223,7 @@ namespace Mesen.Debugger.Windows return new ContextMenuAction() { ActionType = ActionType.MarkSelectionAs, HintText = () => GetAddressRange(), - SubActions = new List() { + SubActions = new() { new ContextMenuAction() { ActionType = ActionType.MarkAsCode, IsEnabled = () => GetMarkStartEnd(out _, out _), diff --git a/NewUI/Localization/resources.en.xml b/NewUI/Localization/resources.en.xml index 78ec2446..609e5618 100644 --- a/NewUI/Localization/resources.en.xml +++ b/NewUI/Localization/resources.en.xml @@ -1531,6 +1531,24 @@ x == [$150] || y == [10] Code Data Unknown + + Decimal Display + Hexadecimal Display + Binary Display + + Row Display Format + Binary + Hexadecimal (8-bit) + Hexadecimal (16-bit) + Hexadecimal (24-bit) + Signed decimal (8-bit) + Signed decimal (16-bit) + Signed decimal (24-bit) + Unsigned decimal + Clear + + Import... + Export... \ No newline at end of file diff --git a/NewUI/Utilities/FileDialogHelper.cs b/NewUI/Utilities/FileDialogHelper.cs index 35d52db4..a0df81fc 100644 --- a/NewUI/Utilities/FileDialogHelper.cs +++ b/NewUI/Utilities/FileDialogHelper.cs @@ -20,6 +20,7 @@ namespace Mesen.Utilities public static string AviExt = "avi"; public static string WaveExt = "wav"; public static string MesenSaveStateExt = "mss"; + public static string WatchFileExt = "txt"; public static async Task OpenFile(string? initialFolder, IRenderRoot? parent, params string[] extensions) {