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 }
124 if (newInfo != m_objInfo) {
125 force = true;
126 }
127 m_objInfo = newInfo;
128 m_tileOffset = newInfo.tile;
129 mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, newInfo.paletteSet);
130
131 int i = 0;
132 for (int y = 0; y < newInfo.height; ++y) {
133 for (int x = 0; x < newInfo.width; ++x, ++i, ++tile, ++tileBase) {
134 const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[16 * tileBase], tile, newInfo.paletteId);
135 if (data) {
136 m_ui.tiles->setTile(i, data);
137 } else if (force) {
138 m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, tile, newInfo.paletteId));
139 }
140 }
141 tile += newInfo.stride - newInfo.width;
142 tileBase += newInfo.stride - newInfo.width;
143 }
144
145 m_ui.x->setText(QString::number(newInfo.x));
146 m_ui.y->setText(QString::number(newInfo.y));
147 m_ui.w->setText(QString::number(newInfo.width * 8));
148 m_ui.h->setText(QString::number(newInfo.height * 8));
149
150 m_ui.address->setText(tr("0x%0").arg(BASE_OAM + m_objId * sizeof(*obj), 8, 16, QChar('0')));
151 m_ui.priority->setText(QString::number(newInfo.priority));
152 m_ui.flippedH->setChecked(newInfo.hflip);
153 m_ui.flippedV->setChecked(newInfo.vflip);
154 m_ui.enabled->setChecked(newInfo.enabled);
155 m_ui.doubleSize->setChecked(GBAObjAttributesAIsDoubleSize(obj->a) && GBAObjAttributesAIsTransformed(obj->a));
156 m_ui.mosaic->setChecked(GBAObjAttributesAIsMosaic(obj->a));
157
158 if (GBAObjAttributesAIsTransformed(obj->a)) {
159 m_ui.transform->setText(QString::number(GBAObjAttributesBGetMatIndex(obj->b)));
160 } else {
161 m_ui.transform->setText(tr("Off"));
162 }
163
164 switch (GBAObjAttributesAGetMode(obj->a)) {
165 case OBJ_MODE_NORMAL:
166 m_ui.mode->setText(tr("Normal"));
167 break;
168 case OBJ_MODE_SEMITRANSPARENT:
169 m_ui.mode->setText(tr("Trans"));
170 break;
171 case OBJ_MODE_OBJWIN:
172 m_ui.mode->setText(tr("OBJWIN"));
173 break;
174 default:
175 m_ui.mode->setText(tr("Invalid"));
176 break;
177 }
178}
179#endif
180
181#ifdef M_CORE_GB
182void ObjView::updateTilesGB(bool force) {
183 m_ui.objId->setMaximum(39);
184 const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
185 const GBObj* obj = &gb->video.oam.obj[m_objId];
186
187 updateObjList(40);
188
189 ObjInfo newInfo;
190 lookupObj(m_objId, &newInfo);
191
192 mTileCache* tileCache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
193 unsigned tile = newInfo.tile;
194 m_ui.tiles->setTileCount(newInfo.height);
195 m_ui.tile->setBoundary(1024, 0, 0);
196 m_ui.tiles->setMinimumSize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
197 m_ui.tiles->resize(QSize(8, newInfo.height * 8) * m_ui.magnification->value());
198 m_ui.palette->setText(QString::number(newInfo.paletteId - 8));
199
200 if (newInfo != m_objInfo) {
201 force = true;
202 }
203 m_objInfo = newInfo;
204 m_tileOffset = tile;
205 m_boundary = 1024;
206
207 int i = 0;
208 m_ui.tile->setPalette(newInfo.paletteId);
209 for (int y = 0; y < newInfo.height; ++y, ++i) {
210 unsigned t = tile + i;
211 const color_t* data = mTileCacheGetTileIfDirty(tileCache, &m_tileStatus[8 * t], t, newInfo.paletteId);
212 if (data) {
213 m_ui.tiles->setTile(i, data);
214 } else if (force) {
215 m_ui.tiles->setTile(i, mTileCacheGetTile(tileCache, t, newInfo.paletteId));
216 }
217 }
218
219 m_ui.x->setText(QString::number(newInfo.x));
220 m_ui.y->setText(QString::number(newInfo.y));
221 m_ui.w->setText(QString::number(8));
222 m_ui.h->setText(QString::number(newInfo.height * 8));
223
224 m_ui.address->setText(tr("0x%0").arg(GB_BASE_OAM + m_objId * sizeof(*obj), 4, 16, QChar('0')));
225 m_ui.priority->setText(QString::number(newInfo.priority));
226 m_ui.flippedH->setChecked(newInfo.hflip);
227 m_ui.flippedV->setChecked(newInfo.vflip);
228 m_ui.enabled->setChecked(newInfo.enabled);
229 m_ui.doubleSize->setChecked(false);
230 m_ui.mosaic->setChecked(false);
231 m_ui.transform->setText(tr("N/A"));
232 m_ui.mode->setText(tr("N/A"));
233}
234#endif
235
236void ObjView::updateObjList(int maxObj) {
237 for (int i = 0; i < maxObj; ++i) {
238 if (m_objs.size() <= i) {
239 QListWidgetItem* item = new QListWidgetItem;
240 item->setText(QString::number(i));
241 item->setData(Qt::UserRole, i);
242 item->setSizeHint(QSize(64, 96));
243 if (m_objId == i) {
244 item->setSelected(true);
245 }
246 m_objs.append(item);
247 m_ui.objList->addItem(item);
248 }
249 QListWidgetItem* item = m_objs[i];
250 ObjInfo info;
251 lookupObj(i, &info);
252 item->setIcon(QPixmap::fromImage(std::move(compositeObj(info))));
253 }
254}
255
256void ObjView::exportObj() {
257 QString filename = GBAApp::app()->getSaveFileName(this, tr("Export sprite"),
258 tr("Portable Network Graphics (*.png)"));
259 if (filename.isNull()) {
260 return;
261 }
262 CoreController::Interrupter interrupter(m_controller);
263 QImage obj = compositeObj(m_objInfo);
264 obj.save(filename, "PNG");
265}
266
267void ObjView::copyObj() {
268 CoreController::Interrupter interrupter(m_controller);
269 GBAApp::app()->clipboard()->setImage(compositeObj(m_objInfo));
270}