From 7de29dcc35020c1ed800c1c035e158e8b8094429 Mon Sep 17 00:00:00 2001 From: Sour Date: Fri, 31 Dec 2021 14:48:23 -0500 Subject: [PATCH] Sprite Viewer: Refactoring, fixes for tooltips, added some options --- Core/Debugger/PpuTools.h | 2 + Core/Gameboy/Debugger/GbPpuTools.cpp | 2 + Core/NES/Debugger/NesPpuTools.cpp | 11 +- Core/SNES/Debugger/SnesPpuTools.cpp | 2 + NewUI/Config/Debugger/SpriteViewerConfig.cs | 6 +- NewUI/Debugger/Controls/PictureViewer.cs | 74 +++++--- .../ViewModels/SpriteViewerViewModel.cs | 171 +++++++++++++----- .../Debugger/Windows/SpriteViewerWindow.axaml | 9 +- .../Windows/SpriteViewerWindow.axaml.cs | 28 +-- NewUI/Interop/DebugApi.cs | 23 ++- NewUI/Localization/resources.en.xml | 4 +- 11 files changed, 230 insertions(+), 102 deletions(-) diff --git a/Core/Debugger/PpuTools.h b/Core/Debugger/PpuTools.h index 1df8a253..2d000868 100644 --- a/Core/Debugger/PpuTools.h +++ b/Core/Debugger/PpuTools.h @@ -112,6 +112,8 @@ struct DebugSpritePreviewInfo uint32_t Width; uint32_t Height; uint32_t SpriteCount; + int32_t CoordOffsetX; + int32_t CoordOffsetY; }; class PpuTools diff --git a/Core/Gameboy/Debugger/GbPpuTools.cpp b/Core/Gameboy/Debugger/GbPpuTools.cpp index 616bb471..3b6c2d6f 100644 --- a/Core/Gameboy/Debugger/GbPpuTools.cpp +++ b/Core/Gameboy/Debugger/GbPpuTools.cpp @@ -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; } diff --git a/Core/NES/Debugger/NesPpuTools.cpp b/Core/NES/Debugger/NesPpuTools.cpp index 1a9e9f87..871320b3 100644 --- a/Core/NES/Debugger/NesPpuTools.cpp +++ b/Core/NES/Debugger/NesPpuTools.cpp @@ -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; } diff --git a/Core/SNES/Debugger/SnesPpuTools.cpp b/Core/SNES/Debugger/SnesPpuTools.cpp index 9be0f158..8c617099 100644 --- a/Core/SNES/Debugger/SnesPpuTools.cpp +++ b/Core/SNES/Debugger/SnesPpuTools.cpp @@ -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; } diff --git a/NewUI/Config/Debugger/SpriteViewerConfig.cs b/NewUI/Config/Debugger/SpriteViewerConfig.cs index 8ce9deec..062501a9 100644 --- a/NewUI/Config/Debugger/SpriteViewerConfig.cs +++ b/NewUI/Config/Debugger/SpriteViewerConfig.cs @@ -5,9 +5,13 @@ namespace Mesen.Config public class SpriteViewerConfig : BaseWindowConfig { [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() { diff --git a/NewUI/Debugger/Controls/PictureViewer.cs b/NewUI/Debugger/Controls/PictureViewer.cs index 1ff7e99e..64ba05e2 100644 --- a/NewUI/Debugger/Controls/PictureViewer.cs +++ b/NewUI/Debugger/Controls/PictureViewer.cs @@ -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 AllowSelectionProperty = AvaloniaProperty.Register(nameof(AllowSelection), true); public static readonly StyledProperty ShowMousePositionProperty = AvaloniaProperty.Register(nameof(ShowMousePosition), true); + public static readonly StyledProperty MouseOverRectProperty = AvaloniaProperty.Register(nameof(MouseOverRect), null, defaultBindingMode: BindingMode.OneWay); public static readonly StyledProperty SelectionRectProperty = AvaloniaProperty.Register(nameof(SelectionRect), Rect.Empty, defaultBindingMode: BindingMode.TwoWay); public static readonly StyledProperty OverlayRectProperty = AvaloniaProperty.Register(nameof(OverlayRect), Rect.Empty); + public static readonly StyledProperty?> HighlightRectsProperty = AvaloniaProperty.Register?>(nameof(HighlightRects), null); + public static readonly RoutedEvent PositionClickedEvent = RoutedEvent.Register(nameof(PositionClicked), RoutingStrategies.Bubble); public event EventHandler 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? HighlightRects + { + get { return GetValue(HighlightRectsProperty); } + set { SetValue(HighlightRectsProperty, value); } + } static PictureViewer() { - AffectsRender(SourceProperty, ZoomProperty, GridSizeXProperty, GridSizeYProperty, ShowGridProperty, SelectionRectProperty, OverlayRectProperty); + AffectsRender( + SourceProperty, ZoomProperty, GridSizeXProperty, GridSizeYProperty, + ShowGridProperty, SelectionRectProperty, OverlayRectProperty, + HighlightRectsProperty, MouseOverRectProperty + ); SourceProperty.Changed.AddClassHandler((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)); + } } } diff --git a/NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs b/NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs index 758aa0de..f2e08e7e 100644 --- a/NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs +++ b/NewUI/Debugger/ViewModels/SpriteViewerViewModel.cs @@ -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 SpritePreviews { get; set; } = new(); + [Reactive] public List? SpriteRects { get; set; } = null; public List FileMenuActions { get; } = new(); public List ViewMenuActions { get; } = new(); private Grid _spriteGrid; + private BaseState? _ppuState; + private byte[] _spriteRam = Array.Empty(); + private byte[] _vram = Array.Empty(); + private UInt32[] _palette = Array.Empty(); [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 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() where T : struct, BaseState { - T ppuState = DebugApi.GetPpuState(CpuType); - byte[] vram = DebugApi.GetMemoryState(CpuType.GetVramMemoryType()); - byte[] spriteRam = DebugApi.GetMemoryState(CpuType.GetSpriteRamMemoryType()); - UInt32[] palette = PaletteHelper.GetConvertedPalette(CpuType, ConsoleType); + _ppuState = DebugApi.GetPpuState(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 spriteRects = new List(); + 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); + } } } diff --git a/NewUI/Debugger/Windows/SpriteViewerWindow.axaml b/NewUI/Debugger/Windows/SpriteViewerWindow.axaml index 5c80b97f..4bcd91ea 100644 --- a/NewUI/Debugger/Windows/SpriteViewerWindow.axaml +++ b/NewUI/Debugger/Windows/SpriteViewerWindow.axaml @@ -50,7 +50,8 @@ - + + @@ -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}}" > - + @@ -82,6 +83,8 @@ Source="{CompiledBinding ViewerBitmap}" Zoom="{CompiledBinding Config.ImageScale}" SelectionRect="{CompiledBinding SelectionRect}" + MouseOverRect="{CompiledBinding MouseOverRect}" + HighlightRects="{CompiledBinding SpriteRects}" ShowMousePosition="False" /> diff --git a/NewUI/Debugger/Windows/SpriteViewerWindow.axaml.cs b/NewUI/Debugger/Windows/SpriteViewerWindow.axaml.cs index 5749f33a..ccee5766 100644 --- a/NewUI/Debugger/Windows/SpriteViewerWindow.axaml.cs +++ b/NewUI/Debugger/Windows/SpriteViewerWindow.axaml.cs @@ -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); diff --git a/NewUI/Interop/DebugApi.cs b/NewUI/Interop/DebugApi.cs index 28db227a..a5e607b0 100644 --- a/NewUI/Interop/DebugApi.cs +++ b/NewUI/Interop/DebugApi.cs @@ -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(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(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(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 diff --git a/NewUI/Localization/resources.en.xml b/NewUI/Localization/resources.en.xml index 6d319ce5..a239db1e 100644 --- a/NewUI/Localization/resources.en.xml +++ b/NewUI/Localization/resources.en.xml @@ -808,8 +808,10 @@ Priority Palette Flags - + Hide off-screen sprites + Show outline around sprites + Show coordinates in hex