Debugger: Tooltips for disassembly + minor refresh tweaks/refactoring

This commit is contained in:
Sour 2022-01-07 21:05:13 -05:00
parent 997dbb00c9
commit ba73872ed7
19 changed files with 372 additions and 77 deletions

View file

@ -227,6 +227,7 @@ void Debugger::SleepUntilResume(BreakSource source, MemoryOperationInfo *operati
_executionStopped = true;
bool notificationSent = false;
if(source != BreakSource::Unspecified || _breakRequestCount == 0) {
_emu->GetSoundMixer()->StopAudio();
@ -239,12 +240,17 @@ void Debugger::SleepUntilResume(BreakSource source, MemoryOperationInfo *operati
}
_waitForBreakResume = true;
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::CodeBreak, &evt);
notificationSent = true;
}
while((_waitForBreakResume && !_suspendRequestCount) || _breakRequestCount) {
std::this_thread::sleep_for(std::chrono::duration<int, std::milli>(_breakRequestCount ? 1 : 10));
}
if(notificationSent) {
_emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::DebuggerResumed);
}
_executionStopped = false;
}

View file

@ -11,6 +11,7 @@ enum class ConsoleNotificationType
GamePaused,
GameResumed,
CodeBreak,
DebuggerResumed,
PpuFrameDone,
ResolutionChanged,
ConfigChanged,

View file

@ -53,18 +53,18 @@ namespace Mesen.Debugger.Controls
set { SetValue(ShowByteCodeProperty, value); }
}
public delegate void RowClickedEventHandler(DisassemblyViewer sender, RowClickedEventArgs args);
public event RowClickedEventHandler? RowClicked;
public delegate void CodePointerMovedEventHandler(DisassemblyViewer sender, CodePointerMovedEventArgs args);
public event CodePointerMovedEventHandler? CodePointerMoved;
private Typeface Font { get; set; }
private Size LetterSize { get; set; }
private double RowHeight => this.LetterSize.Height;
public int GetVisibleRowCount()
{
InitFontAndLetterSize();
return (int)Math.Ceiling(Bounds.Height / RowHeight);
}
public delegate void RowClickedEventHandler(DisassemblyViewer sender, RowClickedEventArgs args);
public event RowClickedEventHandler? RowClicked;
private List<CodeSegmentInfo> _visibleCodeSegments = new();
private Point _previousPointerPos;
private CodeSegmentInfo? _prevPointerOverSegment = null;
static DisassemblyViewer()
{
@ -89,6 +89,47 @@ namespace Mesen.Debugger.Controls
InvalidateVisual();
}
protected override void OnPointerMoved(PointerEventArgs e)
{
base.OnPointerMoved(e);
Point p = e.GetCurrentPoint(this).Position;
if(_previousPointerPos == p) {
//Pointer didn't move, don't trigger the pointer event
return;
}
_previousPointerPos = p;
foreach(var codeSegment in _visibleCodeSegments) {
if(codeSegment.Bounds.Contains(p)) {
//Don't trigger an event if this is the same segment
if(_prevPointerOverSegment != codeSegment) {
CodePointerMoved?.Invoke(this, new CodePointerMovedEventArgs(codeSegment));
_prevPointerOverSegment = codeSegment;
}
return;
}
}
_prevPointerOverSegment = null;
CodePointerMoved?.Invoke(this, new CodePointerMovedEventArgs(null));
}
protected override void OnPointerLeave(PointerEventArgs e)
{
base.OnPointerLeave(e);
_previousPointerPos = new Point(0, 0);
_prevPointerOverSegment = null;
CodePointerMoved?.Invoke(this, new CodePointerMovedEventArgs(null));
}
public int GetVisibleRowCount()
{
InitFontAndLetterSize();
return (int)Math.Ceiling(Bounds.Height / RowHeight);
}
private void InitFontAndLetterSize()
{
this.Font = new Typeface(new FontFamily(this.FontFamily));
@ -132,6 +173,8 @@ namespace Mesen.Debugger.Controls
context.DrawLine(ColorHelper.GetPen(Colors.LightGray), new Point(addressMargin + byteCodeMargin, 0), new Point(addressMargin + byteCodeMargin, Bounds.Height));
}
_visibleCodeSegments.Clear();
//Draw code
foreach(CodeLineData line in lines) {
LineProperties lineStyle = styleProvider.GetLineStyle(line, 0);
@ -184,8 +227,10 @@ namespace Mesen.Debugger.Controls
}
foreach(CodeColor part in lineParts) {
Point pos = new Point(x + codeIndent, y);
text.Text = part.Text;
context.DrawText(ColorHelper.GetBrush(part.Color), new Point(x + codeIndent, y), text);
context.DrawText(ColorHelper.GetBrush(part.Color), pos, text);
_visibleCodeSegments.Add(new CodeSegmentInfo(part.Text, part.Type, text.Bounds.Translate(new Vector(pos.X, pos.Y))));
x += text.Bounds.Width;
}
}
@ -229,6 +274,30 @@ namespace Mesen.Debugger.Controls
}
}
public class CodePointerMovedEventArgs : EventArgs
{
public CodePointerMovedEventArgs(CodeSegmentInfo? codeSegment)
{
this.CodeSegment = codeSegment;
}
public CodeSegmentInfo? CodeSegment { get; }
}
public class CodeSegmentInfo
{
public CodeSegmentInfo(string text, CodeSegmentType type, Rect bounds)
{
this.Text = text;
this.Type = type;
this.Bounds = bounds;
}
public string Text { get; }
public CodeSegmentType Type { get; }
public Rect Bounds { get; }
}
public class RowClickedEventArgs
{
public CodeLineData CodeLineData { get; private set; }
@ -296,9 +365,29 @@ namespace Mesen.Debugger.Controls
Plus = 16
}
public enum CodeSegmentType
{
OpCode,
ImmediateValue,
Address,
Label,
EffectiveAddress,
MemoryValue,
None,
Syntax,
}
public class CodeColor
{
public string Text = "";
public string Text;
public Color Color;
public CodeSegmentType Type;
public CodeColor(string text, Color color, CodeSegmentType type)
{
Text = text;
Color = color;
Type = type;
}
}
}

