mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Sprite Viewer: Refactoring, fixes for tooltips, added some options
This commit is contained in:
parent
e6a79f0133
commit
7de29dcc35
11 changed files with 230 additions and 102 deletions
|
@ -112,6 +112,8 @@ struct DebugSpritePreviewInfo
|
|||
uint32_t Width;
|
||||
uint32_t Height;
|
||||
uint32_t SpriteCount;
|
||||
int32_t CoordOffsetX;
|
||||
int32_t CoordOffsetY;
|
||||
};
|
||||
|
||||
class PpuTools
|
||||
|
|
|
@ -151,6 +151,8 @@ DebugSpritePreviewInfo GbPpuTools::GetSpritePreviewInfo(GetSpritePreviewOptions
|
|||
info.Height = 256;
|
||||
info.Width = 256;
|
||||
info.SpriteCount = 40;
|
||||
info.CoordOffsetX = 0;
|
||||
info.CoordOffsetY = 0;
|
||||
return info;
|
||||
}
|
||||
|
||||
|
|
|
@ -144,9 +144,10 @@ void NesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& b
|
|||
DebugSpriteInfo sprite;
|
||||
for(int i = 0x100 - 4; i >= 0; i -= 4) {
|
||||
GetSpriteInfo(sprite, i / 4, options, state, vram, oamRam, palette);
|
||||
int spritePosY = sprite.Y + 1;
|
||||
|
||||
for(int y = 0; y < sprite.Height; y++) {
|
||||
if(sprite.Y + y >= 256) {
|
||||
if(spritePosY + y >= 256) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -157,7 +158,7 @@ void NesPpuTools::GetSpritePreview(GetSpritePreviewOptions options, BaseState& b
|
|||
|
||||
uint32_t color = sprite.SpritePreview[y * sprite.Width + x];
|
||||
if(color != 0) {
|
||||
outBuffer[((sprite.Y + y) * 256) + sprite.X + x] = color;
|
||||
outBuffer[((spritePosY + y) * 256) + sprite.X + x] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -221,7 +222,7 @@ DebugTilemapTileInfo NesPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uin
|
|||
void NesPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t i, GetSpritePreviewOptions& options, NesPpuState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette)
|
||||
{
|
||||
sprite.SpriteIndex = i;
|
||||
sprite.Y = oamRam[i * 4] + 1;
|
||||
sprite.Y = oamRam[i * 4];
|
||||
sprite.X = oamRam[i * 4 + 3];
|
||||
sprite.TileIndex = oamRam[i * 4 + 1];
|
||||
sprite.TileAddress = ((state.ControlReg & 0x08) ? 0x1000 : 0x0000) | (sprite.TileIndex * 16);
|
||||
|
@ -232,7 +233,7 @@ void NesPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t i, GetSpritePr
|
|||
sprite.HorizontalMirror = (attributes & 0x40) != 0;
|
||||
sprite.VerticalMirror = (attributes & 0x80) != 0;
|
||||
sprite.Priority = (attributes & 0x20) ? DebugSpritePriority::Background : DebugSpritePriority::Foreground;
|
||||
sprite.Visible = sprite.Y < 240;
|
||||
sprite.Visible = sprite.Y < 239;
|
||||
sprite.Width = 8;
|
||||
|
||||
bool largeSprites = (state.ControlReg & 0x20) ? true : false;
|
||||
|
@ -286,5 +287,7 @@ DebugSpritePreviewInfo NesPpuTools::GetSpritePreviewInfo(GetSpritePreviewOptions
|
|||
info.Height = 256;
|
||||
info.Width = 256;
|
||||
info.SpriteCount = 64;
|
||||
info.CoordOffsetX = 0;
|
||||
info.CoordOffsetY = 1;
|
||||
return info;
|
||||
}
|
||||
|
|
|
@ -366,5 +366,7 @@ DebugSpritePreviewInfo SnesPpuTools::GetSpritePreviewInfo(GetSpritePreviewOption
|
|||
info.Height = 256;
|
||||
info.Width = 512;
|
||||
info.SpriteCount = 128;
|
||||
info.CoordOffsetX = 256;
|
||||
info.CoordOffsetY = 0;
|
||||
return info;
|
||||
}
|
||||
|
|
|
@ -5,9 +5,13 @@ namespace Mesen.Config
|
|||
public class SpriteViewerConfig : BaseWindowConfig<SpriteViewerConfig>
|
||||
{
|
||||
[Reactive] public bool ShowSettingsPanel { get; set; } = true;
|
||||
|
||||
[Reactive] public bool ShowCoordsInHex { get; set; } = false;
|
||||
[Reactive] public bool ShowOutline { get; set; } = false;
|
||||
[Reactive] public bool HideOffscreenSprites { get; set; } = false;
|
||||
|
||||
[Reactive] public int ImageScale { get; set; } = 2;
|
||||
[Reactive] public RefreshTimingConfig RefreshTiming { get; set; } = new();
|
||||
[Reactive] public bool HideOffscreenSprites { get; set; } = false;
|
||||
|
||||
public SpriteViewerConfig()
|
||||
{
|
||||
|
|
|
@ -8,6 +8,7 @@ using Avalonia.Media;
|
|||
using Avalonia.Threading;
|
||||
using Mesen.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Mesen.Debugger.Controls
|
||||
|
@ -26,10 +27,13 @@ namespace Mesen.Debugger.Controls
|
|||
public static readonly StyledProperty<bool> AllowSelectionProperty = AvaloniaProperty.Register<PictureViewer, bool>(nameof(AllowSelection), true);
|
||||
|
||||
public static readonly StyledProperty<bool> ShowMousePositionProperty = AvaloniaProperty.Register<PictureViewer, bool>(nameof(ShowMousePosition), true);
|
||||
public static readonly StyledProperty<Rect?> MouseOverRectProperty = AvaloniaProperty.Register<PictureViewer, Rect?>(nameof(MouseOverRect), null, defaultBindingMode: BindingMode.OneWay);
|
||||
|
||||
public static readonly StyledProperty<Rect> SelectionRectProperty = AvaloniaProperty.Register<PictureViewer, Rect>(nameof(SelectionRect), Rect.Empty, defaultBindingMode: BindingMode.TwoWay);
|
||||
public static readonly StyledProperty<Rect> OverlayRectProperty = AvaloniaProperty.Register<PictureViewer, Rect>(nameof(OverlayRect), Rect.Empty);
|
||||
|
||||
public static readonly StyledProperty<List<Rect>?> HighlightRectsProperty = AvaloniaProperty.Register<PictureViewer, List<Rect>?>(nameof(HighlightRects), null);
|
||||
|
||||
public static readonly RoutedEvent<PositionClickedEventArgs> PositionClickedEvent = RoutedEvent.Register<PictureViewer, PositionClickedEventArgs>(nameof(PositionClicked), RoutingStrategies.Bubble);
|
||||
public event EventHandler<PositionClickedEventArgs> PositionClicked
|
||||
{
|
||||
|
@ -114,11 +118,25 @@ namespace Mesen.Debugger.Controls
|
|||
set { SetValue(OverlayRectProperty, value); }
|
||||
}
|
||||
|
||||
private PixelPoint? _mousePosition = null;
|
||||
public Rect? MouseOverRect
|
||||
{
|
||||
get { return GetValue(MouseOverRectProperty); }
|
||||
set { SetValue(MouseOverRectProperty, value); }
|
||||
}
|
||||
|
||||
public List<Rect>? HighlightRects
|
||||
{
|
||||
get { return GetValue(HighlightRectsProperty); }
|
||||
set { SetValue(HighlightRectsProperty, value); }
|
||||
}
|
||||
|
||||
static PictureViewer()
|
||||
{
|
||||
AffectsRender<PictureViewer>(SourceProperty, ZoomProperty, GridSizeXProperty, GridSizeYProperty, ShowGridProperty, SelectionRectProperty, OverlayRectProperty);
|
||||
AffectsRender<PictureViewer>(
|
||||
SourceProperty, ZoomProperty, GridSizeXProperty, GridSizeYProperty,
|
||||
ShowGridProperty, SelectionRectProperty, OverlayRectProperty,
|
||||
HighlightRectsProperty, MouseOverRectProperty
|
||||
);
|
||||
|
||||
SourceProperty.Changed.AddClassHandler<PictureViewer>((x, e) => {
|
||||
x.UpdateSize();
|
||||
|
@ -202,16 +220,20 @@ namespace Mesen.Debugger.Controls
|
|||
protected override void OnPointerMoved(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerMoved(e);
|
||||
PixelPoint? p = GetGridPointFromMousePoint(e.GetCurrentPoint(this).Position);
|
||||
_mousePosition = p;
|
||||
InvalidateVisual();
|
||||
if(ShowMousePosition) {
|
||||
PixelPoint? p = GetGridPointFromMousePoint(e.GetCurrentPoint(this).Position);
|
||||
if(p != null) {
|
||||
MouseOverRect = GetTileRect(p.Value);
|
||||
} else {
|
||||
MouseOverRect = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPointerLeave(PointerEventArgs e)
|
||||
{
|
||||
base.OnPointerLeave(e);
|
||||
_mousePosition = null;
|
||||
InvalidateVisual();
|
||||
MouseOverRect = null;
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
|
@ -306,21 +328,6 @@ namespace Mesen.Debugger.Controls
|
|||
DrawGrid(context, ShowGrid, GridSizeX, GridSizeY, Color.FromArgb(128, Colors.LightBlue.R, Colors.LightBlue.G, Colors.LightBlue.B));
|
||||
DrawGrid(context, ShowAltGrid, AltGridSizeX, AltGridSizeY, Color.FromArgb(128, Colors.Red.R, Colors.Red.G, Colors.Red.B));
|
||||
|
||||
if(ShowMousePosition && _mousePosition.HasValue) {
|
||||
PixelPoint p = _mousePosition.Value;
|
||||
Rect rect = ToDrawRect(GetTileRect(p));
|
||||
context.DrawRectangle(new Pen(0x40000000, 2), rect.Inflate(0.5));
|
||||
context.DrawRectangle(new Pen(Brushes.White, 2), rect.Inflate(0.5));
|
||||
}
|
||||
|
||||
if(SelectionRect != Rect.Empty) {
|
||||
Rect rect = ToDrawRect(SelectionRect);
|
||||
|
||||
DashStyle dashes = new DashStyle(DashStyle.Dash.Dashes, (double)(_stopWatch.ElapsedMilliseconds / 50) % 100 / 5);
|
||||
context.DrawRectangle(new Pen(0x40000000, 2), rect.Inflate(0.5));
|
||||
context.DrawRectangle(new Pen(Brushes.White, 2, dashes), rect.Inflate(0.5));
|
||||
}
|
||||
|
||||
if(OverlayRect != Rect.Empty) {
|
||||
Rect rect = ToDrawRect(OverlayRect);
|
||||
Brush brush = new SolidColorBrush(Colors.Gray, 0.4);
|
||||
|
@ -347,6 +354,29 @@ namespace Mesen.Debugger.Controls
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(HighlightRects?.Count > 0) {
|
||||
Pen pen = new Pen(Brushes.LightSteelBlue, 1);
|
||||
foreach(Rect highlightRect in HighlightRects) {
|
||||
Rect rect = ToDrawRect(highlightRect);
|
||||
context.DrawRectangle(pen, rect);
|
||||
}
|
||||
}
|
||||
|
||||
if(MouseOverRect != null && MouseOverRect != Rect.Empty) {
|
||||
Rect rect = ToDrawRect(MouseOverRect.Value);
|
||||
DashStyle dashes = new DashStyle(DashStyle.Dash.Dashes, 0);
|
||||
context.DrawRectangle(new Pen(Brushes.DimGray, 2), rect.Inflate(0.5));
|
||||
context.DrawRectangle(new Pen(Brushes.LightYellow, 2, dashes), rect.Inflate(0.5));
|
||||
}
|
||||
|
||||
if(SelectionRect != Rect.Empty) {
|
||||
Rect rect = ToDrawRect(SelectionRect);
|
||||
|
||||
DashStyle dashes = new DashStyle(DashStyle.Dash.Dashes, _stopWatch.ElapsedMilliseconds / 250.0);
|
||||
context.DrawRectangle(new Pen(Brushes.Black, 2), rect.Inflate(0.5));
|
||||
context.DrawRectangle(new Pen(Brushes.White, 2, dashes), rect.Inflate(0.5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,11 +16,12 @@ using ReactiveUI;
|
|||
using ReactiveUI.Fody.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Mesen.Debugger.ViewModels
|
||||
{
|
||||
public class SpriteViewerViewModel : ViewModelBase
|
||||
public class SpriteViewerViewModel : ViewModelBase, IDisposable
|
||||
{
|
||||
public SpriteViewerConfig Config { get; }
|
||||
public RefreshTimingViewModel RefreshTiming { get; }
|
||||
|
@ -28,15 +29,29 @@ namespace Mesen.Debugger.ViewModels
|
|||
public ConsoleType ConsoleType { get; }
|
||||
|
||||
[Reactive] public SpritePreviewModel? SelectedSprite { get; set; }
|
||||
[Reactive] public DynamicTooltip? PreviewPanel { get; set; }
|
||||
[Reactive] public DynamicTooltip? SelectedPreviewPanel { get; set; }
|
||||
|
||||
[Reactive] public DynamicTooltip? PreviewPanelTooltip { get; set; }
|
||||
[Reactive] public SpritePreviewModel? PreviewPanelSprite { get; set; }
|
||||
|
||||
[Reactive] public DynamicTooltip? ViewerTooltip { get; set; }
|
||||
[Reactive] public PixelPoint? ViewerMousePos { get; set; }
|
||||
|
||||
[Reactive] public DynamicBitmap ViewerBitmap { get; private set; }
|
||||
[Reactive] public Rect SelectionRect { get; set; }
|
||||
[Reactive] public Rect? MouseOverRect { get; set; }
|
||||
|
||||
[Reactive] public List<SpritePreviewModel> SpritePreviews { get; set; } = new();
|
||||
[Reactive] public List<Rect>? SpriteRects { get; set; } = null;
|
||||
|
||||
public List<object> FileMenuActions { get; } = new();
|
||||
public List<object> ViewMenuActions { get; } = new();
|
||||
|
||||
private Grid _spriteGrid;
|
||||
private BaseState? _ppuState;
|
||||
private byte[] _spriteRam = Array.Empty<byte>();
|
||||
private byte[] _vram = Array.Empty<byte>();
|
||||
private UInt32[] _palette = Array.Empty<UInt32>();
|
||||
|
||||
[Obsolete("For designer only")]
|
||||
public SpriteViewerViewModel() : this(CpuType.Cpu, ConsoleType.Snes, new PictureViewer(), new Grid(), null) { }
|
||||
|
@ -93,17 +108,34 @@ namespace Mesen.Debugger.ViewModels
|
|||
}
|
||||
|
||||
this.WhenAnyValue(x => x.SelectedSprite).Subscribe(x => UpdateSelectionPreview());
|
||||
|
||||
this.WhenAnyValue(x => x.ViewerMousePos, x => x.PreviewPanelSprite).Subscribe(x => UpdateMouseOverRect());
|
||||
|
||||
ReactiveHelper.RegisterRecursiveObserver(Config, Config_PropertyChanged);
|
||||
|
||||
DebugShortcutManager.RegisterActions(wnd, FileMenuActions);
|
||||
DebugShortcutManager.RegisterActions(wnd, ViewMenuActions);
|
||||
}
|
||||
|
||||
public DynamicTooltip? GetPreviewPanel(SpritePreviewModel sprite, DynamicTooltip? existingTooltip)
|
||||
public void Dispose()
|
||||
{
|
||||
ReactiveHelper.UnregisterRecursiveObserver(Config, Config_PropertyChanged);
|
||||
}
|
||||
|
||||
private void Config_PropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
RefreshTab();
|
||||
}
|
||||
|
||||
public DynamicTooltip GetPreviewPanel(SpritePreviewModel sprite, DynamicTooltip? existingTooltip)
|
||||
{
|
||||
TooltipEntries entries = existingTooltip?.Items ?? new();
|
||||
entries.AddPicture("Sprite", sprite.SpritePreview, (int)sprite.SpritePreviewZoom);
|
||||
entries.AddEntry("Sprite index", sprite.SpriteIndex.ToString());
|
||||
entries.AddEntry("X, Y", sprite.X + ", " + sprite.Y);
|
||||
if(Config.ShowCoordsInHex) {
|
||||
entries.AddEntry("X, Y", "$" + sprite.X.ToString("X2") + ", $" + sprite.Y.ToString("X2"));
|
||||
} else {
|
||||
entries.AddEntry("X, Y", sprite.X + ", " + sprite.Y);
|
||||
}
|
||||
entries.AddEntry("Size", sprite.Width + "x" + sprite.Height);
|
||||
|
||||
entries.AddEntry("Tile index", "$" + sprite.TileIndex.ToString("X2"));
|
||||
|
@ -125,15 +157,6 @@ namespace Mesen.Debugger.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public void UpdateSelectionPreview()
|
||||
{
|
||||
if(SelectedSprite != null) {
|
||||
PreviewPanel = GetPreviewPanel(SelectedSprite, PreviewPanel);
|
||||
} else {
|
||||
PreviewPanel = null;
|
||||
}
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(ViewerBitmap))]
|
||||
private void InitBitmap(int width, int height)
|
||||
{
|
||||
|
@ -178,21 +201,19 @@ namespace Mesen.Debugger.ViewModels
|
|||
ToolTip.SetTip(ctrl, null);
|
||||
ToolTip.SetIsOpen(ctrl, false);
|
||||
}
|
||||
PreviewPanelTooltip = null;
|
||||
PreviewPanelSprite = null;
|
||||
}
|
||||
|
||||
private void SpritePreview_PointerEnter(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if(sender is Control ctrl && ctrl.DataContext is SpritePreviewModel sprite) {
|
||||
DynamicTooltip? tooltip = GetPreviewPanel(sprite, null);
|
||||
PreviewPanelTooltip = GetPreviewPanel(sprite, PreviewPanelTooltip);
|
||||
PreviewPanelSprite = sprite;
|
||||
|
||||
if(tooltip != null) {
|
||||
ToolTip.SetTip(ctrl, tooltip);
|
||||
ToolTip.SetHorizontalOffset(ctrl, 15);
|
||||
ToolTip.SetIsOpen(ctrl, true);
|
||||
} else {
|
||||
ToolTip.SetTip(ctrl, null);
|
||||
ToolTip.SetIsOpen(ctrl, false);
|
||||
}
|
||||
ToolTip.SetTip(ctrl, PreviewPanelTooltip);
|
||||
ToolTip.SetHorizontalOffset(ctrl, 15);
|
||||
ToolTip.SetIsOpen(ctrl, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,19 +225,19 @@ namespace Mesen.Debugger.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
private void InitPreviews(DebugSpriteInfo[] sprites)
|
||||
private void InitPreviews(DebugSpriteInfo[] sprites, DebugSpritePreviewInfo previewInfo)
|
||||
{
|
||||
if(SpritePreviews.Count != sprites.Length) {
|
||||
List<SpritePreviewModel> previews = new();
|
||||
for(int i = 0; i < sprites.Length; i++) {
|
||||
SpritePreviewModel model = new SpritePreviewModel();
|
||||
model.Init(ref sprites[i]);
|
||||
model.Init(ref sprites[i], previewInfo);
|
||||
previews.Add(model);
|
||||
}
|
||||
SpritePreviews = previews;
|
||||
} else {
|
||||
for(int i = 0; i < sprites.Length; i++) {
|
||||
SpritePreviews[i].Init(ref sprites[i]);
|
||||
SpritePreviews[i].Init(ref sprites[i], previewInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -234,26 +255,45 @@ namespace Mesen.Debugger.ViewModels
|
|||
|
||||
private void RefreshData<T>() where T : struct, BaseState
|
||||
{
|
||||
T ppuState = DebugApi.GetPpuState<T>(CpuType);
|
||||
byte[] vram = DebugApi.GetMemoryState(CpuType.GetVramMemoryType());
|
||||
byte[] spriteRam = DebugApi.GetMemoryState(CpuType.GetSpriteRamMemoryType());
|
||||
UInt32[] palette = PaletteHelper.GetConvertedPalette(CpuType, ConsoleType);
|
||||
_ppuState = DebugApi.GetPpuState<T>(CpuType);
|
||||
_vram = DebugApi.GetMemoryState(CpuType.GetVramMemoryType());
|
||||
_spriteRam = DebugApi.GetMemoryState(CpuType.GetSpriteRamMemoryType());
|
||||
_palette = PaletteHelper.GetConvertedPalette(CpuType, ConsoleType);
|
||||
|
||||
RefreshTab();
|
||||
}
|
||||
|
||||
private void RefreshTab()
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
if(_ppuState == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GetSpritePreviewOptions options = new GetSpritePreviewOptions() {
|
||||
SelectedSprite = -1
|
||||
};
|
||||
UInt32[] palette = PaletteHelper.GetConvertedPalette(CpuType, ConsoleType);
|
||||
|
||||
DebugSpritePreviewInfo size = DebugApi.GetSpritePreviewInfo(CpuType, options, ppuState);
|
||||
InitBitmap((int)size.Width, (int)size.Height);
|
||||
DebugSpritePreviewInfo previewInfo = DebugApi.GetSpritePreviewInfo(CpuType, options, _ppuState);
|
||||
InitBitmap((int)previewInfo.Width, (int)previewInfo.Height);
|
||||
|
||||
using(var framebuffer = ViewerBitmap.Lock()) {
|
||||
DebugApi.GetSpritePreview(CpuType, options, ppuState, vram, spriteRam, palette, framebuffer.FrameBuffer.Address);
|
||||
DebugApi.GetSpritePreview(CpuType, options, _ppuState, _vram, _spriteRam, palette, framebuffer.FrameBuffer.Address);
|
||||
}
|
||||
|
||||
DebugSpriteInfo[] sprites = DebugApi.GetSpriteList(CpuType, options, ppuState, vram, spriteRam, palette);
|
||||
InitPreviews(sprites);
|
||||
DebugSpriteInfo[] sprites = DebugApi.GetSpriteList(CpuType, options, _ppuState, _vram, _spriteRam, palette);
|
||||
InitPreviews(sprites, previewInfo);
|
||||
|
||||
if(Config.ShowOutline) {
|
||||
List<Rect> spriteRects = new List<Rect>();
|
||||
foreach(SpritePreviewModel sprite in SpritePreviews) {
|
||||
spriteRects.Add(sprite.GetPreviewRect());
|
||||
}
|
||||
SpriteRects = spriteRects;
|
||||
} else {
|
||||
SpriteRects = null;
|
||||
}
|
||||
|
||||
int selectedIndex = SelectedSprite?.SpriteIndex ?? -1;
|
||||
if(selectedIndex >= 0 && selectedIndex < SpritePreviews.Count) {
|
||||
|
@ -263,18 +303,49 @@ namespace Mesen.Debugger.ViewModels
|
|||
SelectedSprite = null;
|
||||
}
|
||||
|
||||
UpdateTooltips();
|
||||
UpdateSelection(SelectedSprite);
|
||||
|
||||
UpdateMouseOverRect();
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateSelectionPreview()
|
||||
{
|
||||
if(SelectedSprite != null) {
|
||||
SelectedPreviewPanel = GetPreviewPanel(SelectedSprite, SelectedPreviewPanel);
|
||||
} else {
|
||||
SelectedPreviewPanel = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateMouseOverRect()
|
||||
{
|
||||
if(PreviewPanelSprite != null) {
|
||||
MouseOverRect = PreviewPanelSprite.GetPreviewRect();
|
||||
} else if(ViewerMousePos != null && GetMatchingSprite(ViewerMousePos.Value) is SpritePreviewModel sprite) {
|
||||
MouseOverRect = sprite.GetPreviewRect();
|
||||
} else {
|
||||
MouseOverRect = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTooltips()
|
||||
{
|
||||
if(PreviewPanelSprite != null && PreviewPanelTooltip != null) {
|
||||
GetPreviewPanel(PreviewPanelSprite, PreviewPanelTooltip);
|
||||
} else if(ViewerMousePos != null && ViewerTooltip != null) {
|
||||
SpritePreviewModel? sprite = GetMatchingSprite(ViewerMousePos.Value);
|
||||
if(sprite != null) {
|
||||
GetPreviewPanel(sprite, ViewerTooltip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSelection(SpritePreviewModel? sprite)
|
||||
{
|
||||
if(sprite != null) {
|
||||
int offset = 0;
|
||||
if(CpuType == CpuType.Cpu) {
|
||||
offset = 256;
|
||||
}
|
||||
SelectionRect = new Rect(sprite.X + offset, sprite.Y, sprite.Width, sprite.Height);
|
||||
SelectionRect = sprite.GetPreviewRect();
|
||||
} else {
|
||||
SelectionRect = Rect.Empty;
|
||||
}
|
||||
|
@ -282,11 +353,10 @@ namespace Mesen.Debugger.ViewModels
|
|||
|
||||
public SpritePreviewModel? GetMatchingSprite(PixelPoint p)
|
||||
{
|
||||
foreach(SpritePreviewModel sprite in SpritePreviews) {
|
||||
if(
|
||||
p.X >= sprite.X && p.X < sprite.X + sprite.Width &&
|
||||
p.Y >= sprite.Y && p.Y < sprite.Y + sprite.Height
|
||||
) {
|
||||
Point point = p.ToPoint(1);
|
||||
for(int i = SpritePreviews.Count - 1; i >= 0; i--) {
|
||||
SpritePreviewModel sprite = SpritePreviews[i];
|
||||
if(sprite.GetPreviewRect().Contains(point)) {
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
|
@ -300,6 +370,8 @@ namespace Mesen.Debugger.ViewModels
|
|||
[Reactive] public int SpriteIndex { get; set; }
|
||||
[Reactive] public int X { get; set; }
|
||||
[Reactive] public int Y { get; set; }
|
||||
[Reactive] public int PreviewX { get; set; }
|
||||
[Reactive] public int PreviewY { get; set; }
|
||||
[Reactive] public int Width { get; set; }
|
||||
[Reactive] public int Height { get; set; }
|
||||
[Reactive] public int TileIndex { get; set; }
|
||||
|
@ -317,11 +389,13 @@ namespace Mesen.Debugger.ViewModels
|
|||
[Reactive] public DynamicBitmap SpritePreview { get; set; } = new DynamicBitmap(new PixelSize(1, 1), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul);
|
||||
[Reactive] public double SpritePreviewZoom { get; set; }
|
||||
|
||||
public unsafe void Init(ref DebugSpriteInfo sprite)
|
||||
public unsafe void Init(ref DebugSpriteInfo sprite, DebugSpritePreviewInfo previewInfo)
|
||||
{
|
||||
SpriteIndex = sprite.SpriteIndex;
|
||||
X = sprite.X;
|
||||
Y = sprite.Y;
|
||||
PreviewX = sprite.X + previewInfo.CoordOffsetX;
|
||||
PreviewY = sprite.Y + previewInfo.CoordOffsetY;
|
||||
Width = sprite.Width;
|
||||
Height = sprite.Height;
|
||||
TileIndex = sprite.TileIndex;
|
||||
|
@ -351,5 +425,10 @@ namespace Mesen.Debugger.ViewModels
|
|||
HorizontalMirror = sprite.HorizontalMirror;
|
||||
VerticalMirror = sprite.VerticalMirror;
|
||||
}
|
||||
|
||||
public Rect GetPreviewRect()
|
||||
{
|
||||
return new Rect(PreviewX, PreviewY, Width, Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,8 @@
|
|||
<Panel DockPanel.Dock="Right" IsVisible="{CompiledBinding Config.ShowSettingsPanel}">
|
||||
<ScrollViewer>
|
||||
<StackPanel Margin="5">
|
||||
<CheckBox Content="{l:Translate chkHideOffscreenSprites}" IsChecked="{CompiledBinding Config.HideOffscreenSprites}" />
|
||||
<CheckBox Content="{l:Translate chkShowCoordsInHex}" IsChecked="{CompiledBinding Config.ShowCoordsInHex}" />
|
||||
<CheckBox Content="{l:Translate chkShowOutline}" IsChecked="{CompiledBinding Config.ShowOutline}" />
|
||||
|
||||
<dv:RefreshTimingView DataContext="{CompiledBinding RefreshTiming}" />
|
||||
|
||||
|
@ -60,9 +61,9 @@
|
|||
Margin="0 10"
|
||||
Padding="3"
|
||||
MinWidth="170"
|
||||
IsVisible="{CompiledBinding PreviewPanel, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
IsVisible="{CompiledBinding SelectedPreviewPanel, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
>
|
||||
<ContentControl Content="{CompiledBinding PreviewPanel}" />
|
||||
<ContentControl Content="{CompiledBinding SelectedPreviewPanel}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
@ -82,6 +83,8 @@
|
|||
Source="{CompiledBinding ViewerBitmap}"
|
||||
Zoom="{CompiledBinding Config.ImageScale}"
|
||||
SelectionRect="{CompiledBinding SelectionRect}"
|
||||
MouseOverRect="{CompiledBinding MouseOverRect}"
|
||||
HighlightRects="{CompiledBinding SpriteRects}"
|
||||
ShowMousePosition="False"
|
||||
/>
|
||||
</ScrollViewer>
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
@ -8,10 +6,8 @@ using Mesen.Debugger.Controls;
|
|||
using Mesen.Debugger.ViewModels;
|
||||
using Mesen.Interop;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Mesen.Debugger.Utilities;
|
||||
|
||||
namespace Mesen.Debugger.Windows
|
||||
|
@ -20,7 +16,6 @@ namespace Mesen.Debugger.Windows
|
|||
{
|
||||
private NotificationListener _listener;
|
||||
private SpriteViewerViewModel _model;
|
||||
private PixelPoint? _prevMousePos = null;
|
||||
|
||||
[Obsolete("For designer only")]
|
||||
public SpriteViewerWindow() : this(CpuType.Cpu, ConsoleType.Snes) { }
|
||||
|
@ -67,31 +62,28 @@ namespace Mesen.Debugger.Windows
|
|||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
_listener?.Dispose();
|
||||
_listener.Dispose();
|
||||
_model.Config.SaveWindowSettings(this);
|
||||
_model.Dispose();
|
||||
}
|
||||
|
||||
private void PicViewer_PointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if(sender is PictureViewer viewer) {
|
||||
PixelPoint? point = viewer.GetGridPointFromMousePoint(e.GetCurrentPoint(viewer).Position);
|
||||
if(point == _prevMousePos) {
|
||||
if(point == _model.ViewerMousePos) {
|
||||
return;
|
||||
}
|
||||
_prevMousePos = point;
|
||||
_model.ViewerMousePos = point;
|
||||
|
||||
DynamicTooltip? tooltip = null;
|
||||
if(point != null) {
|
||||
DynamicTooltip? existingTooltip = ToolTip.GetTip(viewer) as DynamicTooltip;
|
||||
|
||||
if(_model.CpuType == CpuType.Cpu) {
|
||||
point = point.Value.WithX(point.Value.X - 256);
|
||||
}
|
||||
SpritePreviewModel? sprite = _model.GetMatchingSprite(point.Value);
|
||||
tooltip = sprite == null ? null : _model.GetPreviewPanel(sprite, existingTooltip);
|
||||
tooltip = sprite == null ? null : _model.GetPreviewPanel(sprite, _model.ViewerTooltip);
|
||||
}
|
||||
|
||||
if(tooltip != null) {
|
||||
_model.ViewerTooltip = tooltip;
|
||||
ToolTip.SetTip(viewer, tooltip);
|
||||
|
||||
//Force tooltip to update its position
|
||||
|
@ -101,6 +93,7 @@ namespace Mesen.Debugger.Windows
|
|||
} else {
|
||||
ToolTip.SetTip(viewer, null);
|
||||
ToolTip.SetIsOpen(viewer, false);
|
||||
_model.ViewerTooltip = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,17 +104,14 @@ namespace Mesen.Debugger.Windows
|
|||
ToolTip.SetTip(viewer, null);
|
||||
ToolTip.SetIsOpen(viewer, false);
|
||||
}
|
||||
_prevMousePos = null;
|
||||
_model.ViewerTooltip = null;
|
||||
_model.ViewerMousePos = null;
|
||||
}
|
||||
|
||||
private void PicViewer_PositionClicked(object? sender, PositionClickedEventArgs e)
|
||||
{
|
||||
if(sender is PictureViewer viewer) {
|
||||
PixelPoint p = e.Position;
|
||||
if(_model.CpuType == CpuType.Cpu) {
|
||||
p = p.WithX(p.X - 256);
|
||||
}
|
||||
|
||||
SpritePreviewModel? sprite = _model.GetMatchingSprite(p);
|
||||
_model.SelectedSprite = sprite;
|
||||
_model.UpdateSelection(sprite);
|
||||
|
|
|
@ -178,25 +178,34 @@ namespace Mesen.Interop
|
|||
[DllImport(DllPath)] public static extern void GetTileView(CpuType cpuType, GetTileViewOptions options, byte[] source, int srcSize, UInt32[] palette, IntPtr buffer);
|
||||
|
||||
[DllImport(DllPath)] private static extern void GetSpritePreview(CpuType cpuType, GetSpritePreviewOptions options, IntPtr state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr buffer);
|
||||
public unsafe static void GetSpritePreview<T>(CpuType cpuType, GetSpritePreviewOptions options, T state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr outputBuffer) where T : struct, BaseState
|
||||
public unsafe static void GetSpritePreview(CpuType cpuType, GetSpritePreviewOptions options, BaseState state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr outputBuffer)
|
||||
{
|
||||
byte* ptr = stackalloc byte[Marshal.SizeOf(typeof(T))];
|
||||
Debug.Assert(state.GetType().IsValueType);
|
||||
Debug.Assert(IsValidPpuState(ref state, cpuType));
|
||||
|
||||
byte* ptr = stackalloc byte[Marshal.SizeOf(state.GetType())];
|
||||
Marshal.StructureToPtr(state, (IntPtr)ptr, false);
|
||||
DebugApi.GetSpritePreview(cpuType, options, (IntPtr)ptr, vram, spriteRam, palette, outputBuffer);
|
||||
}
|
||||
|
||||
[DllImport(DllPath)] private static extern DebugSpritePreviewInfo GetSpritePreviewInfo(CpuType cpuType, GetSpritePreviewOptions options, IntPtr state);
|
||||
public unsafe static DebugSpritePreviewInfo GetSpritePreviewInfo<T>(CpuType cpuType, GetSpritePreviewOptions options, T state) where T : struct, BaseState
|
||||
public unsafe static DebugSpritePreviewInfo GetSpritePreviewInfo(CpuType cpuType, GetSpritePreviewOptions options, BaseState state)
|
||||
{
|
||||
byte* ptr = stackalloc byte[Marshal.SizeOf(typeof(T))];
|
||||
Debug.Assert(state.GetType().IsValueType);
|
||||
Debug.Assert(IsValidPpuState(ref state, cpuType));
|
||||
|
||||
byte* ptr = stackalloc byte[Marshal.SizeOf(state.GetType())];
|
||||
Marshal.StructureToPtr(state, (IntPtr)ptr, false);
|
||||
return DebugApi.GetSpritePreviewInfo(cpuType, options, (IntPtr)ptr);
|
||||
}
|
||||
|
||||
[DllImport(DllPath)] private static extern void GetSpriteList(CpuType cpuType, GetSpritePreviewOptions options, IntPtr state, byte[] vram, byte[] spriteRam, UInt32[] palette, IntPtr sprites);
|
||||
public unsafe static DebugSpriteInfo[] GetSpriteList<T>(CpuType cpuType, GetSpritePreviewOptions options, T state, byte[] vram, byte[] spriteRam, UInt32[] palette) where T : struct, BaseState
|
||||
public unsafe static DebugSpriteInfo[] GetSpriteList(CpuType cpuType, GetSpritePreviewOptions options, BaseState state, byte[] vram, byte[] spriteRam, UInt32[] palette)
|
||||
{
|
||||
byte* statePtr = stackalloc byte[Marshal.SizeOf(typeof(T))];
|
||||
Debug.Assert(state.GetType().IsValueType);
|
||||
Debug.Assert(IsValidPpuState(ref state, cpuType));
|
||||
|
||||
byte* statePtr = stackalloc byte[Marshal.SizeOf(state.GetType())];
|
||||
Marshal.StructureToPtr(state, (IntPtr)statePtr, false);
|
||||
|
||||
DebugSpriteInfo[] sprites = new DebugSpriteInfo[GetSpritePreviewInfo(cpuType, options, (IntPtr)statePtr).SpriteCount];
|
||||
|
@ -848,6 +857,8 @@ namespace Mesen.Interop
|
|||
public UInt32 Width;
|
||||
public UInt32 Height;
|
||||
public UInt32 SpriteCount;
|
||||
public Int32 CoordOffsetX;
|
||||
public Int32 CoordOffsetY;
|
||||
}
|
||||
|
||||
public enum DebugSpritePriority
|
||||
|
|
|
@ -808,8 +808,10 @@
|
|||
<Control ID="colPriority">Priority</Control>
|
||||
<Control ID="colPalette">Palette</Control>
|
||||
<Control ID="colFlags">Flags</Control>
|
||||
|
||||
|
||||
<Control ID="chkHideOffscreenSprites">Hide off-screen sprites</Control>
|
||||
<Control ID="chkShowOutline">Show outline around sprites</Control>
|
||||
<Control ID="chkShowCoordsInHex">Show coordinates in hex</Control>
|
||||
</Form>
|
||||
|
||||
<Form ID="AssemblerWindow">
|
||||
|
|
Loading…
Add table
Reference in a new issue