Qt: Prototype sprite viewer

This commit is contained in:
Jeffrey Pfau 2016-10-21 01:53:51 -07:00
parent 24e51e1c85
commit 35fcb725e4
11 changed files with 620 additions and 58 deletions

View File

@ -3,6 +3,7 @@ Features:
- GBA: Support printing debug strings from inside a game
- GBA: Better cheat type autodetection
- GB: Tile viewer
- Sprite viewer
Bugfixes:
- LR35902: Fix core never exiting with certain event patterns
- GB Timer: Improve DIV reset behavior

View File

@ -0,0 +1,58 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "AssetView.h"
#include <QTimer>
extern "C" {
#ifdef M_CORE_GBA
#include "gba/gba.h"
#endif
}
using namespace QGBA;
AssetView::AssetView(GameController* controller, QWidget* parent)
: QWidget(parent)
, m_controller(controller)
, m_tileCache(controller->tileCache())
{
m_updateTimer.setSingleShot(true);
m_updateTimer.setInterval(1);
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTiles()));
connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), &m_updateTimer, SLOT(start()));
connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close()));
}
void AssetView::updateTiles(bool force) {
if (!m_controller->thread() || !m_controller->thread()->core) {
return;
}
switch (m_controller->platform()) {
#ifdef M_CORE_GBA
case PLATFORM_GBA:
updateTilesGBA(force);
break;
#endif
#ifdef M_CORE_GB
case PLATFORM_GB:
updateTilesGB(force);
break;
#endif
default:
return;
}
}
void AssetView::resizeEvent(QResizeEvent*) {
updateTiles(true);
}
void AssetView::showEvent(QShowEvent*) {
updateTiles(true);
}

View File

@ -0,0 +1,44 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_ASSET_VIEW
#define QGBA_ASSET_VIEW
#include <QWidget>
#include "GameController.h"
namespace QGBA {
class AssetView : public QWidget {
Q_OBJECT
public:
AssetView(GameController* controller, QWidget* parent = nullptr);
protected slots:
void updateTiles(bool force = false);
protected:
#ifdef M_CORE_GBA
virtual void updateTilesGBA(bool force) = 0;
#endif
#ifdef M_CORE_GB
virtual void updateTilesGB(bool force) = 0;
#endif
void resizeEvent(QResizeEvent*) override;
void showEvent(QShowEvent*) override;
const std::shared_ptr<mTileCache> m_tileCache;
private:
GameController* m_controller;
QTimer m_updateTimer;
};
}
#endif

View File

@ -70,6 +70,7 @@ set(SOURCE_FILES
AboutScreen.cpp
ArchiveInspector.cpp
AssetTile.cpp
AssetView.cpp
AudioProcessor.cpp
CheatsModel.cpp
CheatsView.cpp
@ -95,6 +96,7 @@ set(SOURCE_FILES
MemoryView.cpp
MessagePainter.cpp
MultiplayerController.cpp
ObjView.cpp
OverrideView.cpp
PaletteView.cpp
ROMInfo.cpp
@ -121,6 +123,7 @@ set(UI_FILES
LoadSaveState.ui
LogView.ui
MemoryView.ui
ObjView.ui
OverrideView.ui
PaletteView.ui
ROMInfo.ui

115
src/platform/qt/ObjView.cpp Normal file
View File

@ -0,0 +1,115 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ObjView.h"
#include "GBAApp.h"
#include <QFontDatabase>
#include <QTimer>
extern "C" {
#include "gba/gba.h"
}
using namespace QGBA;
ObjView::ObjView(GameController* controller, QWidget* parent)
: AssetView(controller, parent)
, m_controller(controller)
, m_tileStatus{}
, m_objId(0)
{
m_ui.setupUi(this);
m_ui.tile->setController(controller);
const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
m_ui.x->setFont(font);
m_ui.y->setFont(font);
m_ui.w->setFont(font);
m_ui.h->setFont(font);
m_ui.address->setFont(font);
connect(m_ui.tiles, SIGNAL(indexPressed(int)), this, SLOT(translateIndex(int)));
connect(m_ui.objId, SIGNAL(valueChanged(int)), this, SLOT(selectObj(int)));
connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
updateTiles(true);
});
}
void ObjView::selectObj(int obj) {
m_objId = obj;
updateTiles(true);
}
void ObjView::translateIndex(int index) {
m_ui.tile->selectIndex(index + m_tileOffset);
}
#ifdef M_CORE_GBA
void ObjView::updateTilesGBA(bool force) {
const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
const GBAObj* obj = &gba->video.oam.obj[m_objId];
unsigned shape = GBAObjAttributesAGetShape(obj->a);
unsigned size = GBAObjAttributesBGetSize(obj->b);
unsigned width = GBAVideoObjSizes[shape * 4 + size][0];
unsigned height = GBAVideoObjSizes[shape * 4 + size][1];
m_ui.tiles->setTileCount(width * height / 64);
m_ui.tiles->resize(QSize(width, height) * m_ui.magnification->value());
unsigned palette = GBAObjAttributesCGetPalette(obj->c);
unsigned tile = GBAObjAttributesCGetTile(obj->c);
int i = 0;
// TODO: Tile stride
if (GBAObjAttributesAIs256Color(obj->a)) {
mTileCacheSetPalette(m_tileCache.get(), 1);
m_ui.tile->setPalette(0);
m_ui.tile->setPaletteSet(1, 1024, 1536);
tile /= 2;
for (int y = 0; y < height / 8; ++y) {
for (int x = 0; x < width / 8; ++x, ++i) {
unsigned t = tile + i;
const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * t], t + 1024, 1);
if (data) {
m_ui.tiles->setTile(i, data);
} else if (force) {
m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t + 1024, 1));
}
}
}
tile += 1024;
} else {
mTileCacheSetPalette(m_tileCache.get(), 0);
m_ui.tile->setPalette(palette);
m_ui.tile->setPaletteSet(0, 2048, 3072);
for (int y = 0; y < height / 8; ++y) {
for (int x = 0; x < width / 8; ++x, ++i) {
unsigned t = tile + i;
const uint16_t* data = mTileCacheGetTileIfDirty(m_tileCache.get(), &m_tileStatus[32 * t], t + 2048, palette + 16);
if (data) {
m_ui.tiles->setTile(i, data);
} else if (force) {
m_ui.tiles->setTile(i, mTileCacheGetTile(m_tileCache.get(), t + 2048, palette + 16));
}
}
}
tile += 2048;
}
m_tileOffset = tile;
m_ui.x->setText(QString::number(GBAObjAttributesBGetX(obj->b)));
m_ui.y->setText(QString::number(GBAObjAttributesAGetY(obj->a)));
m_ui.w->setText(QString::number(width));
m_ui.h->setText(QString::number(height));
// TODO: Flags
}
#endif
#ifdef M_CORE_GB
void ObjView::updateTilesGB(bool force) {
}
#endif