View file

@ -21,11 +21,22 @@
<ItemsPresenter.ItemTemplate>
<DataTemplate DataType="dc:TooltipEntry">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" FontWeight="Bold" MinWidth="100" Margin="0 2 10 2" />
<TextBlock
Name="Header"
Text="{Binding Name}"
FontWeight="Bold"
MinWidth="{Binding FirstColumnWidth, ElementName=root}"
IsVisible="{Binding Name.Length}"
Margin="0 2 10 2"
/>
<ContentControl Content="{Binding Value}">
<ContentControl.DataTemplates>
<DataTemplate DataType="x:String">
<TextBlock Text="{Binding}" />
<TextBlock
Text="{Binding}"
FontFamily="{Binding $parent[StackPanel].DataContext.Font}"
FontSize="12"
/>
</DataTemplate>
<DataTemplate DataType="x:Boolean">
<CheckBox IsChecked="{Binding}" />

View file

@ -1,22 +1,20 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Mesen.Config;
using Mesen.Utilities;
using Mesen.ViewModels;
using Mesen.Windows;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace Mesen.Debugger.Controls
{
public class DynamicTooltip : UserControl
{
public static readonly StyledProperty<TooltipEntries> ItemsProperty = AvaloniaProperty.Register<DynamicTooltip, TooltipEntries>(nameof(Items));
public static readonly StyledProperty<int> FirstColumnWidthProperty = AvaloniaProperty.Register<DynamicTooltip, int>(nameof(FirstColumnWidth));
public TooltipEntries Items
{
@ -24,6 +22,19 @@ namespace Mesen.Debugger.Controls
set { SetValue(ItemsProperty, value); }
}
public int FirstColumnWidth
{
get { return GetValue(FirstColumnWidthProperty); }
set { SetValue(FirstColumnWidthProperty, value); }
}
static DynamicTooltip()
{
ItemsProperty.Changed.AddClassHandler<DynamicTooltip>((x, e) => {
x.ComputeColumnWidth();
});
}
public DynamicTooltip()
{
InitializeComponent();
@ -33,24 +44,46 @@ namespace Mesen.Debugger.Controls
{
AvaloniaXamlLoader.Load(this);
}
private void ComputeColumnWidth()
{
//TODO, remove hardcoded font name+size+weight
int maxWidth = 0;
var text = new FormattedText("", new Typeface("Microsoft Sans Serif", FontStyle.Normal, FontWeight.Bold), 11, TextAlignment.Left, TextWrapping.NoWrap, Size.Empty);
foreach(var item in Items) {
text.Text = item.Name;
maxWidth = Math.Max(maxWidth, (int)text.Bounds.Width);
}
FirstColumnWidth = maxWidth;
}
}
public class TooltipEntry : ReactiveObject
{
[Reactive] public string Name { get; set; } = "";
[Reactive] public object Value { get; set; } = "";
[Reactive] public FontFamily? Font { get; set; } = null;
public TooltipEntry(string name, object value)
public TooltipEntry(string name, object value, FontFamily? font = null)
{
Name = name;
Value = value;
Font = font;
}
}
public class TooltipEntries : List<TooltipEntry>
public class TooltipEntries : List<TooltipEntry>, INotifyCollectionChanged
{
private Dictionary<string, TooltipEntry> _entries = new();
public event NotifyCollectionChangedEventHandler? CollectionChanged;
[Obsolete("Do not use")]
public new void Add(TooltipEntry entry)
{
throw new NotImplementedException();
}
public void AddPicture(string name, IImage source, double zoom, PixelRect? cropRect = null)
{
if(_entries.TryGetValue(name, out TooltipEntry? entry)) {
@ -71,19 +104,23 @@ namespace Mesen.Debugger.Controls
} else {
entry = new TooltipEntry(name, new TooltipPictureEntry(source, zoom, cropRect));
_entries[entry.Name] = entry;
Add(entry);
base.Add(entry);
}
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void AddEntry(string name, object value)
public void AddEntry(string name, object value, FontFamily? font = null)
{
if(_entries.TryGetValue(name, out TooltipEntry? entry)) {
entry.Value = value;
} else {
entry = new TooltipEntry(name, value);
entry = new TooltipEntry(name, value, font);
_entries[entry.Name] = entry;
Add(entry);
base.Add(entry);
}
CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}

View file

@ -30,15 +30,32 @@ namespace Mesen.Debugger.Disassembly
Match m;
if(foundOpCode && (m = _operand.Match(codeString)).Success) {
string operand = m.Value;
Color operandColor = operand.Length > 0 ? (operand[0] == '#' ? (Color)cfg.CodeImmediateColor : (operand[0] == '$' ? (Color)cfg.CodeAddressColor : (Color)cfg.CodeLabelDefinitionColor)) : Colors.Black;
colors.Add(new CodeColor() { Text = m.Value, Color = textColor.HasValue ? textColor.Value : operandColor });
Color operandColor = Colors.Black;
CodeSegmentType type = CodeSegmentType.None;
if(operand.Length > 0) {
switch(operand[0]) {
case '#':
operandColor = cfg.CodeImmediateColor;
type = CodeSegmentType.ImmediateValue;
break;
case '$':
operandColor = cfg.CodeAddressColor;
type = CodeSegmentType.Address;
break;
default:
operandColor = cfg.CodeLabelDefinitionColor;
type = CodeSegmentType.Label;
break;
}
}
colors.Add(new CodeColor(m.Value, textColor ?? operandColor, type));
} else if(!foundOpCode && (m = _opCode.Match(codeString)).Success) {
foundOpCode = true;
colors.Add(new CodeColor() { Text = m.Value, Color = textColor.HasValue ? textColor.Value : (Color)cfg.CodeOpcodeColor });
colors.Add(new CodeColor(m.Value, textColor ?? cfg.CodeOpcodeColor, CodeSegmentType.OpCode));
} else if((m = _syntax.Match(codeString)).Success) {
colors.Add(new CodeColor() { Text = m.Value, Color = textColor.HasValue ? textColor.Value : defaultColor });
colors.Add(new CodeColor(m.Value, textColor ?? defaultColor, CodeSegmentType.Syntax));
} else if((m = _space.Match(codeString)).Success) {
colors.Add(new CodeColor() { Text = m.Value, Color = textColor.HasValue ? textColor.Value : defaultColor });
colors.Add(new CodeColor(m.Value, textColor ?? defaultColor, CodeSegmentType.None));
}
if(m.Success) {
@ -49,20 +66,24 @@ namespace Mesen.Debugger.Disassembly
}
//Display the rest of the line (used by trace logger)
colors.Add(new CodeColor() { Text = codeString, Color = defaultColor });
colors.Add(new CodeColor(codeString, defaultColor, CodeSegmentType.None));
if(lineData.EffectiveAddress >= 0) {
colors.Add(new CodeColor() { Text = " " + lineData.GetEffectiveAddressString(addressFormat), Color = cfg.CodeEffectiveAddressColor });
string effAddress = lineData.GetEffectiveAddressString(addressFormat, out CodeSegmentType type);
colors.Add(new CodeColor(" " + effAddress, cfg.CodeEffectiveAddressColor, type));
}
if(showMemoryValues && lineData.ValueSize > 0) {
colors.Add(new CodeColor() { Text = lineData.GetValueString(), Color = defaultColor });
colors.Add(new CodeColor(lineData.GetValueString(), defaultColor, CodeSegmentType.MemoryValue));
}
return colors;
} else {
Color color = codeString.EndsWith(":") ? (Color)cfg.CodeLabelDefinitionColor : (textColor ?? defaultColor);
return new List<CodeColor>() { new CodeColor() { Text = codeString, Color = color } };
if(codeString.EndsWith(":")) {
return new List<CodeColor>() { new CodeColor(codeString, cfg.CodeLabelDefinitionColor, CodeSegmentType.Label) };
} else {
return new List<CodeColor>() { new CodeColor(codeString, textColor ?? defaultColor, CodeSegmentType.None) };
}
}
}
}

View file

@ -1,4 +1,5 @@
using Mesen.Debugger.Labels;
using Mesen.Debugger.Controls;
using Mesen.Debugger.Labels;
using Mesen.Interop;
using System;
using System.Collections.Generic;
@ -30,12 +31,13 @@ namespace Mesen.Debugger
public UInt16 Value = 0;
public byte ValueSize = 0;
public string GetEffectiveAddressString(string format)
public string GetEffectiveAddressString(string format, out CodeSegmentType segmentType)
{
if(EffectiveAddress >= 0) {
AddressInfo relAddress = new AddressInfo() { Address = EffectiveAddress, Type = CpuType.ToMemoryType() };
CodeLabel? label = LabelManager.GetLabel(relAddress);
if(label != null) {
segmentType = CodeSegmentType.Label;
if(label.Length > 1) {
int gap = DebugApi.GetAbsoluteAddress(relAddress).Address - label.GetAbsoluteAddress().Address;
if(gap > 0) {
@ -44,9 +46,11 @@ namespace Mesen.Debugger
}
return "[" + label.Label + "]";
} else {
segmentType = CodeSegmentType.EffectiveAddress;
return "[$" + EffectiveAddress.ToString(format) + "]";
}
} else {
segmentType = CodeSegmentType.None;
return "";
}
}

View file

@ -17,7 +17,7 @@ namespace Mesen.Debugger
private SnesMemoryType _memoryType;
private HexEditorConfig _cfg;
private AddressCounters[] _counters = Array.Empty<AddressCounters>();
private byte[]? _cdlData;
private CdlFlags[]? _cdlData;
private bool[] _hasLabel = Array.Empty<bool>();
private TimingInfo _timing;
private ByteInfo _byteInfo = new ByteInfo();
@ -145,10 +145,10 @@ namespace Mesen.Debugger
_byteInfo.BackColor = Colors.Transparent;
if(_cdlData != null) {
if((_cdlData[index] & (byte)CdlFlags.Code) != 0 && _cfg.CodeHighlight.Highlight) {
if(_cdlData[index].HasFlag(CdlFlags.Code) && _cfg.CodeHighlight.Highlight) {
//Code
_byteInfo.BackColor = _cfg.CodeHighlight.Color;
} else if((_cdlData[index] & (byte)CdlFlags.Data) != 0 && _cfg.DataHighlight.Highlight) {
} else if(_cdlData[index].HasFlag(CdlFlags.Data) && _cfg.DataHighlight.Highlight) {
//Data
_byteInfo.BackColor = _cfg.DataHighlight.Color;
}

View file

@ -142,6 +142,9 @@ namespace Mesen.Debugger.Labels
public AddressInfo GetRelativeAddress(CpuType cpuType)
{
if(MemoryType.IsRelativeMemory()) {
return GetAbsoluteAddress();
}
return DebugApi.GetRelativeAddress(GetAbsoluteAddress(), cpuType);
}

View file

@ -0,0 +1,89 @@
using System;
using Avalonia.Controls;
using Mesen.Interop;
using Mesen.Debugger.Controls;
using Mesen.Debugger.Disassembly;
using Avalonia.Media;
using Mesen.Debugger.Labels;
using Mesen.Localization;
using Mesen.Config;
namespace Mesen.Debugger.Utilities
{
public static class CodeTooltipHelper
{
public static DynamicTooltip? GetTooltip(CpuType cpuType, string text, CodeSegmentType segmentType)
{
FontFamily monoFont = ConfigManager.Config.Debug.Font.FontFamilyObject;
int address = -1;
CodeLabel? label = null;
if(segmentType == CodeSegmentType.Address || segmentType == CodeSegmentType.EffectiveAddress) {
string addressText = text.Trim(' ', '[', ']', '$');
int.TryParse(addressText, System.Globalization.NumberStyles.HexNumber, null, out address);
} else if(segmentType == CodeSegmentType.Label) {
string labelText = text.Trim(' ', ',', ':', ']', '[');
label = LabelManager.GetLabel(labelText);
if(label != null) {
address = label.GetRelativeAddress(cpuType).Address;
}
}
if(address >= 0) {
SnesMemoryType memType = cpuType.ToMemoryType();
int byteValue = DebugApi.GetMemoryValue(memType, (uint)address);
int wordValue = (DebugApi.GetMemoryValue(memType, (uint)address + 1) << 8) | byteValue;
StackPanel mainPanel = new StackPanel() { Spacing = -4 };
mainPanel.Children.Add(GetHexDecPanel(byteValue, "X2", monoFont));
mainPanel.Children.Add(GetHexDecPanel(wordValue, "X4", monoFont));
TooltipEntries items = new();
string addressField = "$" + address.ToString("X" + cpuType.GetAddressSize()) + " (CPU)";
AddressInfo absAddr = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = address, Type = memType });
if(absAddr.Address >= 0) {
addressField += Environment.NewLine + "$" + absAddr.Address.ToString("X" + cpuType.GetAddressSize()) + " (" + ResourceHelper.GetEnumText(absAddr.Type) + ")";
}
if(label != null) {
items.AddEntry("Label", label.Label, monoFont);
}
items.AddEntry("Address", addressField, monoFont);
items.AddEntry("Value", mainPanel);
if(label?.Comment.Length > 0) {
items.AddEntry("Comment", label.Comment, monoFont);
}
bool showPreview = DebugApi.GetCdlData((uint)address, 1, memType)[0].HasFlag(CdlFlags.Code);
if(showPreview) {
items.AddEntry("", new Border() {
BorderBrush = Brushes.Gray,
BorderThickness = new(1),
Child = new DisassemblyViewer() {
Width = 300,
Height = 150,
Lines = new CodeDataProvider(cpuType).GetCodeLines(address, 40),
StyleProvider = new BaseStyleProvider(),
FontFamily = ConfigManager.Config.Debug.Font.FontFamily,
FontSize = ConfigManager.Config.Debug.Font.FontSize - 1
}
});
}
return new DynamicTooltip() { Items = items };
}
return null;
}
private static StackPanel GetHexDecPanel(int value, string format, FontFamily font)
{
StackPanel panel = new StackPanel() { Orientation = Avalonia.Layout.Orientation.Horizontal };
panel.Children.Add(new TextBlock() { Text = "$" + value.ToString(format), FontFamily = font, FontSize = 12 });
panel.Children.Add(new TextBlock() { Text = " (" + value.ToString() + ")", FontFamily = font, FontSize = 12, Foreground = Brushes.DimGray, VerticalAlignment = Avalonia.Layout.VerticalAlignment.Center });
return panel;
}
}
}

View file

@ -44,12 +44,6 @@ namespace Mesen.Debugger.ViewModels
public DebuggerWindowViewModel(CpuType? cpuType = null)
{
Config = ConfigManager.Config.Debug.Debugger;
Options = new DebuggerOptionsViewModel(Config, CpuType);
Disassembly = new DisassemblyViewModel(ConfigManager.Config.Debug);
DockFactory = new DebuggerDockFactory(this);
if(Design.IsDesignMode) {
CpuType = CpuType.Cpu;
} else if(cpuType != null) {
@ -59,6 +53,18 @@ namespace Mesen.Debugger.ViewModels
CpuType = romInfo.ConsoleType.GetMainCpuType();
}
Config = ConfigManager.Config.Debug.Debugger;
DefaultLabelHelper.SetDefaultLabels();
Options = new DebuggerOptionsViewModel(Config, CpuType);
Disassembly = new DisassemblyViewModel(ConfigManager.Config.Debug, CpuType);
BreakpointList = new BreakpointListViewModel(CpuType);
LabelList = new LabelListViewModel(CpuType);
CallStack = new CallStackViewModel(CpuType);
WatchList = new WatchListViewModel(CpuType);
DockFactory = new DebuggerDockFactory(this);
switch(CpuType) {
case CpuType.Cpu:
DockFactory.CpuStatusTool.StatusViewModel = new SnesCpuViewModel();
@ -74,12 +80,6 @@ namespace Mesen.Debugger.ViewModels
break;
}
DefaultLabelHelper.SetDefaultLabels();
BreakpointList = new BreakpointListViewModel(CpuType);
LabelList = new LabelListViewModel(CpuType);
CallStack = new CallStackViewModel(CpuType);
WatchList = new WatchListViewModel(CpuType);
DockLayout = DockFactory.CreateLayout();
DockFactory.InitLayout(DockLayout);
@ -91,16 +91,22 @@ namespace Mesen.Debugger.ViewModels
ConfigApi.SetDebuggerFlag(CpuType.GetDebuggerFlag(), true);
}
internal void Cleanup()
public void Cleanup()
{
BreakpointManager.RemoveCpuType(CpuType);
ConfigApi.SetDebuggerFlag(CpuType.GetDebuggerFlag(), false);
}
internal void UpdateDisassembly()
public void UpdateDisassembly()
{
Disassembly.DataProvider = new CodeDataProvider(CpuType);
Disassembly.SetActiveAddress(DebugUtilities.GetProgramCounter(CpuType));
Disassembly.Refresh();
}
public void ClearActiveAddress()
{
Disassembly.StyleProvider.ActiveAddress = null;
Disassembly.Refresh();
}
public void UpdateCpuPpuState()
@ -171,14 +177,6 @@ namespace Mesen.Debugger.ViewModels
}
}
private void ClearActiveAddress()
{
if(Disassembly.StyleProvider is BaseStyleProvider p) {
p.ActiveAddress = null;
Disassembly.DataProvider = new CodeDataProvider(CpuType);
}
}
public void InitializeMenu(Window wnd)
{
DebuggerConfig cfg = ConfigManager.Config.Debug.Debugger;
@ -246,7 +244,6 @@ namespace Mesen.Debugger.ViewModels
IsEnabled = isPaused,
OnClick = () => {
DebugApi.ResumeExecution();
ClearActiveAddress();
}
},
new ContextMenuAction() {
@ -331,7 +328,6 @@ namespace Mesen.Debugger.ViewModels
private void Step(int instructionCount, StepType type)
{
DebugApi.Step(CpuType, instructionCount, type);
ClearActiveAddress();
}
}
}

