src/platform/qt/ObjView.cpp (view raw)
1/* Copyright (c) 2013-2016 Jeffrey Pfau
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6#include "ObjView.h"
7
8#include "CoreController.h"
9#include "GBAApp.h"
10
11#include <QAction>
12#include <QClipboard>
13#include <QFontDatabase>
14#include <QListWidgetItem>
15#include <QTimer>
16
17#include "LogController.h"
18#include "VFileDevice.h"
19
20#ifdef M_CORE_GBA
21#include <mgba/internal/gba/gba.h>
22#endif
23#ifdef M_CORE_GB
24#include <mgba/internal/gb/gb.h>
25#endif
26#include <mgba-util/vfs.h>
27
28using namespace QGBA;
29
30ObjView::ObjView(std::shared_ptr<CoreController> controller, QWidget* parent)
31 : AssetView(controller, parent)
32 , m_controller(controller)
33{
34 m_ui.setupUi(this);
35 m_ui.tile->setController(controller);
36
37 const QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
38
39 m_ui.x->setFont(font);
40 m_ui.y->setFont(font);
41 m_ui.w->setFont(font);
42 m_ui.h->setFont(font);
43 m_ui.address->setFont(font);
44 m_ui.priority->setFont(font);
45 m_ui.palette->setFont(font);
46 m_ui.transform->setFont(font);
47 m_ui.xformPA->setFont(font);
48 m_ui.xformPB->setFont(font);
49 m_ui.xformPC->setFont(font);
50 m_ui.xformPD->setFont(font);
51 m_ui.mode->setFont(font);
52
53 connect(m_ui.tiles, &TilePainter::indexPressed, this, &ObjView::translateIndex);
54 connect(m_ui.tiles, &TilePainter::needsRedraw, this, [this]() {
55 updateTiles(true);
56 });
57 connect(m_ui.objId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ObjView::selectObj);
58 connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
59 updateTiles(true);
60 });
61 connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
62 connect(m_ui.copyButton, &QAbstractButton::clicked, this, &ObjView::copyObj);
63
64 connect(m_ui.objList, &QListWidget::currentItemChanged, [this]() {
65 QListWidgetItem* item = m_ui.objList->currentItem();
66 if (item) {
67 selectObj(item->data(Qt::UserRole).toInt());
68 }
69 });
70
71 QAction* exportAction = new QAction(this);
72 exportAction->setShortcut(QKeySequence::Save);
73 connect(exportAction, &QAction::triggered, this, &ObjView::exportObj);
74 addAction(exportAction);
75
76 QAction* copyAction = new QAction(this);
77 copyAction->setShortcut(QKeySequence::Copy);
78 connect(copyAction, &QAction::triggered, this, &ObjView::copyObj);
79 addAction(copyAction);
80}
81
82void ObjView::selectObj(int obj) {
83 m_objId = obj;
84 bool blocked = m_ui.objId->blockSignals(true);
85 m_ui.objId->setValue(m_objId);
86 m_ui.objId->blockSignals(blocked);
87 if (m_objs.size() > obj) {
88 blocked = m_ui.objList->blockSignals(true);
89 m_ui.objList->setCurrentItem(m_objs[obj]);
90 m_ui.objList->blockSignals(blocked);
91 }
92 updateTiles(true);
93}
94
95void ObjView::translateIndex(int index) {
96 unsigned x = index % m_objInfo.width;
97 unsigned y = index / m_objInfo.width;
98 m_ui.tile->selectIndex(x + y * m_objInfo.stride + m_tileOffset + m_boundary);
99}
100
101#ifdef M_CORE_GBA
102void ObjView::updateTilesGBA(bool force) {
103 m_ui.objId->setMaximum(127);
104 const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
105 const GBAObj* obj = &gba->video.oam.obj[m_objId];
106
107 updateObjList(128);
108
109 ObjInfo newInfo;
110 lookupObj(m_objId, &newInfo);
111
112 m_ui.tiles->setTileCount(newInfo.width * newInfo.height);
113 m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
114 m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
115 unsigned tileBase = newInfo.tile;
116 unsigned tile = newInfo.tile;
117 if (GBAObjAttributesAIs256Color(obj->a)) {
118 m_ui.palette->setText("256-color");
119 m_ui.tile->setBoundary(1024, 1, 3);
120 m_ui.tile->setPalette(0);
121 m_boundary = 1024;
122 tileBase *= 2;
123 } else {
124 m_ui.palette->setText(QString::number(newInfo.paletteId));
125 m_ui.tile->setBoundary(2048, 0, 2);
126 m_ui.tile->setPalette(newInfo.paletteId);
127 m_boundary = 2048;
128 }
129 if (newInfo != m_objInfo) {
130 force = true;
131 }
132 m_objInfo = newInfo;
133 m_tileOffset = newInfo.tile;
134 mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
135
136 int i = 0;
137 for (int y = 0; y < newInfo.height; ++y) {
138 for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
139 const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
140 if (data) {
141 m_ui.tiles->setTile(i, data);
142 } else if (force) {
143 m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, newInfo.paletteId));
144 }
145 }
146 tile += newInfo.stride - newInfo.width;
147 tileBase += newInfo.stride - newInfo.width;
148 }
149
150 m_ui.x->setText(QString::number(newInfo.x));
151 m_ui.y->setText(QString::number(newInfo.y));
152 m_ui.w->setText(QString::number(newInfo.width * 8));
153 m_ui.h->setText(QString::number(newInfo.height * 8));
154
155 m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
156 m_ui.priority->setText(QString::number(newInfo.priority));
157 m_ui.flippedH->setChecked(newInfo.hflip);
158 m_ui.flippedV->setChecked(newInfo.vflip);
159 m_ui.enabled->setChecked(newInfo.enabled);
160 m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
161 m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
162
163 if (GBAObjAttributesAIsTransformed(obj->a)) {
164 int mtxId = GBAObjAttributesBGetMatIndex(obj->b);
165 struct GBAOAMMatrix mat;
166 LOAD_16LE(mat.a, 0, &gba->video.oam.mat[mtxId].a);
167 LOAD_16LE(mat.b, 0, &gba->video.oam.mat[mtxId].b);
168 LOAD_16LE(mat.c, 0, &gba->video.oam.mat[mtxId].c);
169 LOAD_16LE(mat.d, 0, &gba->video.oam.mat[mtxId].d);
170 m_ui.transform->setText(QString::number(mtxId));
171 m_ui.xformPA->setText(QString("%0").arg(mat.a / 256., 5, 'f', 2));
172 m_ui.xformPB->setText(QString("%0").arg(mat.b / 256., 5, 'f', 2));
173 m_ui.xformPC->setText(QString("%0").arg(mat.c / 256., 5, 'f', 2));
174 m_ui.xformPD->setText(QString("%0").arg(mat.d / 256., 5, 'f', 2));
175 } else {
176 m_ui.transform->setText(tr("Off"));
177 m_ui.xformPA->setText(tr("---"));
178 m_ui.xformPB->setText(tr("---"));
179 m_ui.xformPC->setText(tr("---"));
180 m_ui.xformPD->setText(tr("---"));
181 }
182
183 switch (GBAObjAttributesAGetMode(obj->a)) {
184 case OBJ_MODE_NORMAL:
185 m_ui.mode->setText(tr("Normal"));
186 break;
187 case OBJ_MODE_SEMITRANSPARENT:
188 m_ui.mode->setText(tr("Trans"));
189 break;
190 case OBJ_MODE_OBJWIN:
191 m_ui.mode->setText(tr("OBJWIN"));
192 break;
193 default:
194 m_ui.mode->setText(tr("Invalid"));
195 break;
196 }
197}
198#endif
199
200#ifdef M_CORE_GB
201void ObjView::updateTilesGB(bool force) {
202 m_ui.objId->setMaximum(39);
203 const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
204 const GBObj* obj = &gb->video.oam.obj[m_objId];
205
206 updateObjList(40);
207
208 ObjInfo newInfo;
209 lookupObj(m_objId, &newInfo);
210
211 mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
212 unsigned tile = newInfo.tile;
213 m_ui.tiles->setTileCount(newInfo.height);
214 m_ui.tile->setBoundary(1024, 0, 0);
215 m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
216 m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
217 m_ui.palette->setText(QString::number(newInfo.paletteId - 8));
218
219 if (newInfo != m_objInfo) {
220 force = true;
221 }
222 m_objInfo = newInfo;
223 m_tileOffset = tile;
224 m_boundary = 1024;
225
226 int i = 0;
227 m_ui.tile->setPalette(newInfo.paletteId);
228 for (int y = 0; y < newInfo.height; ++y, ++i) {
229 unsigned t = tile + i;
230 const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId);
231 if (data) {
232 m_ui.tiles->setTile(i, data);
233 } else if (force) {
234 m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, newInfo.paletteId));
235 }
236 }
237
238 m_ui.x->setText(QString::number(newInfo.x));
239 m_ui.y->setText(QString::number(newInfo.y));
240 m_ui.w->setText(QString::number(8));
241 m_ui.h->setText(QString::number(newInfo.height * 8));
242
243 m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
244 m_ui.priority->setText(QString::number(newInfo.priority));
245 m_ui.flippedH->setChecked(newInfo.hflip);
246 m_ui.flippedV->setChecked(newInfo.vflip);
247 m_ui.enabled->setChecked(newInfo.enabled);
248 m_ui.doubleSize->setChecked(false);
249 m_ui.mosaic->setChecked(false);
250 m_ui.transform->setText(tr("N/A"));
251 m_ui.xformPA->setText(tr("---"));
252 m_ui.xformPB->setText(tr("---"));
253 m_ui.xformPC->setText(tr("---"));
254 m_ui.xformPD->setText(tr("---"));
255 m_ui.mode->setText(tr("N/A"));
256}
257#endif
258
259void ObjView::updateObjList(int maxObj) {
260 for (int i = 0; i < maxObj; ++i) {
261 if (m_objs.size() <= i) {
262 QListWidgetItem* item = new QListWidgetItem;
263 item->setText(QString::number(i));
264 item->setData(Qt::UserRole, i);
265 item->setSizeHint(QSize(64, 96));
266 if (m_objId == i) {
267 item->setSelected(true);
268 }
269 m_objs.append(item);
270 m_ui.objList->addItem(item);
271 }
272 QListWidgetItem* item = m_objs[i];
273 ObjInfo info;
274 lookupObj(i, &info);
275 item->setIcon(QPixmap::fromImage(std::move(compositeObj(info))));
276 }
277}
278
279void ObjView::exportObj() {
280 QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
281 tr("Portable Network Graphics (*.png)"));
282 if (filename.isNull()) {
283 return;
284 }
285 CoreController::Interrupter interrupter(m_controller);
286 QImage obj = compositeObj(m_objInfo);
287 obj.save(filename, "PNG");
288}
289
290void ObjView::copyObj() {
291 CoreController::Interrupter interrupter(m_controller);
292 GBAApp::app()->clipboard()->setImage(compositeObj(m_objInfo));
293}