49
src/platform/qt/ObjView.h Normal file
View File

@ -0,0 +1,49 @@
/* Copyright (c) 2013-2016 Jeffrey Pfau
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef QGBA_OBJ_VIEW
#define QGBA_OBJ_VIEW
#include "AssetView.h"
#include "GameController.h"
#include "ui_ObjView.h"
extern "C" {
#include "core/tile-cache.h"
}
namespace QGBA {
class ObjView : public AssetView {
Q_OBJECT
public:
ObjView(GameController* controller, QWidget* parent = nullptr);
private slots:
void selectObj(int);
void translateIndex(int);
private:
#ifdef M_CORE_GBA
void updateTilesGBA(bool force) override;
#endif
#ifdef M_CORE_GB
void updateTilesGB(bool force) override;
#endif
Ui::ObjView m_ui;
GameController* m_controller;
mTileCacheEntry m_tileStatus[1024 * 32]; // TODO: Correct size
int m_objId;
int m_tileOffset;
};
}
#endif

326
src/platform/qt/ObjView.ui Normal file
View File

@ -0,0 +1,326 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ObjView</class>
<widget class="QWidget" name="ObjView">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Sprites</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1">
<item row="0" column="1" rowspan="5">
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0" alignment="Qt::AlignHCenter|Qt::AlignVCenter">
<widget class="QGBA::TilePainter" name="tiles" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>8</width>
<height>8</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QGBA::AssetTile" name="tile">
<property name="title">
<string>Tile</string>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QSpinBox" name="magnification">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string>×</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>4</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Magnification</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QSpinBox" name="objId">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximum">
<number>127</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Object</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Address</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="address">
<property name="text">
<string>0x07000000</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Position</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="x">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tileId_3">
<property name="text">
<string>, </string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="y">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>0</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Dimensions</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="w">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>8</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="tileId_5">
<property name="text">
<string>×</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="h">
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>8</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QGBA::TilePainter</class>
<extends>QWidget</extends>
<header>TilePainter.h</header>
<container>1</container>
<slots>
<slot>setTileMagnification(int)</slot>
</slots>
</customwidget>
<customwidget>
<class>QGBA::AssetTile</class>
<extends>QGroupBox</extends>
<header>AssetTile.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>magnification</sender>
<signal>valueChanged(int)</signal>
<receiver>tiles</receiver>
<slot>setTileMagnification(int)</slot>
<hints>
<hint type="sourcelabel">
<x>36</x>
<y>58</y>
</hint>
<hint type="destinationlabel">
<x>278</x>
<y>149</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -28,6 +28,11 @@ void TilePainter::paintEvent(QPaintEvent* event) {
void TilePainter::resizeEvent(QResizeEvent* event) {
int w = width() / m_size;
if (w < 1) {
// FIXME: Uhh...how did we get here?
// Resizing the window when magnification > 1 seems to trigger this
return;
}
int calculatedHeight = (m_tileCount + w - 1) * m_size / w;
calculatedHeight -= calculatedHeight % m_size;
if (width() / m_size != m_backing.width() / m_size || m_backing.height() != calculatedHeight) {
@ -55,10 +60,13 @@ void TilePainter::setTile(int index, const uint16_t* data) {
void TilePainter::setTileCount(int tiles) {
m_tileCount = tiles;
int w = width() / m_size;
int h = (tiles + w - 1) * m_size / w;
setMinimumSize(16, h - (h % m_size));
resizeEvent(nullptr);
if (sizePolicy().verticalPolicy() != QSizePolicy::Fixed) {
// Only manage the size ourselves if we don't appear to have something else managing it
int w = width() / m_size;
int h = (tiles + w - 1) * m_size / w;
setMinimumSize(m_size, h - (h % m_size));
resizeEvent(nullptr);
}
}
void TilePainter::setTileMagnification(int mag) {

View File

@ -10,28 +10,17 @@
#include <QFontDatabase>
#include <QTimer>
extern "C" {
#include "gba/gba.h"
}
using namespace QGBA;
TileView::TileView(GameController* controller, QWidget* parent)
: QWidget(parent)
: AssetView(controller, parent)
, m_controller(controller)
, m_tileStatus{}
, m_tileCache(controller->tileCache())
, m_paletteId(0)
{
m_ui.setupUi(this);
m_ui.tile->setController(controller);
m_updateTimer.setSingleShot(true);
m_updateTimer.setInterval(1);
connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(updateTiles()));
connect(m_controller, SIGNAL(frameAvailable(const uint32_t*)), &m_updateTimer, SLOT(start()));
connect(m_controller, SIGNAL(gameStopped(mCoreThread*)), this, SLOT(close()));
connect(m_ui.tiles, SIGNAL(indexPressed(int)), m_ui.tile, SLOT(selectIndex(int)));
connect(m_ui.paletteId, SIGNAL(valueChanged(int)), this, SLOT(updatePalette(int)));
@ -84,27 +73,6 @@ TileView::TileView(GameController* controller, QWidget* parent)
});
}
void TileView::updateTiles(bool force) {
if (!m_controller->thread() || !m_controller->thread()->core) {
return;
}
switch (m_controller->platform()) {
#ifdef M_CORE_GBA
case PLATFORM_GBA:
updateTilesGBA(force);
break;
#endif
#ifdef M_CORE_GB
case PLATFORM_GB:
updateTilesGB(force);
break;
#endif
default:
return;
}
}
#ifdef M_CORE_GBA
void TileView::updateTilesGBA(bool force) {
if (m_ui.palette256->isChecked()) {
@ -169,11 +137,3 @@ void TileView::updatePalette(int palette) {
m_ui.tile->setPalette(palette);
updateTiles(true);
}
void TileView::resizeEvent(QResizeEvent*) {
updateTiles(true);
}
void TileView::showEvent(QShowEvent*) {
updateTiles(true);
}

View File

@ -6,8 +6,7 @@
#ifndef QGBA_TILE_VIEW
#define QGBA_TILE_VIEW
#include <QWidget>
#include "AssetView.h"
#include "GameController.h"
#include "ui_TileView.h"
@ -18,35 +17,28 @@ extern "C" {
namespace QGBA {
class TileView : public QWidget {
class TileView : public AssetView {
Q_OBJECT
public:
TileView(GameController* controller, QWidget* parent = nullptr);
public slots:
void updateTiles(bool force = false);
void updatePalette(int);
protected:
void resizeEvent(QResizeEvent*) override;
void showEvent(QShowEvent*) override;
private:
#ifdef M_CORE_GBA
void updateTilesGBA(bool force);
void updateTilesGBA(bool force) override;
#endif
#ifdef M_CORE_GB
void updateTilesGB(bool force);
void updateTilesGB(bool force) override;
#endif
Ui::TileView m_ui;
GameController* m_controller;
std::shared_ptr<mTileCache> m_tileCache;
mTileCacheEntry m_tileStatus[3072 * 32]; // TODO: Correct size
int m_paletteId;
QTimer m_updateTimer;
};
}

View File

@ -30,13 +30,14 @@
#include "MultiplayerController.h"
#include "MemoryView.h"
#include "OverrideView.h"
#include "ObjView.h"
#include "PaletteView.h"
#include "TileView.h"
#include "ROMInfo.h"
#include "SensorView.h"
#include "SettingsView.h"
#include "ShaderSelector.h"
#include "ShortcutController.h"
#include "TileView.h"
#include "VideoView.h"
extern "C" {
@ -1340,6 +1341,11 @@ void Window::setupMenu(QMenuBar* menubar) {
m_gameActions.append(paletteView);
addControlledAction(toolsMenu, paletteView, "paletteWindow");
QAction* objView = new QAction(tr("View &sprites..."), toolsMenu);
connect(objView, &QAction::triggered, openTView<ObjView>());
m_gameActions.append(objView);
addControlledAction(toolsMenu, objView, "spriteWindow");
QAction* tileView = new QAction(tr("View &tiles..."), toolsMenu);
connect(tileView, &QAction::triggered, openTView<TileView>());
m_gameActions.append(tileView);