BizHawk/BizHawk.Client.EmuHawk/tools/GBA/GBAGPUView.cs

849 lines
24 KiB
C#

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Cores.Nintendo.Gameboy;
using BizHawk.Emulation.Cores.Nintendo.GBA;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.EmuHawk
{
public partial class GbaGpuView : Form, IToolFormAutoConfig
{
[RequiredService]
private IGBAGPUViewable GBA { get; set; }
// emulator memory areas
private IntPtr _vram;
private IntPtr _oam;
private IntPtr _mmio;
private IntPtr _palRam;
// color conversion to RGB888
private readonly int[] _colorConversion;
private MobileBmpView _bg0, _bg1, _bg2, _bg3, _bgPal, _spPal, _sprites, _bgTiles16, _bgTiles256, _spTiles16, _spTiles256;
// MobileDetailView memory;
public bool AskSaveChanges() => true;
public bool UpdateBefore => true;
public GbaGpuView()
{
InitializeComponent();
// TODO: hook up something
// we do this twice to avoid having to & 0x7fff with every color
int[] tmp = GBColors.GetLut(GBColors.ColorType.vivid);
_colorConversion = new int[65536];
Buffer.BlockCopy(tmp, 0, _colorConversion, 0, sizeof(int) * tmp.Length);
Buffer.BlockCopy(tmp, 0, _colorConversion, sizeof(int) * tmp.Length, sizeof(int) * tmp.Length);
radioButtonManual.Checked = true;
GenerateWidgets();
hScrollBar1_ValueChanged(null, null);
RecomputeRefresh();
}
#region drawing primitives
private unsafe void DrawTile256(int* dest, int pitch, byte* tile, ushort* palette, bool hFlip, bool vFlip)
{
if (vFlip)
{
dest += pitch * 7;
pitch = -pitch;
}
if (hFlip)
{
dest += 7;
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
*dest-- = _colorConversion[palette[*tile++]];
}
dest += 8;
dest += pitch;
}
}
else
{
for (int y = 0; y < 8; y++)
{
for (int x = 0; x < 8; x++)
{
*dest++ = _colorConversion[palette[*tile++]];
}
dest -= 8;
dest += pitch;
}
}
}
private unsafe void DrawTile16(int* dest, int pitch, byte* tile, ushort* palette, bool hFlip, bool vFlip)
{
if (vFlip)
{
dest += pitch * 7;
pitch = -pitch;
}
if (hFlip)
{
dest += 7;
for (int y = 0; y < 8; y++)
{
for (int i = 0; i < 4; i++)
{
*dest-- = _colorConversion[palette[*tile & 15]];
*dest-- = _colorConversion[palette[*tile >> 4]];
tile++;
}
dest += 8;
dest += pitch;
}
}
else
{
for (int y = 0; y < 8; y++)
{
for (int i = 0; i < 4; i++)
{
*dest++ = _colorConversion[palette[*tile & 15]];
*dest++ = _colorConversion[palette[*tile >> 4]];
tile++;
}
dest -= 8;
dest += pitch;
}
}
}
unsafe void DrawTextNameTable16(int* dest, int pitch, ushort* nametable, byte* tiles)
{
for (int ty = 0; ty < 32; ty++)
{
for (int tx = 0; tx < 32; tx++)
{
ushort ntent = *nametable++;
DrawTile16(dest, pitch, tiles + (ntent & 1023) * 32, (ushort*)_palRam + (ntent >> 12 << 4), ntent.Bit(10), ntent.Bit(11));
dest += 8;
}
dest -= 256;
dest += 8 * pitch;
}
}
unsafe void DrawTextNameTable256(int* dest, int pitch, ushort* nametable, byte* tiles)
{
for (int ty = 0; ty < 32; ty++)
{
for (int tx = 0; tx < 32; tx++)
{
ushort ntent = *nametable++;
DrawTile256(dest, pitch, tiles + (ntent & 1023) * 64, (ushort*)_palRam, ntent.Bit(10), ntent.Bit(11));
dest += 8;
}
dest -= 256;
dest += 8 * pitch;
}
}
unsafe void DrawTextNameTable(int* dest, int pitch, ushort* nametable, byte* tiles, bool eightbit)
{
if (eightbit)
DrawTextNameTable256(dest, pitch, nametable, tiles);
else
DrawTextNameTable16(dest, pitch, nametable, tiles);
}
unsafe void DrawTextBG(int n, MobileBmpView mbv)
{
ushort bgcnt = ((ushort*)_mmio)[4 + n];
int ssize = bgcnt >> 14;
switch (ssize)
{
case 0: mbv.ChangeAllSizes(256, 256); break;
case 1: mbv.ChangeAllSizes(512, 256); break;
case 2: mbv.ChangeAllSizes(256, 512); break;
case 3: mbv.ChangeAllSizes(512, 512); break;
}
Bitmap bmp = mbv.BmpView.BMP;
var lockData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
int* pixels = (int*)lockData.Scan0;
int pitch = lockData.Stride / sizeof(int);
byte* tiles = (byte*)_vram + ((bgcnt & 0xc) << 12);
ushort* nametable = (ushort*)_vram + ((bgcnt & 0x1f00) << 2);
bool eighthBit = bgcnt.Bit(7);
switch (ssize)
{
case 0:
DrawTextNameTable(pixels, pitch, nametable, tiles, eighthBit);
break;
case 1:
DrawTextNameTable(pixels, pitch, nametable, tiles, eighthBit);
pixels += 256;
nametable += 1024;
DrawTextNameTable(pixels, pitch, nametable, tiles, eighthBit);
break;
case 2:
DrawTextNameTable(pixels, pitch, nametable, tiles, eighthBit);
pixels += pitch * 256;
nametable += 1024;
DrawTextNameTable(pixels, pitch, nametable, tiles, eighthBit);
break;
case 3:
DrawTextNameTable(pixels, pitch, nametable, tiles, eighthBit);
pixels += 256;
nametable += 1024;
DrawTextNameTable(pixels, pitch, nametable, tiles, eighthBit);
pixels -= 256;
pixels += pitch * 256;
nametable += 1024;
DrawTextNameTable(pixels, pitch, nametable, tiles, eighthBit);
pixels += 256;
nametable += 1024;
DrawTextNameTable(pixels, pitch, nametable, tiles, eighthBit);
break;
}
bmp.UnlockBits(lockData);
mbv.BmpView.Refresh();
}
unsafe void DrawAffineBG(int n, MobileBmpView mbv)
{
ushort bgcnt = ((ushort*)_mmio)[4 + n];
int ssize = bgcnt >> 14;
switch (ssize)
{
case 0: mbv.ChangeAllSizes(128, 128); break;
case 1: mbv.ChangeAllSizes(256, 256); break;
case 2: mbv.ChangeAllSizes(512, 512); break;
case 3: mbv.ChangeAllSizes(1024, 1024); break;
}
Bitmap bmp = mbv.BmpView.BMP;
var lockData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
int* pixels = (int*)lockData.Scan0;
int pitch = lockData.Stride / sizeof(int);
byte* tiles = (byte*)_vram + ((bgcnt & 0xc) << 12);
byte* nametable = (byte*)_vram + ((bgcnt & 0x1f00) << 3);
for (int ty = 0; ty < bmp.Height / 8; ty++)
{
for (int tx = 0; tx < bmp.Width / 8; tx++)
{
DrawTile256(pixels, pitch, tiles + *nametable++ * 64, (ushort*)_palRam, false, false);
pixels += 8;
}
pixels -= bmp.Width;
pixels += 8 * pitch;
}
bmp.UnlockBits(lockData);
mbv.BmpView.Refresh();
}
unsafe void DrawM3BG(MobileBmpView mbv)
{
mbv.ChangeAllSizes(240, 160);
Bitmap bmp = mbv.BmpView.BMP;
var lockData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
int* pixels = (int*)lockData.Scan0;
int pitch = lockData.Stride / sizeof(int);
ushort* frame = (ushort*)_vram;
for (int y = 0; y < 160; y++)
{
for (int x = 0; x < 240; x++)
{
*pixels++ = _colorConversion[*frame++];
}
pixels -= 240;
pixels += pitch;
}
bmp.UnlockBits(lockData);
mbv.BmpView.Refresh();
}
unsafe void DrawM4BG(MobileBmpView mbv, bool secondFrame)
{
mbv.ChangeAllSizes(240, 160);
Bitmap bmp = mbv.BmpView.BMP;
var lockData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
int* pixels = (int*)lockData.Scan0;
int pitch = lockData.Stride / sizeof(int);
byte* frame = (byte*)_vram + (secondFrame ? 40960 : 0);
ushort* palette = (ushort*)_palRam;
for (int y = 0; y < 160; y++)
{
for (int x = 0; x < 240; x++)
{
*pixels++ = _colorConversion[palette[*frame++]];
}
pixels -= 240;
pixels += pitch;
}
bmp.UnlockBits(lockData);
mbv.BmpView.Refresh();
}
unsafe void DrawM5BG(MobileBmpView mbv, bool secondFrame)
{
mbv.ChangeAllSizes(160, 128);
Bitmap bmp = mbv.BmpView.BMP;
var lockData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
int* pixels = (int*)lockData.Scan0;
int pitch = lockData.Stride / sizeof(int);
ushort* frame = (ushort*)_vram + (secondFrame ? 20480 : 0);
for (int y = 0; y < 128; y++)
{
for (int x = 0; x < 160; x++)
*pixels++ = _colorConversion[*frame++];
pixels -= 160;
pixels += pitch;
}
bmp.UnlockBits(lockData);
mbv.BmpView.Refresh();
}
private static readonly int[, ,] SpriteSizes = { { { 1, 1 }, { 2, 2 }, { 4, 4 }, { 8, 8 } }, { { 2, 1 }, { 4, 1 }, { 4, 2 }, { 8, 4 } }, { { 1, 2 }, { 1, 4 }, { 2, 4 }, { 4, 8 } } };
unsafe void DrawSprite(int* dest, int pitch, ushort* sprite, byte* tiles, bool twodee)
{
ushort attr0 = sprite[0];
ushort attr1 = sprite[1];
ushort attr2 = sprite[2];
if (!attr0.Bit(8) && attr0.Bit(9))
return; // 2x with affine off
int shape = attr0 >> 14;
if (shape == 3)
return;
int size = attr1 >> 14;
int tw = SpriteSizes[shape, size, 0];
int th = SpriteSizes[shape, size, 1];
bool eighthBit = attr0.Bit(13);
bool hFlip = attr1.Bit(12);
bool vFlip = attr1.Bit(13);
ushort* palette = (ushort*)_palRam + 256;
if (!eighthBit)
palette += attr2 >> 12 << 4;
int tileIndex = eighthBit ? attr2 & 1022 : attr2 & 1023;
int tileStride = twodee ? 1024 - tw * (eighthBit ? 64 : 32) : 0;
// see if the sprite would read past the end of vram, and skip it if it would
{
int tileEnd;
if (!twodee)
tileEnd = tileIndex + tw * th * (eighthBit ? 2 : 1);
else
tileEnd = tileIndex + tw * (eighthBit ? 2 : 1) + (th - 1) * 32;
if (tileEnd > 1024)
return;
}
tiles += 32 * tileIndex;
if (vFlip)
dest += pitch * 8 * (th - 1);
if (hFlip)
dest += 8 * (tw - 1);
for (int ty = 0; ty < th; ty++)
{
for (int tx = 0; tx < tw; tx++)
{
if (eighthBit)
{
DrawTile256(dest, pitch, tiles, palette, hFlip, vFlip);
tiles += 64;
}
else
{
DrawTile16(dest, pitch, tiles, palette, hFlip, vFlip);
tiles += 32;
}
if (hFlip)
dest -= 8;
else
dest += 8;
}
if (hFlip)
dest += tw * 8;
else
dest -= tw * 8;
if (vFlip)
dest -= pitch * 8;
else
dest += pitch * 8;
tiles += tileStride;
}
}
unsafe void DrawSprites(MobileBmpView mbv)
{
mbv.BmpView.ChangeBitmapSize(1024, 512);
Bitmap bmp = mbv.BmpView.BMP;
var lockData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Win32Imports.MemSet(lockData.Scan0, 0xff, (uint)(lockData.Height * lockData.Stride));
int* pixels = (int*)lockData.Scan0;
int pitch = lockData.Stride / sizeof(int);
ushort* sprites = (ushort*)_oam;
byte* tiles = (byte*)_vram + 65536;
ushort dispcnt = ((ushort*)_mmio)[0];
bool twodee = !dispcnt.Bit(6);
for (int sy = 0; sy < 8; sy++)
{
for (int sx = 0; sx < 16; sx++)
{
DrawSprite(pixels, pitch, sprites, tiles, twodee);
pixels += 64;
sprites += 4;
}
pixels -= 1024;
pixels += pitch * 64;
}
bmp.UnlockBits(lockData);
mbv.BmpView.Refresh();
}
unsafe void DrawPalette(MobileBmpView mbv, bool sprite)
{
mbv.BmpView.ChangeBitmapSize(16, 16);
Bitmap bmp = mbv.BmpView.BMP;
var lockData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
int* pixels = (int*)lockData.Scan0;
int pitch = lockData.Stride / sizeof(int);
ushort* palette = (ushort*)_palRam + (sprite ? 256 : 0);
for (int j = 0; j < 16; j++)
{
for (int i = 0; i < 16; i++)
*pixels++ = _colorConversion[*palette++];
pixels -= 16;
pixels += pitch;
}
bmp.UnlockBits(lockData);
mbv.BmpView.Refresh();
}
unsafe void DrawTileRange(int* dest, int pitch, byte* tiles, ushort* palette, int tw, int th, bool eightbit)
{
for (int ty = 0; ty < th; ty++)
{
for (int tx = 0; tx < tw; tx++)
{
if (eightbit)
{
DrawTile256(dest, pitch, tiles, palette, false, false);
dest += 8;
tiles += 64;
}
else
{
DrawTile16(dest, pitch, tiles, palette, false, false);
dest += 8;
tiles += 32;
}
}
dest -= tw * 8;
dest += pitch * 8;
}
}
unsafe void DrawSpriteTiles(MobileBmpView mbv, bool tophalfonly, bool eightbit)
{
int tw = eightbit ? 16 : 32;
int th = tophalfonly ? 16 : 32;
mbv.BmpView.ChangeBitmapSize(tw * 8, 256);
Bitmap bmp = mbv.BmpView.BMP;
var lockData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
int* pixels = (int*)lockData.Scan0;
int pitch = lockData.Stride / sizeof(int);
byte* tiles = (byte*)_vram + 65536;
// TODO: palette changing (in 4 bit mode anyway)
ushort* palette = (ushort*)_palRam + 256;
if (tophalfonly)
{
Win32Imports.MemSet(lockData.Scan0, 0xff, (uint)(128 * lockData.Stride));
pixels += 128 * pitch;
tiles += 16384;
}
DrawTileRange(pixels, pitch, tiles, palette, tw, th, eightbit);
bmp.UnlockBits(lockData);
mbv.BmpView.Refresh();
}
unsafe void DrawBGTiles(MobileBmpView mbv, bool eightbit)
{
int tw = eightbit ? 32 : 64;
int th = 32;
mbv.BmpView.ChangeBitmapSize(tw * 8, th * 8);
Bitmap bmp = mbv.BmpView.BMP;
var lockData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
int* pixels = (int*)lockData.Scan0;
int pitch = lockData.Stride / sizeof(int);
byte* tiles = (byte*)_vram;
// TODO: palette changing (in 4 bit mode anyway)
ushort* palette = (ushort*)_palRam;
DrawTileRange(pixels, pitch, tiles, palette, tw, th, eightbit);
bmp.UnlockBits(lockData);
mbv.BmpView.Refresh();
}
#endregion
unsafe void DrawEverything()
{
ushort dispcnt = ((ushort*)_mmio)[0];
int bgMode = dispcnt & 7;
switch (bgMode)
{
case 0:
if (_bg0.ShouldDraw) DrawTextBG(0, _bg0);
if (_bg1.ShouldDraw) DrawTextBG(1, _bg1);
if (_bg2.ShouldDraw) DrawTextBG(2, _bg2);
if (_bg3.ShouldDraw) DrawTextBG(3, _bg3);
if (_bgTiles16.ShouldDraw) DrawBGTiles(_bgTiles16, false);
if (_bgTiles256.ShouldDraw) DrawBGTiles(_bgTiles256, true);
if (_spTiles16.ShouldDraw) DrawSpriteTiles(_spTiles16, false, false);
if (_spTiles256.ShouldDraw) DrawSpriteTiles(_spTiles256, false, true);
break;
case 1:
if (_bg0.ShouldDraw) DrawTextBG(0, _bg0);
if (_bg1.ShouldDraw) DrawTextBG(1, _bg1);
if (_bg2.ShouldDraw) DrawAffineBG(2, _bg2);
if (_bg3.ShouldDraw) _bg3.BmpView.Clear();
if (_bgTiles16.ShouldDraw) DrawBGTiles(_bgTiles16, false);
if (_bgTiles256.ShouldDraw) DrawBGTiles(_bgTiles256, true);
if (_spTiles16.ShouldDraw) DrawSpriteTiles(_spTiles16, false, false);
if (_spTiles256.ShouldDraw) DrawSpriteTiles(_spTiles256, false, true);
break;
case 2:
if (_bg0.ShouldDraw) _bg0.BmpView.Clear();
if (_bg1.ShouldDraw) _bg1.BmpView.Clear();
if (_bg2.ShouldDraw) DrawAffineBG(2, _bg2);
if (_bg3.ShouldDraw) DrawAffineBG(3, _bg3);
// while there are no 4bpp tiles possible in mode 2, there might be some in memory
// due to midframe mode switching. no real reason not to display them if that's
// what the user wants to see
if (_bgTiles16.ShouldDraw) DrawBGTiles(_bgTiles16, false);
if (_bgTiles256.ShouldDraw) DrawBGTiles(_bgTiles256, true);
if (_spTiles16.ShouldDraw) DrawSpriteTiles(_spTiles16, false, false);
if (_spTiles256.ShouldDraw) DrawSpriteTiles(_spTiles256, false, true);
break;
case 3:
if (_bg0.ShouldDraw) _bg0.BmpView.Clear();
if (_bg1.ShouldDraw) _bg1.BmpView.Clear();
if (_bg2.ShouldDraw) DrawM3BG(_bg2);
if (_bg3.ShouldDraw) _bg3.BmpView.Clear();
if (_bgTiles16.ShouldDraw) _bgTiles16.BmpView.Clear();
if (_bgTiles256.ShouldDraw) _bgTiles256.BmpView.Clear();
if (_spTiles16.ShouldDraw) DrawSpriteTiles(_spTiles16, true, false);
if (_spTiles256.ShouldDraw) DrawSpriteTiles(_spTiles256, true, true);
break;
//in modes 4, 5, bg3 is repurposed as bg2 invisible frame
case 4:
if (_bg0.ShouldDraw) _bg0.BmpView.Clear();
if (_bg1.ShouldDraw) _bg1.BmpView.Clear();
if (_bg2.ShouldDraw) DrawM4BG(_bg2, dispcnt.Bit(4));
if (_bg3.ShouldDraw) DrawM4BG(_bg3, !dispcnt.Bit(4));
if (_bgTiles16.ShouldDraw) _bgTiles16.BmpView.Clear();
if (_bgTiles256.ShouldDraw) _bgTiles256.BmpView.Clear();
if (_spTiles16.ShouldDraw) DrawSpriteTiles(_spTiles16, true, false);
if (_spTiles256.ShouldDraw) DrawSpriteTiles(_spTiles256, true, true);
break;
case 5:
if (_bg0.ShouldDraw) _bg0.BmpView.Clear();
if (_bg1.ShouldDraw) _bg1.BmpView.Clear();
if (_bg2.ShouldDraw) DrawM5BG(_bg2, dispcnt.Bit(4));
if (_bg3.ShouldDraw) DrawM5BG(_bg3, !dispcnt.Bit(4));
if (_bgTiles16.ShouldDraw) _bgTiles16.BmpView.Clear();
if (_bgTiles256.ShouldDraw) _bgTiles256.BmpView.Clear();
if (_spTiles16.ShouldDraw) DrawSpriteTiles(_spTiles16, true, false);
if (_spTiles256.ShouldDraw) DrawSpriteTiles(_spTiles256, true, true);
break;
default:
// shouldn't happen, but shouldn't be our problem either
if (_bg0.ShouldDraw) _bg0.BmpView.Clear();
if (_bg1.ShouldDraw) _bg1.BmpView.Clear();
if (_bg2.ShouldDraw) _bg2.BmpView.Clear();
if (_bg3.ShouldDraw) _bg3.BmpView.Clear();
if (_bgTiles16.ShouldDraw) _bgTiles16.BmpView.Clear();
if (_bgTiles256.ShouldDraw) _bgTiles256.BmpView.Clear();
if (_spTiles16.ShouldDraw) _spTiles16.BmpView.Clear();
if (_spTiles256.ShouldDraw) _spTiles256.BmpView.Clear();
break;
}
if (_bgPal.ShouldDraw) DrawPalette(_bgPal, false);
if (_spPal.ShouldDraw) DrawPalette(_spPal, true);
if (_sprites.ShouldDraw) DrawSprites(_sprites);
}
private MobileBmpView MakeMBVWidget(string text, int w, int h)
{
var mbv = new MobileBmpView { Text = text };
mbv.BmpView.Text = text;
mbv.TopLevel = false;
mbv.ChangeViewSize(w, h);
mbv.BmpView.Clear();
panel1.Controls.Add(mbv);
listBoxWidgets.Items.Add(mbv);
return mbv;
}
private MobileDetailView MakeMDVWidget(string text, int w, int h)
{
var mdv = new MobileDetailView { Text = text };
mdv.BmpView.Text = text;
mdv.TopLevel = false;
mdv.ClientSize = new Size(w, h);
mdv.BmpView.Clear();
panel1.Controls.Add(mdv);
listBoxWidgets.Items.Add(mdv);
return mdv;
}
void GenerateWidgets()
{
listBoxWidgets.BeginUpdate();
_bg0 = MakeMBVWidget("Background 0", 256, 256);
_bg1 = MakeMBVWidget("Background 1", 256, 256);
_bg2 = MakeMBVWidget("Background 2", 256, 256);
_bg3 = MakeMBVWidget("Background 3", 256, 256);
_bgPal = MakeMBVWidget("Background Palettes", 256, 256);
_spPal = MakeMBVWidget("Sprite Palettes", 256, 256);
_sprites = MakeMBVWidget("Sprites", 1024, 512);
_spTiles16 = MakeMBVWidget("Sprite Tiles (4bpp)", 256, 256);
_spTiles256 = MakeMBVWidget("Sprite Tiles (8bpp)", 128, 256);
_bgTiles16 = MakeMBVWidget("Background Tiles (4bpp)", 512, 256);
_bgTiles256 = MakeMBVWidget("Background Tiles (8bpp)", 256, 256);
// todo: finish these
// MakeMDVWidget("Details", 128, 192);
// memory = MakeMDVWidget("Details - Memory", 128, 192);
listBoxWidgets.EndUpdate();
foreach (var f in listBoxWidgets.Items)
{
Form form = (Form)f;
// close becomes hide
form.FormClosing += delegate(object sender, FormClosingEventArgs e)
{
e.Cancel = true;
listBoxWidgets.Items.Add(sender);
(sender as Form).Hide();
};
// hackish, and why doesn't winforms handle this directly?
BringToFrontHack(form, form);
}
}
static void BringToFrontHack(Control c, Control top)
{
c.Click += (o, e) => top.BringToFront();
if (c.HasChildren)
{
foreach (Control cc in c.Controls)
{
BringToFrontHack(cc, top);
}
}
}
public void Restart()
{
var mem = GBA.GetMemoryAreas();
_vram = mem.vram;
_palRam = mem.palram;
_oam = mem.oam;
_mmio = mem.mmio;
_cbScanlineEmu = 500; // force an update
UpdateValues();
}
public void NewUpdate(ToolFormUpdateType type) { }
/// <summary>belongs in ToolsBefore</summary>
public void UpdateValues()
{
if (!IsHandleCreated || IsDisposed)
{
return;
}
if (_cbScanlineEmu != _cbScanline)
{
_cbScanlineEmu = _cbScanline;
if (!_cbScanline.HasValue) // manual, deactivate callback
{
GBA.SetScanlineCallback(null, 0);
}
else
{
GBA.SetScanlineCallback(DrawEverything, _cbScanline.Value);
}
}
}
public void FastUpdate()
{
// Do nothing
}
void ShowSelectedWidget()
{
if (listBoxWidgets.SelectedItem != null)
{
var form = listBoxWidgets.SelectedItem as Form;
form.Show();
form.BringToFront();
listBoxWidgets.Items.RemoveAt(listBoxWidgets.SelectedIndex);
}
}
private void buttonShowWidget_Click(object sender, EventArgs e)
{
ShowSelectedWidget();
}
private void listBoxWidgets_DoubleClick(object sender, EventArgs e)
{
ShowSelectedWidget();
}
#region refresh control
private int? _cbScanline;
private int? _cbScanlineEmu = 500;
private void RecomputeRefresh()
{
if (radioButtonScanline.Checked)
{
hScrollBar1.Enabled = true;
buttonRefresh.Enabled = false;
_cbScanline = (hScrollBar1.Value + 160) % 228;
}
else if (radioButtonManual.Checked)
{
hScrollBar1.Enabled = false;
buttonRefresh.Enabled = true;
_cbScanline = null;
}
}
private void radioButtonScanline_CheckedChanged(object sender, EventArgs e)
{
RecomputeRefresh();
}
private void hScrollBar1_ValueChanged(object sender, EventArgs e)
{
_cbScanline = (hScrollBar1.Value + 160) % 228;
radioButtonScanline.Text = $"Scanline {_cbScanline}";
}
private void radioButtonManual_CheckedChanged(object sender, EventArgs e)
{
RecomputeRefresh();
}
private void buttonRefresh_Click(object sender, EventArgs e)
{
DrawEverything();
}
#endregion
private void GbaGpuView_FormClosed(object sender, FormClosedEventArgs e)
{
GBA?.SetScanlineCallback(null, 0);
}
#region copy to clipboard
private void timerMessage_Tick(object sender, EventArgs e)
{
timerMessage.Stop();
labelClipboard.Text = "CTRL + C: Copy under mouse to clipboard.";
}
private void GbaGpuView_KeyDown(object sender, KeyEventArgs e)
{
if (ModifierKeys.HasFlag(Keys.Control) && e.KeyCode == Keys.C)
{
// find the control under the mouse
Point m = Cursor.Position;
Control top = this;
Control found;
do
{
found = top.GetChildAtPoint(top.PointToClient(m));
top = found;
} while (found != null && found.HasChildren);
if (found is BmpView view)
{
Clipboard.SetImage(view.BMP);
labelClipboard.Text = $"{view.Text} copied to clipboard.";
timerMessage.Stop();
timerMessage.Start();
}
}
}
#endregion
}
}