Files
MesenCE/UI/Debugger/ViewModels/SpriteViewerViewModel.cs

748 lines
25 KiB
C#

using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using Mesen.Config;
using Mesen.Debugger.Controls;
using Mesen.Debugger.Utilities;
using Mesen.Debugger.Windows;
using Mesen.Interop;
using Mesen.Localization;
using Mesen.Utilities;
using Mesen.ViewModels;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Mesen.Debugger.ViewModels
{
public partial class SpriteViewerViewModel : DisposableViewModel, ICpuTypeModel, IMouseOverViewerModel
{
public SpriteViewerConfig Config { get; }
[ObservableProperty] public partial RefreshTimingViewModel RefreshTiming { get; private set; }
public CpuType CpuType { get; set; }
[ObservableProperty] public partial SpritePreviewModel? SelectedSprite { get; set; }
[ObservableProperty] public partial DynamicTooltip? SelectedPreviewPanel { get; set; }
[ObservableProperty] public partial DynamicTooltip? PreviewPanelTooltip { get; set; }
[ObservableProperty] public partial SpritePreviewModel? PreviewPanelSprite { get; set; }
[ObservableProperty] public partial DynamicTooltip? ViewerTooltip { get; set; }
[ObservableProperty] public partial PixelPoint? ViewerMousePos { get; set; }
[ObservableProperty] public partial DynamicBitmap ViewerBitmap { get; private set; }
[ObservableProperty] public partial Rect SelectionRect { get; set; }
[ObservableProperty] public partial Rect? MouseOverRect { get; set; }
[ObservableProperty] public partial int TopClipSize { get; set; }
[ObservableProperty] public partial int BottomClipSize { get; set; }
[ObservableProperty] public partial int LeftClipSize { get; set; }
[ObservableProperty] public partial int RightClipSize { get; set; }
[ObservableProperty] public partial List<SpritePreviewModel> SpritePreviews { get; set; } = new();
public SpriteViewerListViewModel ListView { get; }
[ObservableProperty] public partial int MaxSourceOffset { get; set; } = 0;
public List<object> FileMenuActions { get; } = new();
public List<object> ViewMenuActions { get; } = new();
private Grid _spriteGrid;
private object _updateLock = new();
private SpriteViewerData _data = new();
private SpriteViewerData _coreData = new();
private byte[] _baseVram = Array.Empty<byte>();
private byte[] _baseSpriteRam = Array.Empty<byte>();
private byte[] _extVram = Array.Empty<byte>();
private byte[] _extSpriteRam = Array.Empty<byte>();
private DebugSpriteInfo[] _spriteList = Array.Empty<DebugSpriteInfo>();
private UInt32[] _spritePreviews = Array.Empty<UInt32>();
private bool _refreshPending;
[Obsolete("For designer only")]
public SpriteViewerViewModel() : this(CpuType.Snes, new(), new(), new(), new(), null) { }
public SpriteViewerViewModel(CpuType cpuType, PictureViewer picViewer, ScrollPictureViewer scrollViewer, Grid spriteGrid, Control listView, Window? wnd)
{
Config = ConfigManager.Config.Debug.SpriteViewer.Clone();
ListView = AddDisposable(new SpriteViewerListViewModel(this));
CpuType = cpuType;
RefreshTiming = new RefreshTimingViewModel(Config.RefreshTiming, cpuType);
_spriteGrid = spriteGrid;
_spriteGrid.PointerExited += SpriteGrid_PointerExited;
InitBitmap(256, 256);
FileMenuActions = AddDisposables(new List<object>() {
new ContextMenuAction() {
ActionType = ActionType.ExportToPng,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.SaveAsPng),
OnClick = () => picViewer.ExportToPng()
},
new ContextMenuAction() {
ActionType = ActionType.CopyToClipboard,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.Copy),
OnClick = () => picViewer.CopyToClipboard()
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.Exit,
OnClick = () => wnd?.Close()
}
});
ViewMenuActions = AddDisposables(new List<object>() {
new ContextMenuAction() {
ActionType = ActionType.Refresh,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.Refresh),
OnClick = () => RefreshData()
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.EnableAutoRefresh,
IsSelected = () => Config.RefreshTiming.AutoRefresh,
OnClick = () => Config.RefreshTiming.AutoRefresh = !Config.RefreshTiming.AutoRefresh
},
new ContextMenuAction() {
ActionType = ActionType.RefreshOnBreakPause,
IsSelected = () => Config.RefreshTiming.RefreshOnBreakPause,
OnClick = () => Config.RefreshTiming.RefreshOnBreakPause = !Config.RefreshTiming.RefreshOnBreakPause
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.ShowSettingsPanel,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ToggleSettingsPanel),
IsSelected = () => Config.ShowSettingsPanel,
OnClick = () => Config.ShowSettingsPanel = !Config.ShowSettingsPanel
},
new ContextMenuAction() {
ActionType = ActionType.ShowListView,
IsSelected = () => Config.ShowListView,
OnClick = () => Config.ShowListView = !Config.ShowListView
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.ZoomIn,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ZoomIn),
OnClick = () => picViewer.ZoomIn()
},
new ContextMenuAction() {
ActionType = ActionType.ZoomOut,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ZoomOut),
OnClick = () => picViewer.ZoomOut()
},
});
if(Design.IsDesignMode || wnd == null) {
return;
}
AddDisposables(DebugShortcutManager.CreateContextMenu(picViewer, scrollViewer, new List<object> {
GetCopyTileAction(wnd),
new ContextMenuSeparator(),
GetEditTileAction(wnd),
GetViewInMemoryViewerAction(),
GetViewInTileViewerAction(),
GetCopyHdPackFormatActionSeparator(),
GetCopyHdPackFormatAction()
}));
AddDisposables(DebugShortcutManager.CreateContextMenu(_spriteGrid, new List<object> {
GetCopyTileAction(wnd),
new ContextMenuSeparator(),
GetEditTileAction(wnd),
GetViewInMemoryViewerAction(),
GetViewInTileViewerAction(),
GetCopyHdPackFormatActionSeparator(),
GetCopyHdPackFormatAction()
}));
AddDisposables(DebugShortcutManager.CreateContextMenu(listView, new List<object> {
GetCopyTileAction(wnd),
new ContextMenuSeparator(),
GetEditTileAction(wnd),
GetViewInMemoryViewerAction(),
GetViewInTileViewerAction(),
GetCopyHdPackFormatActionSeparator(),
GetCopyHdPackFormatAction()
}));
AddDisposable(this.ObserveProp([nameof(ViewerMousePos), nameof(PreviewPanelSprite)], UpdateMouseOverRect));
AddDisposable(Config.ObserveProp([nameof(Config.Source), nameof(Config.SourceOffset)], RefreshData));
AddDisposable(Config.ObserveProp(nameof(Config.ShowOffscreenRegions), RefreshTab));
AddDisposable(ReactiveHelper.RegisterRecursiveObserver(Config, Config_PropertyChanged));
DebugShortcutManager.RegisterActions(wnd, FileMenuActions);
DebugShortcutManager.RegisterActions(wnd, ViewMenuActions);
}
partial void OnSelectedSpriteChanged(SpritePreviewModel? value)
{
UpdateSelectionPreview();
if(value != null) {
ListView.SelectSprite(value.SpriteIndex);
}
}
partial void OnSpritePreviewsChanged(List<SpritePreviewModel> value)
{
ListView.RefreshList(true);
}
private ContextMenuAction GetCopyTileAction(Window wnd)
{
return new ContextMenuAction() {
ActionType = ActionType.CopyToClipboard,
IsEnabled = () => GetSelectedSprite() != null,
OnClick = () => {
SpritePreviewModel? sprite = GetSelectedSprite();
if(sprite != null) {
wnd.Clipboard?.SetBitmapAsync(sprite.SpritePreview);
}
}
};
}
private ContextMenuAction GetEditTileAction(Window wnd)
{
return new ContextMenuAction() {
ActionType = ActionType.EditSprite,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.SpriteViewer_EditSprite),
IsEnabled = () => GetSelectedSprite() != null,
OnClick = () => {
SpritePreviewModel? sprite = GetSelectedSprite();
if(sprite?.TileAddress >= 0 && _data.Palette != null) {
PixelSize size = sprite.Format.GetTileSize();
DebugPaletteInfo pal = _data.Palette.Value;
int paletteOffset = (int)(pal.SpritePaletteOffset / pal.ColorsPerPalette);
TileEditorWindow.OpenAtTile(
sprite.TileAddresses.Select(x => new TileAddressInfo() { Address = new AddressInfo() { Address = (int)x, Type = CpuType.GetVramMemoryType(sprite.UseExtendedVram) } }).ToList(),
sprite.RealWidth / size.Width,
sprite.Format,
sprite.Palette + paletteOffset,
wnd,
CpuType,
RefreshTiming.ConsoleConfig.RefreshScanline,
RefreshTiming.ConsoleConfig.RefreshCycle
);
}
}
};
}
private ContextMenuAction GetViewInMemoryViewerAction()
{
return new ContextMenuAction() {
ActionType = ActionType.ViewInMemoryViewer,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.SpriteViewer_ViewInMemoryViewer),
IsEnabled = () => GetSelectedSprite() != null,
OnClick = () => {
SpritePreviewModel? sprite = GetSelectedSprite();
if(sprite?.TileAddress >= 0) {
MemoryToolsWindow.ShowInMemoryTools(CpuType.GetVramMemoryType(sprite.UseExtendedVram), sprite.TileAddress);
}
}
};
}
private ContextMenuAction GetViewInTileViewerAction()
{
return new ContextMenuAction() {
ActionType = ActionType.ViewInTileViewer,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.SpriteViewer_ViewInTileViewer),
OnClick = () => {
SpritePreviewModel? sprite = GetSelectedSprite();
if(sprite?.TileAddress >= 0 && _data.Palette != null) {
DebugPaletteInfo pal = _data.Palette.Value;
int paletteOffset = (int)(pal.SpritePaletteOffset / pal.ColorsPerPalette);
TileViewerWindow.OpenAtTile(CpuType, CpuType.GetVramMemoryType(sprite.UseExtendedVram), sprite.TileAddress, sprite.Format, TileLayout.Normal, sprite.Palette + paletteOffset);
}
}
};
}
private ContextMenuAction GetCopyHdPackFormatActionSeparator()
{
return new ContextMenuSeparator() { IsVisible = () => CpuType == CpuType.Nes };
}
private ContextMenuAction GetCopyHdPackFormatAction()
{
return new ContextMenuAction() {
ActionType = ActionType.CopyToHdPackFormat,
IsVisible = () => CpuType == CpuType.Nes,
IsEnabled = () => {
SpritePreviewModel? sprite = GetSelectedSprite();
return sprite != null && HdPackCopyHelper.IsActionAllowed(CpuType.GetVramMemoryType(sprite.UseExtendedVram));
},
OnClick = () => {
SpritePreviewModel? sprite = GetSelectedSprite();
if(sprite != null && sprite.TileAddress >= 0 && _data.Palette != null) {
HdPackCopyHelper.CopyToHdPackFormat(sprite.TileAddress, CpuType.GetVramMemoryType(sprite.UseExtendedVram), _data.Palette.Value.GetRawPalette(), sprite.Palette, true, sprite.Height > 8);
}
}
};
}
private SpritePreviewModel? GetSelectedSprite()
{
return PreviewPanelSprite ?? SelectedSprite;
}
private void Config_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
RefreshTab();
}
public DynamicTooltip? GetPreviewPanel(PixelPoint p, DynamicTooltip? tooltipToUpdate)
{
SpritePreviewModel? sprite = GetMatchingSprite(p, out _);
return sprite == null ? null : GetPreviewPanel(sprite, tooltipToUpdate);
}
public DynamicTooltip? GetPreviewPanel(SpritePreviewModel sprite, DynamicTooltip? existingTooltip)
{
if(_data.Palette == null) {
return null;
}
TooltipEntries entries = existingTooltip?.Items ?? new();
entries.StartUpdate();
if(sprite.SpritePreview != null) {
entries.AddPicture("Sprite", sprite.SpritePreview, 48.0 / sprite.Width);
}
DebugPaletteInfo palette = _data.Palette.Value;
int paletteSize = (int)Math.Pow(2, sprite.Bpp);
int paletteIndex = (sprite.Palette >= 0 ? sprite.Palette : 0) + (int)(palette.SpritePaletteOffset / paletteSize);
if(sprite.Bpp > 1 && sprite.Bpp < 8) {
entries.AddEntry("Palette", new TooltipPaletteEntry(paletteIndex, paletteSize, palette.GetRgbPalette(), palette.GetRawPalette(), palette.RawFormat));
}
entries.AddEntry("Sprite index", sprite.SpriteIndex.ToString());
entries.AddEntry("X, Y",
"$" + sprite.RawX.ToString("X2") + ", $" + sprite.RawY.ToString("X2") + Environment.NewLine +
sprite.X + ", " + sprite.Y
);
entries.AddEntry("Size", sprite.Width + "x" + sprite.Height);
entries.AddSeparator("TileSeparator");
entries.AddEntry("Tile index", "$" + sprite.TileIndex.ToString("X2"));
MemoryType memType = CpuType.GetVramMemoryType(sprite.UseExtendedVram);
if(memType.IsRelativeMemory()) {
entries.AddEntry("Tile address (" + memType.GetShortName() + ")", FormatAddress(sprite.TileAddress, memType));
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = sprite.TileAddress, Type = memType });
if(absAddress.Address >= 0) {
entries.AddEntry("Tile address (" + absAddress.Type.GetShortName() + ")", FormatAddress(absAddress.Address, absAddress.Type));
}
} else {
entries.AddEntry("Tile address", FormatAddress(sprite.TileAddress, memType));
}
entries.AddSeparator("PaletteSeparator");
entries.AddEntry("Palette index", sprite.Palette.ToString());
if(sprite.PaletteAddress >= 0) {
entries.AddEntry("Palette address", "$" + sprite.PaletteAddress.ToString("X2"));
}
entries.AddSeparator("MiscSeparator");
entries.AddEntry("Visibility", ResourceHelper.GetEnumText(sprite.Visibility));
if(sprite.Mode != DebugSpriteMode.Undefined) {
entries.AddEntry("Mode", sprite.Mode);
}
entries.AddEntry("Horizontal mirror", sprite.HorizontalMirror);
entries.AddEntry("Vertical mirror", sprite.VerticalMirror);
if(sprite.TransformEnabled != NullableBoolean.Undefined) {
entries.AddEntry("Transform", sprite.TransformEnabled);
if(sprite.TransformParamIndex >= 0) {
entries.AddEntry("Transform Param Index", sprite.TransformParamIndex);
}
}
entries.AddEntry("Mosaic", sprite.MosaicEnabled);
entries.AddEntry("Second table", sprite.UseSecondTable);
if(sprite.Priority != DebugSpritePriority.Undefined) {
entries.AddEntry("Priority", ResourceHelper.GetEnumText(sprite.Priority));
}
entries.EndUpdate();
if(existingTooltip != null) {
return existingTooltip;
} else {
return new DynamicTooltip() { Items = entries };
}
}
private string FormatAddress(int address, MemoryType memType)
{
if(memType.IsWordAddressing()) {
return $"${address / 2:X4}.w";
} else {
return $"${address:X4}";
}
}
[MemberNotNull(nameof(ViewerBitmap))]
private void InitBitmap(int width, int height)
{
if(ViewerBitmap == null || ViewerBitmap.PixelSize.Width != width || ViewerBitmap.PixelSize.Height != height) {
ViewerBitmap = new DynamicBitmap(new PixelSize(width, height), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul);
}
}
private void InitGrid(int spriteCount)
{
double dpiScale = LayoutHelper.GetLayoutScale(_spriteGrid);
if(_spriteGrid.Children.Count != spriteCount) {
_spriteGrid.Children.Clear();
_spriteGrid.ColumnDefinitions.Clear();
_spriteGrid.RowDefinitions.Clear();
for(int i = 0; i < 8; i++) {
_spriteGrid.ColumnDefinitions.Add(new() { Width = new GridLength(1, GridUnitType.Auto) });
}
for(int i = 0; i < spriteCount / 8; i++) {
_spriteGrid.RowDefinitions.Add(new() { Height = new GridLength(1, GridUnitType.Auto) });
}
for(int i = 0; i < spriteCount; i++) {
SpritePreviewPanel preview = new SpritePreviewPanel(SpritePreviews[i], Config);
preview.InnerHeight = (32.0 / dpiScale);
preview.InnerWidth = (32.0 / dpiScale);
Grid.SetColumn(preview, i % 8);
Grid.SetRow(preview, i / 8);
preview.DataContext = SpritePreviews[i];
preview.PointerPressed += SpritePreview_PointerPressed;
preview.PointerMoved += SpritePreview_PointerMoved;
_spriteGrid.Children.Add(preview);
}
}
}
private void SpriteGrid_PointerExited(object? sender, PointerEventArgs e)
{
TooltipHelper.HideTooltip(_spriteGrid);
PreviewPanelTooltip = null;
PreviewPanelSprite = null;
}
private void SpritePreview_PointerMoved(object? sender, PointerEventArgs e)
{
if(sender is Control ctrl && ctrl.DataContext is SpritePreviewModel sprite) {
PreviewPanelTooltip = GetPreviewPanel(sprite, PreviewPanelTooltip);
PreviewPanelSprite = sprite;
TooltipHelper.ShowTooltip(_spriteGrid, PreviewPanelTooltip, 15);
}
}
private void SpritePreview_PointerPressed(object? sender, PointerPressedEventArgs e)
{
if(sender is Control ctrl && ctrl.DataContext is SpritePreviewModel sprite) {
SelectedSprite = sprite;
UpdateSelection(sprite);
}
}
public void SelectSprite(int spriteIndex)
{
SelectedSprite = SpritePreviews[spriteIndex];
UpdateSelection(SelectedSprite);
}
private void InitPreviews(DebugSpriteInfo[] sprites, UInt32[] spritePreviews, 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], spritePreviews, previewInfo);
previews.Add(model);
}
SpritePreviews = previews;
} else {
for(int i = 0; i < sprites.Length; i++) {
SpritePreviews[i].Init(ref sprites[i], spritePreviews, previewInfo);
}
}
InitGrid(sprites.Length);
}
private void UpdateRamData(ref byte[] baseRam, ref byte[] extRam, ref byte[] combinedRam, MemoryType baseType, MemoryType extType)
{
if(baseType == MemoryType.None) {
Array.Resize(ref baseRam, 0);
Array.Resize(ref extRam, 0);
Array.Resize(ref combinedRam, 0);
} else if(extType != baseType) {
DebugApi.GetMemoryState(extType, ref extRam);
if(extRam.Length > 0) {
DebugApi.GetMemoryState(baseType, ref baseRam);
Array.Resize(ref combinedRam, extRam.Length + baseRam.Length);
Array.Copy(baseRam, 0, combinedRam, 0, baseRam.Length);
Array.Copy(extRam, 0, combinedRam, baseRam.Length, extRam.Length);
} else {
DebugApi.GetMemoryState(baseType, ref combinedRam);
}
} else {
Array.Resize(ref extRam, 0);
DebugApi.GetMemoryState(baseType, ref combinedRam);
}
}
private void UpdateVramData()
{
UpdateRamData(ref _baseVram, ref _extVram, ref _coreData.Vram, CpuType.GetVramMemoryType(), CpuType.GetVramMemoryType(getExtendedRam: true));
}
private void UpdateSpriteRamData()
{
UpdateRamData(ref _baseSpriteRam, ref _extSpriteRam, ref _coreData.SpriteRam, CpuType.GetSpriteRamMemoryType(), CpuType.GetSpriteRamMemoryType(getExtendedRam: true));
}
public void RefreshData()
{
lock(_updateLock) {
_coreData.PpuState = DebugApi.GetPpuState(CpuType);
_coreData.PpuToolsState = DebugApi.GetPpuToolsState(CpuType);
UpdateVramData();
int spriteRamSize;
MemoryType spriteRamType = CpuType.GetSpriteRamMemoryType();
if(spriteRamType == MemoryType.None) {
//SMS-specific
spriteRamSize = 0x100;
} else {
spriteRamSize = DebugApi.GetMemorySize(spriteRamType);
MemoryType spriteRamExtType = CpuType.GetSpriteRamMemoryType(getExtendedRam: true);
if(spriteRamType != spriteRamExtType) {
spriteRamSize += DebugApi.GetMemorySize(spriteRamExtType);
}
}
if(Config.Source == SpriteViewerSource.SpriteRam) {
UpdateSpriteRamData();
} else {
MemoryType cpuMemory = CpuType.ToMemoryType();
DebugApi.GetMemoryValues(cpuMemory, (uint)Config.SourceOffset, (uint)(Config.SourceOffset + spriteRamSize - 1), ref _coreData.SpriteRam);
Dispatcher.UIThread.Post(() => {
MaxSourceOffset = Math.Max(0, DebugApi.GetMemorySize(cpuMemory) - spriteRamSize);
});
}
DebugPaletteInfo palette = DebugApi.GetPaletteInfo(CpuType);
_coreData.Palette = palette.ColorCount > 0 ? palette : null;
}
RefreshTab();
}
private void RefreshTab()
{
if(_refreshPending) {
return;
}
_refreshPending = true;
Dispatcher.UIThread.Post(() => {
InternalRefreshTab();
_refreshPending = false;
});
}
private void InternalRefreshTab()
{
if(Disposed) {
return;
}
lock(_updateLock) {
_coreData.CopyTo(_data);
}
if(_data.PpuState == null || _data.Palette == null || _data.PpuToolsState == null) {
return;
}
GetSpritePreviewOptions options = new GetSpritePreviewOptions() {
Background = Config.Background
};
DebugSpritePreviewInfo previewInfo = DebugApi.GetSpritePreviewInfo(CpuType, options, _data.PpuState, _data.PpuToolsState);
InitBitmap((int)previewInfo.Width, (int)previewInfo.Height);
UInt32[] palette = _data.Palette.Value.GetRgbPalette();
LeftClipSize = Config.ShowOffscreenRegions ? 0 : (int)previewInfo.VisibleX;
RightClipSize = Config.ShowOffscreenRegions ? 0 : (int)(previewInfo.Width - (previewInfo.VisibleWidth + previewInfo.VisibleX));
TopClipSize = Config.ShowOffscreenRegions ? 0 : (int)previewInfo.VisibleY;
BottomClipSize = Config.ShowOffscreenRegions ? 0 : (int)(previewInfo.Height - (previewInfo.VisibleHeight + previewInfo.VisibleY));
using(var framebuffer = ViewerBitmap.Lock(true)) {
DebugApi.GetSpriteList(ref _spriteList, ref _spritePreviews, CpuType, options, _data.PpuState, _data.PpuToolsState, _data.Vram, _data.SpriteRam, palette, framebuffer.FrameBuffer.Address);
}
InitPreviews(_spriteList, _spritePreviews, previewInfo);
if(Config.ShowOutline) {
List<Rect> spriteRects = new List<Rect>();
foreach(SpritePreviewModel sprite in SpritePreviews) {
(Rect mainRect, Rect alt1, Rect alt2, Rect alt3) = sprite.GetPreviewRect();
spriteRects.Add(mainRect);
if(alt1 != default) {
spriteRects.Add(alt1);
}
if(alt2 != default) {
spriteRects.Add(alt2);
}
if(alt3 != default) {
spriteRects.Add(alt3);
}
}
ViewerBitmap.HighlightRects = spriteRects;
} else {
ViewerBitmap.HighlightRects = null;
}
ViewerBitmap.Invalidate();
int selectedIndex = SelectedSprite?.SpriteIndex ?? -1;
if(selectedIndex >= 0 && selectedIndex < SpritePreviews.Count) {
SelectedSprite = SpritePreviews[selectedIndex];
UpdateSelectionPreview();
} else {
SelectedSprite = null;
}
ListView.RefreshList();
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().Item1;
} else if(ViewerMousePos != null && GetMatchingSprite(ViewerMousePos.Value, out Rect matchingRect) is SpritePreviewModel sprite) {
MouseOverRect = matchingRect;
} 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, out _);
if(sprite != null) {
GetPreviewPanel(sprite, ViewerTooltip);
}
}
}
public void UpdateSelection(SpritePreviewModel? sprite)
{
if(sprite != null) {
SelectionRect = sprite.GetPreviewRect().Item1;
} else {
SelectionRect = default;
}
}
public SpritePreviewModel? GetMatchingSprite(PixelPoint p, out Rect matchingRect)
{
Point point = p.ToPoint(1);
for(int i = SpritePreviews.Count - 1; i >= 0; i--) {
SpritePreviewModel sprite = SpritePreviews[i];
(Rect mainRect, Rect alt1, Rect alt2, Rect alt3) = sprite.GetPreviewRect();
if(mainRect.Contains(point)) {
matchingRect = mainRect;
return sprite;
} else if(alt1.Contains(point)) {
matchingRect = alt1;
return sprite;
} else if(alt2.Contains(point)) {
matchingRect = alt2;
return sprite;
} else if(alt3.Contains(point)) {
matchingRect = alt3;
return sprite;
}
}
matchingRect = default;
return null;
}
public void OnGameLoaded()
{
RefreshTiming = new RefreshTimingViewModel(Config.RefreshTiming, CpuType);
RefreshData();
}
}
public class SpriteViewerData
{
public BaseState? PpuState;
public BaseState? PpuToolsState;
public byte[] SpriteRam = Array.Empty<byte>();
public byte[] Vram = Array.Empty<byte>();
public DebugPaletteInfo? Palette = null;
public void CopyTo(SpriteViewerData dst)
{
dst.PpuState = PpuState;
dst.Palette = Palette;
dst.PpuToolsState = PpuToolsState;
CopyArray(SpriteRam, ref dst.SpriteRam);
CopyArray(Vram, ref dst.Vram);
}
private void CopyArray<T>(T[] src, ref T[] dst)
{
if(src.Length != dst.Length) {
Array.Resize(ref dst, src.Length);
}
Array.Copy(src, dst, src.Length);
}
}
}