View file

@ -1,6 +1,7 @@
using Mesen.Config;
using Mesen.Debugger.Controls;
using Mesen.Debugger.Disassembly;
using Mesen.Interop;
using Mesen.ViewModels;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
@ -24,12 +25,14 @@ namespace Mesen.Debugger.ViewModels
private int _ignoreScrollUpdates = 0;
[Obsolete("For designer only")]
public DisassemblyViewModel(): this(new DebugConfig()) { }
public DisassemblyViewModel(): this(new DebugConfig(), CpuType.Cpu) { }
public DisassemblyViewModel(DebugConfig config)
public DisassemblyViewModel(DebugConfig config, CpuType cpuType)
{
Config = config;
DataProvider = new CodeDataProvider(cpuType);
this.WhenAnyValue(x => x.DataProvider).Subscribe(x => Refresh());
this.WhenAnyValue(x => x.TopAddress).Subscribe(x => Refresh());

View file

@ -37,6 +37,7 @@
ShowByteCode="{CompiledBinding Config.Debugger.ShowByteCode}"
RowClicked="Diassembly_RowClicked"
PointerWheelChanged="Disassembly_PointerWheelChanged"
CodePointerMoved="Disassembly_CodePointerMoved"
/>
</DockPanel>
</UserControl>

View file

@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Mesen.Debugger.Controls;
using Mesen.Debugger.Utilities;
using Mesen.Debugger.ViewModels;
using Mesen.Interop;
@ -45,6 +46,21 @@ namespace Mesen.Debugger.Views
}
}
public void Disassembly_CodePointerMoved(DisassemblyViewer sender, CodePointerMovedEventArgs e)
{
DynamicTooltip? tooltip;
ICodeDataProvider? dp = Model.DataProvider;
if(e.CodeSegment != null && dp != null && (tooltip = CodeTooltipHelper.GetTooltip(dp.CpuType, e.CodeSegment.Text, e.CodeSegment.Type)) != null) {
ToolTip.SetTip(this, tooltip);
ToolTip.SetHorizontalOffset(this, 14);
ToolTip.SetHorizontalOffset(this, 15);
ToolTip.SetIsOpen(this, true);
} else {
ToolTip.SetIsOpen(this, false);
ToolTip.SetTip(this, null);
}
}
public void Disassembly_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
{
Model.Scroll((int)(-e.Delta.Y * 3));

View file

@ -26,7 +26,7 @@ namespace Mesen.Debugger.Windows
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
this.AttachDevTools();
#endif
}
@ -39,7 +39,6 @@ namespace Mesen.Debugger.Windows
{
if(this.DataContext is DebuggerWindowViewModel model) {
_model = model;
_model.Disassembly.StyleProvider = new BaseStyleProvider();
_model.InitializeMenu(this);
_model.Config.LoadWindowSettings(this);
if(Design.IsDesignMode) {
@ -104,10 +103,18 @@ namespace Mesen.Debugger.Windows
private void _listener_OnNotification(NotificationEventArgs e)
{
if(e.NotificationType == ConsoleNotificationType.CodeBreak) {
Dispatcher.UIThread.Post(() => {
UpdateDebugger();
});
switch(e.NotificationType) {
case ConsoleNotificationType.CodeBreak:
Dispatcher.UIThread.Post(() => {
UpdateDebugger();
});
break;
case ConsoleNotificationType.DebuggerResumed:
Dispatcher.UIThread.Post(() => {
_model.ClearActiveAddress();
});
break;
}
}

View file

@ -295,10 +295,10 @@ namespace Mesen.Interop
return counts;
}
[DllImport(DllPath, EntryPoint = "GetCdlData")] private static extern void GetCdlDataWrapper(UInt32 offset, UInt32 length, SnesMemoryType memType, [In,Out] byte[] cdlData);
public static byte[] GetCdlData(UInt32 offset, UInt32 length, SnesMemoryType memType)
[DllImport(DllPath, EntryPoint = "GetCdlData")] private static extern void GetCdlDataWrapper(UInt32 offset, UInt32 length, SnesMemoryType memType, [In,Out] CdlFlags[] cdlData);
public static CdlFlags[] GetCdlData(UInt32 offset, UInt32 length, SnesMemoryType memType)
{
byte[] cdlData = new byte[length];
CdlFlags[] cdlData = new CdlFlags[length];
DebugApi.GetCdlDataWrapper(offset, length, memType, cdlData);
return cdlData;
}

View file

@ -61,6 +61,7 @@ namespace Mesen.Interop
GamePaused,
GameResumed,
CodeBreak,
DebuggerResumed,
PpuFrameDone,
ResolutionChanged,
ConfigChanged,

View file

@ -15,7 +15,7 @@
<MenuItem Header="Abcd" />
</MenuItem>
</Menu>
<CheckBox Content="Test" />
<CheckBox Content="Test" ToolTip.Tip="Tooltip test" />
<CheckBox Content="Test" IsChecked="True" />
<TabControl>
<TabItem Header="Test" />
@ -76,7 +76,11 @@
<Color x:Key="CheckBoxCheckBackgroundFillCheckedPressed">#000</Color>
<Color x:Key="CheckBoxCheckBackgroundFillUncheckedPressed">#000</Color>
<Color x:Key="CheckBoxCheckGlyphForegroundCheckedPressed">Black</Color>
<!-- dark tooltips -->
<Color x:Key="ToolTipForeground">#FFF</Color>
<Color x:Key="ToolTipBackground">#444</Color>
<!-- Misc fluent overrides -->
<x:Double x:Key="DatePickerThemeMinWidth">150</x:Double>
<x:Double x:Key="TimePickerThemeMinWidth">150</x:Double>

View file

@ -25,7 +25,7 @@
<MenuItem Header="Abcd" />
</MenuItem>
</Menu>
<CheckBox Content="Test" />
<CheckBox Content="Test" ToolTip.Tip="Test tooltip" />
<CheckBox Content="Test" IsChecked="True" />
<TabControl>
<TabItem Header="Test" />
@ -94,6 +94,9 @@
<Color x:Key="CheckBoxCheckBackgroundFillUncheckedPressed">#CCE4F7</Color>
<Color x:Key="CheckBoxCheckGlyphForegroundCheckedPressed">Black</Color>
<!-- light yellow tooltips -->
<Color x:Key="ToolTipBackground">#ffffed</Color>
<!-- Misc fluent overrides -->
<x:Double x:Key="DatePickerThemeMinWidth">150</x:Double>
<x:Double x:Key="TimePickerThemeMinWidth">150</x:Double>
@ -109,6 +112,9 @@
<CornerRadius x:Key="ControlCornerRadius">0</CornerRadius>
<CornerRadius x:Key="OverlayCornerRadius">0</CornerRadius>
<x:Double x:Key="ToolTipContentMaxWidth">800</x:Double>
<Thickness x:Key="ToolTipBorderThemePadding">3,3,3,3</Thickness>
<!-- Menu items -->
<Thickness x:Key="MenuFlyoutPresenterThemePadding">0</Thickness>
<Thickness x:Key="MenuFlyoutScrollerMargin">0</Thickness>