mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Debugger: Tooltips for disassembly + minor refresh tweaks/refactoring
This commit is contained in:
parent
997dbb00c9
commit
ba73872ed7
19 changed files with 372 additions and 77 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ enum class ConsoleNotificationType
|
|||
GamePaused,
|
||||
GameResumed,
|
||||
CodeBreak,
|
||||
DebuggerResumed,
|
||||
PpuFrameDone,
|
||||
ResolutionChanged,
|
||||
ConfigChanged,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}" />
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -142,6 +142,9 @@ namespace Mesen.Debugger.Labels
|
|||
|
||||
public AddressInfo GetRelativeAddress(CpuType cpuType)
|
||||
{
|
||||
if(MemoryType.IsRelativeMemory()) {
|
||||
return GetAbsoluteAddress();
|
||||
}
|
||||
return DebugApi.GetRelativeAddress(GetAbsoluteAddress(), cpuType);
|
||||
}
|
||||
|
||||
|
|
89
NewUI/Debugger/Utilities/CodeTooltipHelper.cs
Normal file
89
NewUI/Debugger/Utilities/CodeTooltipHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
ShowByteCode="{CompiledBinding Config.Debugger.ShowByteCode}"
|
||||
RowClicked="Diassembly_RowClicked"
|
||||
PointerWheelChanged="Disassembly_PointerWheelChanged"
|
||||
CodePointerMoved="Disassembly_CodePointerMoved"
|
||||
/>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ namespace Mesen.Interop
|
|||
GamePaused,
|
||||
GameResumed,
|
||||
CodeBreak,
|
||||
DebuggerResumed,
|
||||
PpuFrameDone,
|
||||
ResolutionChanged,
|
||||
ConfigChanged,
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue