mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Tilemap Viewer: Tile information + tooltip
This commit is contained in:
parent
c074981bb6
commit
f5d02d7607
18 changed files with 547 additions and 120 deletions
|
@ -33,6 +33,33 @@ struct DebugSpriteInfo
|
|||
uint32_t SpritePreview[64 * 64];
|
||||
};
|
||||
|
||||
enum class NullableBoolean
|
||||
{
|
||||
Undefined = -1,
|
||||
False = 0,
|
||||
True = 1
|
||||
};
|
||||
|
||||
struct DebugTilemapTileInfo
|
||||
{
|
||||
int32_t Row = -1;
|
||||
int32_t Column = -1;
|
||||
int32_t Width = -1;
|
||||
int32_t Height = -1;
|
||||
|
||||
int32_t TileMapAddress = -1;
|
||||
|
||||
int32_t TileIndex = -1;
|
||||
int32_t TileAddress = -1;
|
||||
|
||||
int32_t PaletteIndex = -1;
|
||||
int32_t PaletteAddress = -1;
|
||||
|
||||
NullableBoolean HorizontalMirroring = NullableBoolean::Undefined;
|
||||
NullableBoolean VerticalMirroring = NullableBoolean::Undefined;
|
||||
NullableBoolean HighPriority = NullableBoolean::Undefined;
|
||||
};
|
||||
|
||||
class PpuTools
|
||||
{
|
||||
protected:
|
||||
|
@ -51,6 +78,7 @@ public:
|
|||
|
||||
void GetTileView(GetTileViewOptions options, uint8_t *source, uint32_t srcSize, uint32_t* palette, uint32_t *outBuffer);
|
||||
|
||||
virtual DebugTilemapTileInfo GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) = 0;
|
||||
virtual FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) = 0;
|
||||
virtual void GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer) = 0;
|
||||
|
||||
|
|
|
@ -88,6 +88,54 @@ FrameInfo GbPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& state
|
|||
return { 256, 256 };
|
||||
}
|
||||
|
||||
DebugTilemapTileInfo GbPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState)
|
||||
{
|
||||
DebugTilemapTileInfo result;
|
||||
|
||||
FrameInfo size = GetTilemapSize(options, baseState);
|
||||
|
||||
if(x >= size.Width || y >= size.Height) {
|
||||
return result;
|
||||
}
|
||||
|
||||
GbPpuState& state = (GbPpuState&)baseState;
|
||||
bool isCgb = state.CgbEnabled;
|
||||
|
||||
int row = y / 8;
|
||||
int column = x / 8;
|
||||
|
||||
int offset = options.Layer == 1 ? 0x1C00 : 0x1800;
|
||||
uint16_t baseOffset = offset + ((row & 0x1F) << 5);
|
||||
uint16_t addr = (baseOffset + column);
|
||||
uint8_t tileIndex = vram[addr];
|
||||
|
||||
uint8_t attributes = isCgb ? vram[addr | 0x2000] : 0;
|
||||
|
||||
uint16_t baseTile = state.BgTileSelect ? 0 : 0x1000;
|
||||
uint16_t tileStart = baseTile + (baseTile ? (int8_t)tileIndex * 16 : tileIndex * 16);
|
||||
|
||||
uint16_t tileBank = (attributes & 0x08) ? 0x2000 : 0x0000;
|
||||
tileStart |= tileBank;
|
||||
|
||||
result.Column = column;
|
||||
result.Row = row;
|
||||
result.Height = 8;
|
||||
result.Width = 8;
|
||||
result.TileMapAddress = addr;
|
||||
result.TileIndex = tileIndex;
|
||||
result.TileAddress = tileStart;
|
||||
|
||||
if(isCgb) {
|
||||
result.PaletteIndex = (attributes & 0x07);
|
||||
result.PaletteAddress = (result.PaletteIndex << 2);
|
||||
result.HorizontalMirroring = (NullableBoolean)((attributes & 0x20) != 0);
|
||||
result.VerticalMirroring = (NullableBoolean)((attributes & 0x40) != 0);
|
||||
result.HighPriority = (NullableBoolean)((attributes & 0x80) != 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
FrameInfo GbPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state)
|
||||
{
|
||||
return { 256, 256 };
|
||||
|
|
|
@ -15,6 +15,7 @@ public:
|
|||
|
||||
void GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t *outBuffer) override;
|
||||
FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) override;
|
||||
DebugTilemapTileInfo GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) override;
|
||||
|
||||
void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t *outBuffer) override;
|
||||
FrameInfo GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) override;
|
||||
|
|
|
@ -119,6 +119,55 @@ FrameInfo NesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& stat
|
|||
return { 512, 480 };
|
||||
}
|
||||
|
||||
DebugTilemapTileInfo NesPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState)
|
||||
{
|
||||
DebugTilemapTileInfo result;
|
||||
|
||||
FrameInfo size = GetTilemapSize(options, baseState);
|
||||
if(x >= size.Width || y >= size.Height) {
|
||||
return result;
|
||||
}
|
||||
|
||||
uint8_t row = y / 8;
|
||||
uint8_t column = x / 8;
|
||||
uint8_t nametableIndex = (column >= 32 ? 1 : 0) | (row >= 30 ? 2 : 0);
|
||||
|
||||
column &= 0x1F;
|
||||
if(row > 30) {
|
||||
row -= 30;
|
||||
}
|
||||
|
||||
MMC5* mmc5 = dynamic_cast<MMC5*>(_mapper);
|
||||
NesPpuState& state = (NesPpuState&)baseState;
|
||||
|
||||
uint16_t bgAddr = (state.ControlReg & 0x10) ? 0x1000 : 0;
|
||||
uint16_t baseAddr = 0x2000 + nametableIndex * 0x400;
|
||||
uint16_t baseAttributeAddr = baseAddr + 960;
|
||||
uint16_t ntIndex = (row << 5) + column;
|
||||
uint16_t attributeAddress = baseAttributeAddr + ((row & 0xFC) << 1) + (column >> 2);
|
||||
uint8_t attribute = vram[attributeAddress];
|
||||
uint8_t shift = (column & 0x02) | ((row & 0x02) << 1);
|
||||
|
||||
uint8_t paletteBaseAddr;
|
||||
if(mmc5 && mmc5->IsExtendedAttributes()) {
|
||||
paletteBaseAddr = mmc5->GetExAttributeNtPalette(ntIndex) << 2;
|
||||
} else {
|
||||
paletteBaseAddr = ((attribute >> shift) & 0x03) << 2;
|
||||
}
|
||||
|
||||
result.Row = row;
|
||||
result.Column = column;
|
||||
result.Width = 8;
|
||||
result.Height = 8;
|
||||
result.TileMapAddress = baseAddr + ntIndex;
|
||||
result.TileIndex = vram[result.TileMapAddress];
|
||||
result.TileAddress = bgAddr + (result.TileIndex << 4);
|
||||
result.PaletteIndex = paletteBaseAddr >> 2;
|
||||
result.PaletteAddress = 0x3F00 | paletteBaseAddr;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void NesPpuTools::GetSpriteInfo(DebugSpriteInfo& sprite, uint32_t i, GetSpritePreviewOptions& options, NesPpuState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette)
|
||||
{
|
||||
sprite.SpriteIndex = i;
|
||||
|
|
|
@ -18,9 +18,10 @@ public:
|
|||
NesPpuTools(Debugger* debugger, Emulator *emu, NesConsole* console);
|
||||
|
||||
void GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t *outBuffer) override;
|
||||
FrameInfo GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) override;
|
||||
|
||||
void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t *outBuffer) override;
|
||||
FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) override;
|
||||
DebugTilemapTileInfo GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) override;
|
||||
|
||||
FrameInfo GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) override;
|
||||
void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t *outBuffer) override;
|
||||
uint32_t GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[]) override;
|
||||
};
|
|
@ -5,6 +5,10 @@
|
|||
#include "SNES/SnesDefaultVideoFilter.h"
|
||||
#include "SNES/Ppu.h"
|
||||
|
||||
static constexpr uint8_t layerBpp[8][4] = {
|
||||
{ 2,2,2,2 }, { 4,4,2,0 }, { 4,4,0,0 }, { 8,4,0,0 }, { 8,2,0,0 }, { 4,2,0,0 }, { 4,0,0,0 }, { 8,0,0,0 }
|
||||
};
|
||||
|
||||
SnesPpuTools::SnesPpuTools(Debugger* debugger, Emulator *emu) : PpuTools(debugger, emu)
|
||||
{
|
||||
}
|
||||
|
@ -14,10 +18,6 @@ void SnesPpuTools::GetTilemap(GetTilemapOptions options, BaseState& baseState, u
|
|||
PpuState& state = (PpuState&)baseState;
|
||||
FrameInfo outputSize = GetTilemapSize(options, state);
|
||||
|
||||
static constexpr uint8_t layerBpp[8][4] = {
|
||||
{ 2,2,2,2 }, { 4,4,2,0 }, { 4,4,0,0 }, { 8,4,0,0 }, { 8,2,0,0 }, { 4,2,0,0 }, { 4,0,0,0 }, { 8,0,0,0 }
|
||||
};
|
||||
|
||||
bool directColor = state.DirectColorMode && (state.BgMode == 3 || state.BgMode == 4 || state.BgMode == 7);
|
||||
|
||||
uint16_t basePaletteOffset = 0;
|
||||
|
@ -281,6 +281,67 @@ FrameInfo SnesPpuTools::GetTilemapSize(GetTilemapOptions options, BaseState& bas
|
|||
return size;
|
||||
}
|
||||
|
||||
DebugTilemapTileInfo SnesPpuTools::GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState)
|
||||
{
|
||||
DebugTilemapTileInfo result;
|
||||
|
||||
FrameInfo size = GetTilemapSize(options, baseState);
|
||||
if(x >= size.Width || y >= size.Height) {
|
||||
return result;
|
||||
}
|
||||
|
||||
PpuState& state = (PpuState&)baseState;
|
||||
LayerConfig layer = state.Layers[options.Layer];
|
||||
|
||||
uint8_t bpp = layerBpp[state.BgMode][options.Layer];
|
||||
if(bpp == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t basePaletteOffset = 0;
|
||||
if(state.BgMode == 0) {
|
||||
basePaletteOffset = options.Layer * 64;
|
||||
}
|
||||
|
||||
uint32_t row = y / 8;
|
||||
uint32_t column = x / 8;
|
||||
|
||||
result.Row = row;
|
||||
result.Column = column;
|
||||
|
||||
if(state.BgMode == 7) {
|
||||
result.TileMapAddress = row * 256 + column * 2;
|
||||
result.TileIndex = vram[result.TileMapAddress];
|
||||
result.TileAddress = result.TileIndex * 128;
|
||||
result.Height = 8;
|
||||
result.Width = 8;
|
||||
} else {
|
||||
bool largeTileWidth = layer.LargeTiles || state.BgMode == 5 || state.BgMode == 6;
|
||||
bool largeTileHeight = layer.LargeTiles;
|
||||
|
||||
uint16_t addrVerticalScrollingOffset = layer.DoubleHeight ? ((row & 0x20) << (layer.DoubleWidth ? 6 : 5)) : 0;
|
||||
uint16_t baseOffset = layer.TilemapAddress + addrVerticalScrollingOffset + ((row & 0x1F) << 5);
|
||||
|
||||
uint16_t addr = (baseOffset + (column & 0x1F) + (layer.DoubleWidth ? ((column & 0x20) << 5) : 0)) << 1;
|
||||
|
||||
result.Height = largeTileHeight ? 16 : 8;
|
||||
result.Width = largeTileWidth ? 16 : 8;
|
||||
result.VerticalMirroring = (NullableBoolean)((vram[addr + 1] & 0x80) != 0);
|
||||
result.HorizontalMirroring = (NullableBoolean)((vram[addr + 1] & 0x40) != 0);
|
||||
result.HighPriority = (NullableBoolean)((vram[addr + 1] & 0x20) != 0);
|
||||
result.PaletteIndex = bpp == 8 ? 0 : (vram[addr + 1] >> 2) & 0x07;
|
||||
result.TileIndex = ((vram[addr + 1] & 0x03) << 8) | vram[addr];
|
||||
result.TileMapAddress = addr;
|
||||
|
||||
uint16_t tileStart = (layer.ChrAddress << 1) + result.TileIndex * 8 * bpp;
|
||||
result.TileAddress = tileStart;
|
||||
|
||||
result.PaletteAddress = basePaletteOffset + (result.PaletteIndex * (1 << bpp));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
FrameInfo SnesPpuTools::GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state)
|
||||
{
|
||||
return { 512, 256 };
|
||||
|
|
|
@ -16,7 +16,8 @@ public:
|
|||
|
||||
void GetTilemap(GetTilemapOptions options, BaseState& state, uint8_t* vram, uint32_t* palette, uint32_t* outBuffer) override;
|
||||
FrameInfo GetTilemapSize(GetTilemapOptions options, BaseState& state) override;
|
||||
|
||||
DebugTilemapTileInfo GetTilemapTileInfo(uint32_t x, uint32_t y, uint8_t* vram, GetTilemapOptions options, BaseState& baseState) override;
|
||||
|
||||
void GetSpritePreview(GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, uint32_t* outBuffer) override;
|
||||
uint32_t GetSpriteList(GetSpritePreviewOptions options, BaseState& baseState, uint8_t* vram, uint8_t* oamRam, uint32_t* palette, DebugSpriteInfo outBuffer[]) override;
|
||||
FrameInfo GetSpritePreviewSize(GetSpritePreviewOptions options, BaseState& state) override;
|
||||
|
|
|
@ -99,6 +99,7 @@ extern "C"
|
|||
|
||||
DllExport void __stdcall GetTilemap(CpuType cpuType, GetTilemapOptions options, BaseState& state, uint8_t *vram, uint32_t* palette, uint32_t *outputBuffer) { GetDebugger()->GetPpuTools(cpuType)->GetTilemap(options, state, vram, palette, outputBuffer); }
|
||||
DllExport FrameInfo __stdcall GetTilemapSize(CpuType cpuType, GetTilemapOptions options, BaseState& state) { return GetDebugger()->GetPpuTools(cpuType)->GetTilemapSize(options, state); }
|
||||
DllExport DebugTilemapTileInfo __stdcall GetTilemapTileInfo(uint32_t x, uint32_t y, CpuType cpuType, GetTilemapOptions options, uint8_t* vram, BaseState& state) { return GetDebugger()->GetPpuTools(cpuType)->GetTilemapTileInfo(x, y, vram, options, state); }
|
||||
|
||||
DllExport FrameInfo __stdcall GetSpritePreviewSize(CpuType cpuType, GetSpritePreviewOptions options, BaseState& state) { return GetDebugger()->GetPpuTools(cpuType)->GetSpritePreviewSize(options, state); }
|
||||
DllExport void __stdcall GetSpritePreview(CpuType cpuType, GetSpritePreviewOptions options, BaseState& state, uint8_t* vram, uint8_t *oamRam, uint32_t* palette, uint32_t *buffer) { GetDebugger()->GetPpuTools(cpuType)->GetSpritePreview(options, state, vram, oamRam, palette, buffer); }
|
||||
|
|
|
@ -8,23 +8,27 @@ using System.Xml;
|
|||
using System.Xml.Serialization;
|
||||
using Avalonia;
|
||||
using Mesen.Debugger;
|
||||
using Mesen.Interop;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
|
||||
namespace Mesen.Config
|
||||
{
|
||||
public class TilemapViewerConfig
|
||||
public class TilemapViewerConfig : BaseWindowConfig<TilemapViewerConfig>
|
||||
{
|
||||
public Size WindowSize = new Size(0, 0);
|
||||
public Point WindowLocation;
|
||||
[Reactive] public bool ShowSettingsPanel { get; set; } = true;
|
||||
|
||||
public int ImageScale = 1;
|
||||
public bool ShowScrollOverlay = false;
|
||||
public bool ShowTileGrid = false;
|
||||
[Reactive] public int ImageScale { get; set; } = 1;
|
||||
|
||||
public RefreshSpeed AutoRefreshSpeed = RefreshSpeed.Low;
|
||||
[Reactive] public bool ShowGrid { get; set; }
|
||||
[Reactive] public bool ShowAltGrid { get; set; }
|
||||
[Reactive] public bool ShowScrollOverlay { get; set; }
|
||||
[Reactive] public bool HighlightTileChanges { get; set; }
|
||||
[Reactive] public bool HighlightAttributeChanges { get; set; }
|
||||
[Reactive] public TilemapDisplayMode DisplayMode { get; set; } = TilemapDisplayMode.Default;
|
||||
|
||||
public bool AutoRefresh = true;
|
||||
public int RefreshScanline = 240;
|
||||
public int RefreshCycle = 0;
|
||||
[Reactive] public bool AutoRefresh { get; set; } = true;
|
||||
[Reactive] public int RefreshScanline { get; set; } = 240;
|
||||
[Reactive] public int RefreshCycle { get; set; } = 0;
|
||||
|
||||
public TilemapViewerConfig()
|
||||
{
|
||||
|
|
|
@ -20,8 +20,17 @@
|
|||
<ItemsPresenter.ItemTemplate>
|
||||
<DataTemplate DataType="dc:TooltipEntry">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding Name}" FontWeight="Bold" MinWidth="70" />
|
||||
<TextBlock Text="{Binding Value}" />
|
||||
<TextBlock Text="{Binding Name}" FontWeight="Bold" MinWidth="100" Margin="0 0 10 0" />
|
||||
<ContentControl Content="{Binding Value}">
|
||||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="x:String">
|
||||
<TextBlock Text="{Binding}" />
|
||||
</DataTemplate>
|
||||
<DataTemplate DataType="x:Boolean">
|
||||
<CheckBox IsChecked="{Binding}" />
|
||||
</DataTemplate>
|
||||
</ContentControl.DataTemplates>
|
||||
</ContentControl>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsPresenter.ItemTemplate>
|
||||
|
|
|
@ -35,9 +35,9 @@ namespace Mesen.Debugger.Controls
|
|||
public class TooltipEntry
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
public string Value { get; set; } = "";
|
||||
public object Value { get; set; } = "";
|
||||
|
||||
public TooltipEntry(string name, string value)
|
||||
public TooltipEntry(string name, object value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace Mesen.Debugger.Controls
|
|||
{
|
||||
public class PictureViewer : Control
|
||||
{
|
||||
public static readonly StyledProperty<Bitmap> SourceProperty = AvaloniaProperty.Register<PictureViewer, Bitmap>(nameof(Source));
|
||||
public static readonly StyledProperty<IImage> SourceProperty = AvaloniaProperty.Register<PictureViewer, IImage>(nameof(Source));
|
||||
public static readonly StyledProperty<int> ZoomProperty = AvaloniaProperty.Register<PictureViewer, int>(nameof(Zoom), 1, defaultBindingMode: BindingMode.TwoWay);
|
||||
public static readonly StyledProperty<int> GridSizeXProperty = AvaloniaProperty.Register<PictureViewer, int>(nameof(GridSizeX), 8);
|
||||
public static readonly StyledProperty<int> GridSizeYProperty = AvaloniaProperty.Register<PictureViewer, int>(nameof(GridSizeY), 8);
|
||||
|
@ -30,7 +30,7 @@ namespace Mesen.Debugger.Controls
|
|||
public static readonly StyledProperty<bool> ShowAltGridProperty = AvaloniaProperty.Register<PictureViewer, bool>(nameof(ShowAltGrid), false);
|
||||
public static readonly StyledProperty<bool> AllowSelectionProperty = AvaloniaProperty.Register<PictureViewer, bool>(nameof(AllowSelection), true);
|
||||
|
||||
public static readonly StyledProperty<Rect> SelectionRectProperty = AvaloniaProperty.Register<PictureViewer, Rect>(nameof(SelectionRect), Rect.Empty);
|
||||
public static readonly StyledProperty<Rect> SelectionRectProperty = AvaloniaProperty.Register<PictureViewer, Rect>(nameof(SelectionRect), Rect.Empty, defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public static readonly RoutedEvent<PositionClickedEventArgs> PositionClickedEvent = RoutedEvent.Register<PictureViewer, PositionClickedEventArgs>(nameof(PositionClicked), RoutingStrategies.Bubble);
|
||||
public event EventHandler<PositionClickedEventArgs> PositionClicked
|
||||
|
@ -44,7 +44,7 @@ namespace Mesen.Debugger.Controls
|
|||
private Stopwatch _stopWatch = Stopwatch.StartNew();
|
||||
private DispatcherTimer _timer = new DispatcherTimer();
|
||||
|
||||
public Bitmap Source
|
||||
public IImage Source
|
||||
{
|
||||
get { return GetValue(SourceProperty); }
|
||||
set { SetValue(SourceProperty, value); }
|
||||
|
@ -156,8 +156,8 @@ namespace Mesen.Debugger.Controls
|
|||
|
||||
private void UpdateSize()
|
||||
{
|
||||
MinWidth = Source.PixelSize.Width * Zoom;
|
||||
MinHeight = Source.PixelSize.Height * Zoom;
|
||||
MinWidth = (int)Source.Size.Width * Zoom;
|
||||
MinHeight = (int)Source.Size.Height * Zoom;
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
|
||||
|
@ -184,29 +184,46 @@ namespace Mesen.Debugger.Controls
|
|||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
base.OnPointerPressed(e);
|
||||
Point p = e.GetCurrentPoint(this).Position;
|
||||
p = new Point(Math.Min(p.X, MinWidth - 1) / Zoom, Math.Min(p.Y, MinHeight - 1) / Zoom);
|
||||
Point p = GetGridPointFromMousePoint(e.GetCurrentPoint(this).Position);
|
||||
|
||||
PositionClickedEventArgs args = new() { RoutedEvent = PositionClickedEvent, Position = p };
|
||||
RaiseEvent(args);
|
||||
|
||||
if(!args.Handled && AllowSelection) {
|
||||
Rect selection = new Rect(
|
||||
(int)p.X / GridSizeX * GridSizeX,
|
||||
(int)p.Y / GridSizeY * GridSizeY,
|
||||
GridSizeX,
|
||||
GridSizeY
|
||||
);
|
||||
SelectionRect = selection;
|
||||
SelectionRect = GetTileRect(p);
|
||||
}
|
||||
}
|
||||
|
||||
private Point GetGridPointFromMousePoint(Point p)
|
||||
{
|
||||
return new Point(Math.Min(p.X, MinWidth - 1) / Zoom, Math.Min(p.Y, MinHeight - 1) / Zoom);
|
||||
}
|
||||
|
||||
private Rect GetTileRect(Point p)
|
||||
{
|
||||
return new Rect(
|
||||
(int)p.X / GridSizeX * GridSizeX,
|
||||
(int)p.Y / GridSizeY * GridSizeY,
|
||||
GridSizeX,
|
||||
GridSizeY
|
||||
);
|
||||
}
|
||||
|
||||
private Rect ToDrawRect(Rect r)
|
||||
{
|
||||
return new Rect(
|
||||
r.X * Zoom - 0.5,
|
||||
r.Y * Zoom - 0.5,
|
||||
r.Width * Zoom + 1,
|
||||
r.Height * Zoom + 1
|
||||
);
|
||||
}
|
||||
|
||||
private void DrawGrid(DrawingContext context, bool show, int gridX, int gridY, Color color)
|
||||
{
|
||||
if(show) {
|
||||
int width = Source.PixelSize.Width * Zoom;
|
||||
int height = Source.PixelSize.Height * Zoom;
|
||||
int width = (int)Source.Size.Width * Zoom;
|
||||
int height = (int)Source.Size.Height * Zoom;
|
||||
int gridSizeX = gridX * Zoom;
|
||||
int gridSizeY = gridY * Zoom;
|
||||
|
||||
|
@ -230,14 +247,14 @@ namespace Mesen.Debugger.Controls
|
|||
return;
|
||||
}
|
||||
|
||||
int width = Source.PixelSize.Width * Zoom;
|
||||
int height = Source.PixelSize.Height * Zoom;
|
||||
int width = (int)Source.Size.Width * Zoom;
|
||||
int height = (int)Source.Size.Height * Zoom;
|
||||
|
||||
context.FillRectangle(new SolidColorBrush(0xFFFFFFFF), new Rect(Bounds.Size));
|
||||
|
||||
context.DrawImage(
|
||||
Source,
|
||||
new Rect(0, 0, Source.PixelSize.Width, Source.PixelSize.Height),
|
||||
new Rect(0, 0, (int)Source.Size.Width, (int)Source.Size.Height),
|
||||
new Rect(0, 0, width, height),
|
||||
Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode.Default
|
||||
);
|
||||
|
@ -246,24 +263,19 @@ namespace Mesen.Debugger.Controls
|
|||
DrawGrid(context, ShowAltGrid, AltGridSizeX, AltGridSizeY, Color.FromArgb(128, Colors.Red.R, Colors.Red.G, Colors.Red.B));
|
||||
|
||||
if(SelectionRect != Rect.Empty) {
|
||||
Rect rect = new Rect(
|
||||
SelectionRect.X * Zoom - 0.5,
|
||||
SelectionRect.Y * Zoom - 0.5,
|
||||
SelectionRect.Width * Zoom + 1,
|
||||
SelectionRect.Height * Zoom + 1
|
||||
);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
//TODO delete?
|
||||
/*if(_mousePosition.HasValue) {
|
||||
Point p = new Point(Math.Floor(_mousePosition.Value.X) + 0.5, Math.Floor(_mousePosition.Value.Y) + 0.5);
|
||||
context.DrawLine(new Pen(Brushes.Black), new Point(p.X, 0), new Point(p.X, Bounds.Height));
|
||||
context.DrawLine(new Pen(Brushes.Black), new Point(0, p.Y), new Point(Bounds.Width, p.Y));
|
||||
}*/
|
||||
if(_mousePosition.HasValue) {
|
||||
Point p = GetGridPointFromMousePoint(_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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
using Avalonia.Controls;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
using Mesen.Config;
|
||||
using Mesen.Debugger.Controls;
|
||||
using Mesen.Interop;
|
||||
using Mesen.ViewModels;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
@ -12,19 +19,26 @@ namespace Mesen.Debugger.ViewModels
|
|||
public CpuType CpuType { get; }
|
||||
public ConsoleType ConsoleType { get; }
|
||||
|
||||
[Reactive] public bool ShowGrid { get; set; }
|
||||
[Reactive] public bool ShowAltGrid { get; set; }
|
||||
[Reactive] public bool HighlightTileChanges { get; set; }
|
||||
[Reactive] public bool HighlightAttributeChanges { get; set; }
|
||||
[Reactive] public TilemapDisplayMode DisplayMode { get; set; }
|
||||
public TilemapViewerConfig Config { get; }
|
||||
|
||||
[Reactive] public List<TilemapViewerTab> Tabs { get; set; } = new List<TilemapViewerTab>();
|
||||
[Reactive] public Rect SelectionRect { get; set; }
|
||||
|
||||
[Reactive] public WriteableBitmap ViewerBitmap { get; private set; }
|
||||
[Reactive] public BaseState? PpuState { get; set; }
|
||||
[Reactive] public byte[]? PrevVram { get; set; }
|
||||
|
||||
[Reactive] public DynamicTooltip? PreviewPanel { get; private set; }
|
||||
|
||||
[Reactive] public List<TilemapViewerTab> Tabs { get; private set; } = new List<TilemapViewerTab>();
|
||||
[Reactive] public bool ShowTabs { get; private set; }
|
||||
[Reactive] public TilemapViewerTab SelectedTab { get; set; }
|
||||
|
||||
//For designer
|
||||
public TilemapViewerViewModel() : this(CpuType.Cpu, ConsoleType.Snes) { }
|
||||
|
||||
public TilemapViewerViewModel(CpuType cpuType, ConsoleType consoleType)
|
||||
{
|
||||
Config = ConfigManager.Config.Debug.TilemapViewer;
|
||||
CpuType = cpuType;
|
||||
ConsoleType = consoleType;
|
||||
|
||||
|
@ -51,6 +65,107 @@ namespace Mesen.Debugger.ViewModels
|
|||
};
|
||||
break;
|
||||
}
|
||||
|
||||
SelectedTab = Tabs[0];
|
||||
|
||||
InitBitmap(256, 256);
|
||||
|
||||
this.WhenAnyValue(x => x.Tabs).Subscribe(x => ShowTabs = x.Count > 1);
|
||||
|
||||
this.WhenAnyValue(x => x.SelectionRect).Subscribe(x => {
|
||||
if(x.IsEmpty) {
|
||||
PreviewPanel = null;
|
||||
} else {
|
||||
PreviewPanel = GetPreviewPanel(PixelPoint.FromPoint(x.TopLeft, 1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void InitBitmap(int width, int height)
|
||||
{
|
||||
if(ViewerBitmap == null || ViewerBitmap.PixelSize.Width != width || ViewerBitmap.PixelSize.Height != height) {
|
||||
ViewerBitmap = new WriteableBitmap(new PixelSize(width, height), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul);
|
||||
}
|
||||
}
|
||||
|
||||
private GetTilemapOptions GetOptions(byte[]? prevVram = null)
|
||||
{
|
||||
return new GetTilemapOptions() {
|
||||
Layer = (byte)SelectedTab.Layer,
|
||||
CompareVram = prevVram,
|
||||
HighlightTileChanges = Config.HighlightTileChanges,
|
||||
HighlightAttributeChanges = Config.HighlightAttributeChanges,
|
||||
DisplayMode = Config.DisplayMode
|
||||
};
|
||||
}
|
||||
|
||||
public void UpdateBitmap<T>(T ppuState, byte[] vram, byte[]? prevVram) where T : struct, BaseState
|
||||
{
|
||||
GetTilemapOptions options = GetOptions(prevVram);
|
||||
UInt32[] palette = PaletteHelper.GetConvertedPalette(CpuType, ConsoleType);
|
||||
|
||||
FrameInfo size = DebugApi.GetTilemapSize(CpuType, options, ppuState);
|
||||
InitBitmap((int)size.Width, (int)size.Height);
|
||||
|
||||
using(var framebuffer = ViewerBitmap.Lock()) {
|
||||
DebugApi.GetTilemap(CpuType, options, ppuState, vram, palette, framebuffer.Address);
|
||||
}
|
||||
}
|
||||
|
||||
public DynamicTooltip? GetPreviewPanel(PixelPoint p)
|
||||
{
|
||||
if(PpuState == null || PrevVram == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DebugTilemapTileInfo? result = DebugApi.GetTilemapTileInfo((uint)p.X, (uint)p.Y, CpuType, GetOptions(), PrevVram, PpuState);
|
||||
if(result == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DebugTilemapTileInfo tileInfo = result.Value;
|
||||
CroppedBitmap preview = new CroppedBitmap(ViewerBitmap, new PixelRect(p.X / tileInfo.Width * tileInfo.Width, p.Y / tileInfo.Height * tileInfo.Height, tileInfo.Width, tileInfo.Height));
|
||||
|
||||
List<TooltipEntry> entries = new();
|
||||
entries.Add(new("Tile", new Border() {
|
||||
BorderBrush = Brushes.Gray,
|
||||
BorderThickness = new Thickness(1),
|
||||
Child = new PictureViewer() {
|
||||
Source = preview,
|
||||
Width = tileInfo.Width * 6,
|
||||
Height = tileInfo.Height * 6,
|
||||
Zoom = 6
|
||||
}
|
||||
}));
|
||||
entries.Add(new("Column, Row", tileInfo.Column + ", " + tileInfo.Row));
|
||||
entries.Add(new("Size", tileInfo.Width + "x" + tileInfo.Height));
|
||||
|
||||
if(tileInfo.TileMapAddress >= 0) {
|
||||
entries.Add(new("Tilemap address", "$" + tileInfo.TileMapAddress.ToString("X4")));
|
||||
}
|
||||
if(tileInfo.TileIndex >= 0) {
|
||||
entries.Add(new("Tile index", "$" + tileInfo.TileIndex.ToString("X2")));
|
||||
}
|
||||
if(tileInfo.TileAddress >= 0) {
|
||||
entries.Add(new("Tile address", "$" + tileInfo.TileAddress.ToString("X4")));
|
||||
}
|
||||
if(tileInfo.PaletteIndex >= 0) {
|
||||
entries.Add(new("Palette index", tileInfo.PaletteIndex.ToString()));
|
||||
}
|
||||
if(tileInfo.PaletteAddress >= 0) {
|
||||
entries.Add(new("Palette address", "$" + tileInfo.PaletteAddress.ToString("X2")));
|
||||
}
|
||||
if(tileInfo.HorizontalMirroring != NullableBoolean.Undefined) {
|
||||
entries.Add(new("Horizontal mirror", tileInfo.HorizontalMirroring == NullableBoolean.True));
|
||||
}
|
||||
if(tileInfo.VerticalMirroring != NullableBoolean.Undefined) {
|
||||
entries.Add(new("Vertical mirror", tileInfo.VerticalMirroring == NullableBoolean.True));
|
||||
}
|
||||
if(tileInfo.HighPriority != NullableBoolean.Undefined) {
|
||||
entries.Add(new("High priority", tileInfo.HighPriority == NullableBoolean.True));
|
||||
}
|
||||
|
||||
return new DynamicTooltip() { Items = entries };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,16 +40,25 @@ namespace Mesen.Debugger.Windows
|
|||
_model.Config.LoadWindowSettings(this);
|
||||
DataContext = _model;
|
||||
|
||||
viewer.PointerMoved += Viewer_PointerMoved;
|
||||
|
||||
if(Design.IsDesignMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewer.PointerMoved += Viewer_PointerMoved;
|
||||
viewer.PointerLeave += Viewer_PointerLeave;
|
||||
|
||||
_timer = new DispatcherTimer(TimeSpan.FromMilliseconds(100), DispatcherPriority.Normal, (s, e) => UpdateConfig());
|
||||
_listener = new NotificationListener();
|
||||
}
|
||||
|
||||
private void Viewer_PointerLeave(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if(sender is PictureViewer viewer) {
|
||||
ToolTip.SetTip(viewer, null);
|
||||
ToolTip.SetIsOpen(viewer, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Viewer_PointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if(sender is PictureViewer viewer) {
|
||||
|
|
|
@ -24,36 +24,76 @@
|
|||
</Design.DataContext>
|
||||
|
||||
<Window.Styles>
|
||||
<Style Selector="TabControl:singleitem">
|
||||
<Style Selector="TabStrip:singleitem">
|
||||
<Setter Property="IsVisible" Value="False" />
|
||||
</Style>
|
||||
<Style Selector="Button">
|
||||
<Setter Property="MinWidth" Value="0" />
|
||||
<Setter Property="Padding" Value="1" />
|
||||
</Style>
|
||||
<Style Selector="Button Image">
|
||||
<Setter Property="Margin" Value="1 1 0 0" />
|
||||
<Setter Property="Width" Value="16" />
|
||||
<Setter Property="Height" Value="16" />
|
||||
</Style>
|
||||
</Window.Styles>
|
||||
|
||||
<DockPanel>
|
||||
<Grid
|
||||
DockPanel.Dock="Right"
|
||||
ColumnDefinitions="Auto,*"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto"
|
||||
Margin="5 0 5 0"
|
||||
Width="180"
|
||||
>
|
||||
<TextBlock Text="{l:Translate lblDisplayMode}" />
|
||||
<c:EnumComboBox Grid.Row="0" Grid.Column="1" EnumType="{x:Type i:TilemapDisplayMode}" SelectedItem="{CompiledBinding DisplayMode}" />
|
||||
<CheckBox Grid.Row="1" Grid.ColumnSpan="2" Content="{l:Translate chkShowGrid}" IsChecked="{CompiledBinding ShowGrid}" />
|
||||
<CheckBox Grid.Row="2" Grid.ColumnSpan="2" Content="{l:Translate chkShowAltGrid}" IsChecked="{CompiledBinding ShowAltGrid}" />
|
||||
<CheckBox Grid.Row="3" Grid.ColumnSpan="2" Content="{l:Translate chkHighlightTileChanges}" IsChecked="{CompiledBinding HighlightTileChanges}" />
|
||||
<CheckBox Grid.Row="4" Grid.ColumnSpan="2" Content="{l:Translate chkHighlightAttributeChanges}" IsChecked="{CompiledBinding HighlightAttributeChanges}" />
|
||||
</Grid>
|
||||
<Panel DockPanel.Dock="Top">
|
||||
<Menu DockPanel.Dock="Top">
|
||||
<MenuItem Header="File" />
|
||||
<MenuItem Header="View" />
|
||||
</Menu>
|
||||
<Button
|
||||
HorizontalAlignment="Right"
|
||||
DockPanel.Dock="Right"
|
||||
ToolTip.Tip="Toggle settings panel"
|
||||
Click="OnSettingsClick"
|
||||
>
|
||||
<Image Source="/Assets/Settings.png" />
|
||||
</Button>
|
||||
</Panel>
|
||||
<StackPanel DockPanel.Dock="Right" IsVisible="{CompiledBinding Config.ShowSettingsPanel}">
|
||||
<Grid
|
||||
ColumnDefinitions="Auto,*"
|
||||
RowDefinitions="Auto,Auto,Auto,Auto,Auto"
|
||||
Margin="5 0 5 0"
|
||||
Width="180"
|
||||
>
|
||||
<TextBlock Text="{l:Translate lblDisplayMode}" />
|
||||
<c:EnumComboBox Grid.Row="0" Grid.Column="1" EnumType="{x:Type i:TilemapDisplayMode}" SelectedItem="{CompiledBinding Config.DisplayMode}" />
|
||||
<CheckBox Grid.Row="1" Grid.ColumnSpan="2" Content="{l:Translate chkShowGrid}" IsChecked="{CompiledBinding Config.ShowGrid}" />
|
||||
<CheckBox Grid.Row="2" Grid.ColumnSpan="2" Content="{l:Translate chkShowAltGrid}" IsChecked="{CompiledBinding Config.ShowAltGrid}" />
|
||||
<CheckBox Grid.Row="3" Grid.ColumnSpan="2" Content="{l:Translate chkHighlightTileChanges}" IsChecked="{CompiledBinding Config.HighlightTileChanges}" />
|
||||
<CheckBox Grid.Row="4" Grid.ColumnSpan="2" Content="{l:Translate chkHighlightAttributeChanges}" IsChecked="{CompiledBinding Config.HighlightAttributeChanges}" />
|
||||
</Grid>
|
||||
|
||||
<TabControl Items="{CompiledBinding Tabs}" SelectionChanged="OnTabSelected" Padding="1" DockPanel.Dock="Top">
|
||||
<Border
|
||||
BorderBrush="Gray"
|
||||
BorderThickness="1"
|
||||
Margin="3"
|
||||
Padding="3"
|
||||
IsVisible="{CompiledBinding PreviewPanel, Converter={x:Static ObjectConverters.IsNotNull}}"
|
||||
>
|
||||
<ContentControl Content="{CompiledBinding PreviewPanel}" Margin="0 0 10 0"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<TabControl
|
||||
Items="{CompiledBinding Tabs}"
|
||||
SelectedItem="{CompiledBinding SelectedTab}"
|
||||
IsVisible="{CompiledBinding ShowTabs}"
|
||||
Padding="1"
|
||||
DockPanel.Dock="Top"
|
||||
>
|
||||
<TabControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Title}" />
|
||||
</DataTemplate>
|
||||
</TabControl.ItemTemplate>
|
||||
<TabControl.DataTemplates>
|
||||
<TabControl.ContentTemplate>
|
||||
<DataTemplate></DataTemplate>
|
||||
</TabControl.DataTemplates>
|
||||
</TabControl.ContentTemplate>
|
||||
</TabControl>
|
||||
|
||||
<Border BorderBrush="Gray" BorderThickness="1">
|
||||
|
@ -66,11 +106,12 @@
|
|||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
x:Name="picViewer"
|
||||
ShowGrid="{CompiledBinding ShowGrid}"
|
||||
|
||||
Zoom="{CompiledBinding Config.ImageScale}"
|
||||
ShowGrid="{CompiledBinding Config.ShowGrid}"
|
||||
SelectionRect="{CompiledBinding SelectionRect}"
|
||||
AltGridSizeX="16"
|
||||
AltGridSizeY="16"
|
||||
ShowAltGrid="{CompiledBinding ShowAltGrid}"
|
||||
ShowAltGrid="{CompiledBinding Config.ShowAltGrid}"
|
||||
/>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
|
|
@ -11,6 +11,8 @@ using Mesen.Debugger.ViewModels;
|
|||
using Avalonia.Platform;
|
||||
using Mesen.Interop;
|
||||
using System.ComponentModel;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace Mesen.Debugger.Windows
|
||||
{
|
||||
|
@ -19,10 +21,7 @@ namespace Mesen.Debugger.Windows
|
|||
private NotificationListener _listener;
|
||||
private TilemapViewerViewModel _model;
|
||||
private PictureViewer _picViewer;
|
||||
private WriteableBitmap _viewerBitmap;
|
||||
private byte[]? _prevVram = null;
|
||||
private TilemapViewerTab _selectedTab = new TilemapViewerTab();
|
||||
|
||||
|
||||
public TilemapViewerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
@ -46,24 +45,20 @@ namespace Mesen.Debugger.Windows
|
|||
|
||||
//Renderer.DrawFps = true;
|
||||
_picViewer = this.FindControl<PictureViewer>("picViewer");
|
||||
_picViewer.Source = _viewerBitmap;
|
||||
InitBitmap(256, 256);
|
||||
_picViewer.PointerMoved += PicViewer_PointerMoved;
|
||||
_picViewer.PointerLeave += PicViewer_PointerLeave;
|
||||
_picViewer.Source = _model.ViewerBitmap;
|
||||
|
||||
_listener = new NotificationListener();
|
||||
_listener.OnNotification += listener_OnNotification;
|
||||
}
|
||||
|
||||
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
_listener?.Dispose();
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
private void InitBitmap(int width, int height)
|
||||
{
|
||||
_viewerBitmap = new WriteableBitmap(new PixelSize(width, height), new Vector(96, 96), PixelFormat.Bgra8888, AlphaFormat.Premul);
|
||||
}
|
||||
|
||||
protected override void OnDataContextChanged(EventArgs e)
|
||||
{
|
||||
if(this.DataContext is TilemapViewerViewModel model) {
|
||||
|
@ -73,41 +68,52 @@ namespace Mesen.Debugger.Windows
|
|||
}
|
||||
}
|
||||
|
||||
private void OnTabSelected(object? sender, SelectionChangedEventArgs e)
|
||||
private void PicViewer_PointerMoved(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if(e.AddedItems.Count > 0) {
|
||||
_selectedTab = (TilemapViewerTab)e.AddedItems[0]!;
|
||||
if(sender is PictureViewer viewer) {
|
||||
PixelPoint p = PixelPoint.FromPoint(e.GetCurrentPoint(viewer).Position / viewer.Zoom, 1);
|
||||
DynamicTooltip? tooltip = _model.GetPreviewPanel(p);
|
||||
|
||||
if(tooltip != null) {
|
||||
ToolTip.SetTip(viewer, tooltip);
|
||||
|
||||
//Force tooltip to update its position
|
||||
ToolTip.SetHorizontalOffset(viewer, 4);
|
||||
ToolTip.SetHorizontalOffset(viewer, 5);
|
||||
ToolTip.SetIsOpen(viewer, true);
|
||||
} else {
|
||||
ToolTip.SetTip(viewer, null);
|
||||
ToolTip.SetIsOpen(viewer, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PicViewer_PointerLeave(object? sender, PointerEventArgs e)
|
||||
{
|
||||
if(sender is PictureViewer viewer) {
|
||||
ToolTip.SetTip(viewer, null);
|
||||
ToolTip.SetIsOpen(viewer, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSettingsClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_model.Config.ShowSettingsPanel = !_model.Config.ShowSettingsPanel;
|
||||
}
|
||||
|
||||
private void UpdateTilemap<T>() where T : struct, BaseState
|
||||
{
|
||||
T ppuState = DebugApi.GetPpuState<T>(_model.CpuType);
|
||||
byte[] vram = DebugApi.GetMemoryState(_model.CpuType.GetVramMemoryType());
|
||||
byte[]? prevVram = _prevVram;
|
||||
_prevVram = vram;
|
||||
byte[]? prevVram = _model.PrevVram;
|
||||
_model.PpuState = ppuState;
|
||||
_model.PrevVram = vram;
|
||||
UInt32[] palette = PaletteHelper.GetConvertedPalette(_model.CpuType, _model.ConsoleType);
|
||||
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
GetTilemapOptions options = new GetTilemapOptions() {
|
||||
Layer = (byte)_selectedTab.Layer,
|
||||
CompareVram = prevVram,
|
||||
HighlightTileChanges = _model.HighlightTileChanges,
|
||||
HighlightAttributeChanges = _model.HighlightAttributeChanges,
|
||||
DisplayMode = _model.DisplayMode
|
||||
};
|
||||
UInt32[] palette = PaletteHelper.GetConvertedPalette(_model.CpuType, _model.ConsoleType);
|
||||
_model.UpdateBitmap(ppuState, vram, prevVram);
|
||||
|
||||
FrameInfo size = DebugApi.GetTilemapSize(_model.CpuType, options, ppuState);
|
||||
if(_viewerBitmap.PixelSize.Width != size.Width || _viewerBitmap.PixelSize.Height != size.Height) {
|
||||
InitBitmap((int)size.Width, (int)size.Height);
|
||||
}
|
||||
|
||||
using(var framebuffer = _viewerBitmap.Lock()) {
|
||||
DebugApi.GetTilemap(_model.CpuType, options, ppuState, vram, palette, framebuffer.Address);
|
||||
}
|
||||
|
||||
_picViewer.Source = _viewerBitmap;
|
||||
_picViewer.Source = _model.ViewerBitmap;
|
||||
_picViewer.InvalidateVisual();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
@ -155,6 +156,17 @@ namespace Mesen.Interop
|
|||
return DebugApi.GetTilemapSize(cpuType, options.ToInterop(), (IntPtr)ptr);
|
||||
}
|
||||
|
||||
[DllImport(DllPath)] private static extern DebugTilemapTileInfo GetTilemapTileInfo(UInt32 x, UInt32 y, CpuType cpuType, InteropGetTilemapOptions options, byte[] vram, IntPtr state);
|
||||
public unsafe static DebugTilemapTileInfo? GetTilemapTileInfo(UInt32 x, UInt32 y, CpuType cpuType, GetTilemapOptions options, byte[] vram, BaseState state)
|
||||
{
|
||||
Debug.Assert(state.GetType().IsValueType);
|
||||
|
||||
byte* ptr = stackalloc byte[Marshal.SizeOf(state.GetType())];
|
||||
Marshal.StructureToPtr(state, (IntPtr)ptr, false);
|
||||
DebugTilemapTileInfo info = DebugApi.GetTilemapTileInfo(x, y, cpuType, options.ToInterop(), vram, (IntPtr)ptr);
|
||||
return info.Row >= 0 ? info : null;
|
||||
}
|
||||
|
||||
[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);
|
||||
|
@ -748,6 +760,34 @@ namespace Mesen.Interop
|
|||
Magenta = 4
|
||||
}
|
||||
|
||||
public enum NullableBoolean
|
||||
{
|
||||
Undefined = -1,
|
||||
False = 0,
|
||||
True = 1
|
||||
}
|
||||
|
||||
public struct DebugTilemapTileInfo
|
||||
{
|
||||
public Int32 Row;
|
||||
public Int32 Column;
|
||||
|
||||
public Int32 Width;
|
||||
public Int32 Height;
|
||||
|
||||
public Int32 TileMapAddress;
|
||||
|
||||
public Int32 TileIndex;
|
||||
public Int32 TileAddress;
|
||||
|
||||
public Int32 PaletteIndex;
|
||||
public Int32 PaletteAddress;
|
||||
|
||||
public NullableBoolean HorizontalMirroring;
|
||||
public NullableBoolean VerticalMirroring;
|
||||
public NullableBoolean HighPriority;
|
||||
};
|
||||
|
||||
public struct GetTileViewOptions
|
||||
{
|
||||
public TileFormat Format;
|
||||
|
|
|
@ -57,6 +57,7 @@ namespace Mesen.Windows
|
|||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
ConfigManager.SaveConfig();
|
||||
EmuApi.Release();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue