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.mode->setFont(font);
48
49 connect(m_ui.tiles, &TilePainter::indexPressed, this, &ObjView::translateIndex);
50 connect(m_ui.tiles, &TilePainter::needsRedraw, this, [this]() {
51 updateTiles(true);
52 });
53 connect(m_ui.objId, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &ObjView::selectObj);
54 connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), [this]() {
55 updateTiles(true);
56 });
57 connect(m_ui.exportButton, &QAbstractButton::clicked, this, &ObjView::exportObj);
58 connect(m_ui.copyButton, &QAbstractButton::clicked, this, &ObjView::copyObj);
59
60 connect(m_ui.objList, &QListWidget::currentItemChanged, [this]() {
61 QListWidgetItem* item = m_ui.objList->currentItem();
62 if (item) {
63 selectObj(item->data(Qt::UserRole).toInt());
64 }
65 });
66
67 QAction* exportAction = new QAction(this);
68 exportAction->setShortcut(QKeySequence::Save);
69 connect(exportAction, &QAction::triggered, this, &ObjView::exportObj);
70 addAction(exportAction);
71
72 QAction* copyAction = new QAction(this);
73 copyAction->setShortcut(QKeySequence::Copy);
74 connect(copyAction, &QAction::triggered, this, &ObjView::copyObj);
75 addAction(copyAction);
76}
77
78void ObjView::selectObj(int obj) {
79 m_objId = obj;
80 bool blocked = m_ui.objId->blockSignals(true);
81 m_ui.objId->setValue(m_objId);
82 m_ui.objId->blockSignals(blocked);
83 if (m_objs.size() > obj) {
84 blocked = m_ui.objList->blockSignals(true);
85 m_ui.objList->setCurrentItem(m_objs[obj]);
86 m_ui.objList->blockSignals(blocked);
87 }
88 updateTiles(true);
89}
90
91void ObjView::translateIndex(int index) {
92 unsigned x = index % m_objInfo.width;
93 unsigned y = index / m_objInfo.width;
94 m_ui.tile->selectIndex(x + y * m_objInfo.stride + m_tileOffset + m_boundary);
95}
96
97#ifdef M_CORE_GBA
98void ObjView::updateTilesGBA(bool force) {
99 m_ui.objId->setMaximum(127);
100 const GBA* gba = static_cast<const GBA*>(m_controller->thread()->core->board);
101 const GBAObj* obj = &gba->video.oam.obj[m_objId];
102
103 updateObjList(128);
104
105 ObjInfo newInfo;
106 lookupObj(m_objId, &newInfo);
107
108 m_ui.tiles->setTileCount(newInfo.width * newInfo.height);
109 m_ui.tiles->setMinimumSize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
110 m_ui.tiles->resize(QSize(newInfo.width * 8, newInfo.height * 8) * m_ui.magnification->value());
111 unsigned tileBase = newInfo.tile;
112 unsigned tile = newInfo.tile;
113 if (GBAObjAttributesAIs256Color(obj->a)) {
114 m_ui.palette->setText("256-color");
115 m_ui.tile->setBoundary(1024, 1, 3);
116 m_ui.tile->setPalette(0);
117 m_boundary = 1024;
118 tileBase *= 2;
119 } else {
120 m_ui.palette->setText(QString::number(newInfo.paletteId));
121 m_ui.tile->setBoundary(2048, 0, 2);
122 m_ui.tile->setPalette(newInfo.paletteId);
123 m_boundary = 2048;
124 }
125 if (newInfo != m_objInfo) {
126 force = true;
127 }
128 m_objInfo = newInfo;
129 m_tileOffset = newInfo.tile;
130 mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
131
132 int i = 0;
133 for (int y = 0; y < newInfo.height; ++y) {
134 for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
135 const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
136 if (data) {
137 m_ui.tiles->setTile(i, data);
138 } else if (force) {
139 m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, newInfo.paletteId));
140 }
141 }
142 tile += newInfo.stride - newInfo.width;
143 tileBase += newInfo.stride - newInfo.width;
144 }
145
146 m_ui.x->setText(QString::number(newInfo.x));
147 m_ui.y->setText(QString::number(newInfo.y));
148 m_ui.w->setText(QString::number(newInfo.width * 8));
149 m_ui.h->setText(QString::number(newInfo.height * 8));
150
151 m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
152 m_ui.priority->setText(QString::number(newInfo.priority));
153 m_ui.flippedH->setChecked(newInfo.hflip);
154 m_ui.flippedV->setChecked(newInfo.vflip);
155 m_ui.enabled->setChecked(newInfo.enabled);
156 m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
157 m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
158
159 if (GBAObjAttributesAIsTransformed(obj->a)) {
160 m_ui.transform->setText(QString::number(GBAObjAttributesBGetMatIndex(obj->b)));
161 } else {
162 m_ui.transform->setText(tr("Off"));
163 }
164
165 switch (GBAObjAttributesAGetMode(obj->a)) {
166 case OBJ_MODE_NORMAL:
167 m_ui.mode->setText(tr("Normal"));
168 break;
169 case OBJ_MODE_SEMITRANSPARENT:
170 m_ui.mode->setText(tr("Trans"));
171 break;
172 case OBJ_MODE_OBJWIN:
173 m_ui.mode->setText(tr("OBJWIN"));
174 break;
175 default:
176 m_ui.mode->setText(tr("Invalid"));
177 break;
178 }
179}
180#endif
181
182#ifdef M_CORE_GB
183void ObjView::updateTilesGB(bool force) {
184 m_ui.objId->setMaximum(39);
185 const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
186 const GBObj* obj = &gb->video.oam.obj[m_objId];
187
188 updateObjList(40);
189
190 ObjInfo newInfo;
191 lookupObj(m_objId, &newInfo);
192
193 mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
194 unsigned tile = newInfo.tile;
195 m_ui.tiles->setTileCount(newInfo.height);
196 m_ui.tile->setBoundary(1024, 0, 0);
197 m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
198 m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
199 m_ui.palette->setText(QString::number(newInfo.paletteId - 8));
200
201 if (newInfo != m_objInfo) {
202 force = true;
203 }
204 m_objInfo = newInfo;
205 m_tileOffset = tile;
206 m_boundary = 1024;
207
208 int i = 0;
209 m_ui.tile->setPalette(newInfo.paletteId);
210 for (int y = 0; y < newInfo.height; ++y, ++i) {
211 unsigned t = tile + i;
212 const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId);
213 if (data) {
214 m_ui.tiles->setTile(i, data);
215 } else if (force) {
216 m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, newInfo.paletteId));
217 }
218 }
219
220 m_ui.x->setText(QString::number(newInfo.x));
221 m_ui.y->setText(QString::number(newInfo.y));
222 m_ui.w->setText(QString::number(8));
223 m_ui.h->setText(QString::number(newInfo.height * 8));
224
225 m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
226 m_ui.priority->setText(QString::number(newInfo.priority));
227 m_ui.flippedH->setChecked(newInfo.hflip);
228 m_ui.flippedV->setChecked(newInfo.vflip);
229 m_ui.enabled->setChecked(newInfo.enabled);
230 m_ui.doubleSize->setChecked(false);
231 m_ui.mosaic->setChecked(false);
232 m_ui.transform->setText(tr("N/A"));
233 m_ui.mode->setText(tr("N/A"));
234}
235#endif
236
237void ObjView::updateObjList(int maxObj) {
238 for (int i = 0; i < maxObj; ++i) {
239 if (m_objs.size() <= i) {
240 QListWidgetItem* item = new QListWidgetItem;
241 item->setText(QString::number(i));
242 item->setData(Qt::UserRole, i);
243 item->setSizeHint(QSize(64, 96));
244 if (m_objId == i) {
245 item->setSelected(true);
246 }
247 m_objs.append(item);
248 m_ui.objList->addItem(item);
249 }
250 QListWidgetItem* item = m_objs[i];
251 ObjInfo info;
252 lookupObj(i, &info);
253 item->setIcon(QPixmap::fromImage(std::move(compositeObj(info))));
254 }
255}
256
257void ObjView::exportObj() {
258 QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
259 tr("Portable Network Graphics (*.png)"));
260 if (filename.isNull()) {
261 return;
262 }
263 CoreController::Interrupter interrupter(m_controller);
264 QImage obj = compositeObj(m_objInfo);
265 obj.save(filename, "PNG");
266}
267
268void ObjView::copyObj() {
269 CoreController::Interrupter interrupter(m_controller);
270 GBAApp::app()->clipboard()->setImage(compositeObj(m_objInfo));
271}