all repos — mgba @ 59d2e58bbbd42746d2bc781fe8d8adfe1ff3588f

mGBA Game Boy Advance Emulator

src/platform/qt/FrameView.cpp (view raw)

  1/* Copyright (c) 2013-2019 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 "FrameView.h"
  7
  8#include <QMouseEvent>
  9#include <QPainter>
 10#include <QPalette>
 11
 12#include <array>
 13#include <cmath>
 14
 15#include "CoreController.h"
 16
 17#ifdef M_CORE_GBA
 18#include <mgba/internal/gba/gba.h>
 19#include <mgba/internal/gba/io.h>
 20#include <mgba/internal/gba/memory.h>
 21#include <mgba/internal/gba/video.h>
 22#endif
 23#ifdef M_CORE_GB
 24#include <mgba/internal/gb/gb.h>
 25#include <mgba/internal/gb/memory.h>
 26#endif
 27
 28using namespace QGBA;
 29
 30FrameView::FrameView(std::shared_ptr<CoreController> controller, QWidget* parent)
 31	: AssetView(controller, parent)
 32{
 33	m_ui.setupUi(this);
 34
 35	m_glowTimer.setInterval(33);
 36	connect(&m_glowTimer, &QTimer::timeout, this, [this]() {
 37		++m_glowFrame;
 38		invalidateQueue();
 39	});
 40	m_glowTimer.start();
 41
 42	m_ui.renderedView->installEventFilter(this);
 43	m_ui.compositedView->installEventFilter(this);
 44
 45	connect(m_ui.queue, &QListWidget::itemChanged, this, [this](QListWidgetItem* item) {
 46		Layer& layer = m_queue[item->data(Qt::UserRole).toInt()];
 47		layer.enabled = item->checkState() == Qt::Checked;
 48		if (layer.enabled) {
 49			m_disabled.remove(layer.id);
 50		} else {
 51			m_disabled.insert(layer.id);			
 52		}
 53		invalidateQueue();
 54	});
 55	connect(m_ui.queue, &QListWidget::currentItemChanged, this, [this](QListWidgetItem* item) {
 56		if (item) {
 57			m_active = m_queue[item->data(Qt::UserRole).toInt()].id;
 58		} else {
 59			m_active = {};
 60		}
 61		invalidateQueue();
 62	});
 63	connect(m_ui.magnification, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, [this]() {
 64		invalidateQueue();
 65
 66		QPixmap rendered = m_rendered.scaledToHeight(m_rendered.height() * m_ui.magnification->value());
 67		m_ui.renderedView->setPixmap(rendered);
 68	});
 69}
 70
 71bool FrameView::lookupLayer(const QPointF& coord, Layer*& out) {
 72	for (Layer& layer : m_queue) {
 73		if (!layer.enabled || m_disabled.contains(layer.id)) {
 74			continue;
 75		}
 76		QPointF location = layer.location;
 77		QSizeF layerDims(layer.image.width(), layer.image.height());
 78		QRegion region;
 79		if (layer.repeats) {
 80			if (location.x() + layerDims.width() < 0) {
 81				location.setX(std::fmod(location.x(), layerDims.width()));
 82			}
 83			if (location.y() + layerDims.height() < 0) {
 84				location.setY(std::fmod(location.y(), layerDims.height()));
 85			}
 86
 87			region += layer.mask.translated(location.x(), location.y());
 88			region += layer.mask.translated(location.x() + layerDims.width(), location.y());
 89			region += layer.mask.translated(location.x(), location.y() + layerDims.height());
 90			region += layer.mask.translated(location.x() + layerDims.width(), location.y() + layerDims.height());
 91		} else {
 92			region = layer.mask.translated(location.x(), location.y());
 93		}
 94
 95		if (region.contains(QPoint(coord.x(), coord.y()))) {
 96			out = &layer;
 97			return true;
 98		}
 99	}
100	return false;
101}
102
103void FrameView::selectLayer(const QPointF& coord) {
104	Layer* layer;
105	if (!lookupLayer(coord, layer)) {
106		return;
107	}
108	if (layer->id == m_active) {
109		m_active = {};
110	} else {
111		m_active = layer->id;
112	}
113	m_glowFrame = 0;
114}
115
116void FrameView::disableLayer(const QPointF& coord) {
117	Layer* layer;
118	if (!lookupLayer(coord, layer)) {
119		return;
120	}
121	layer->enabled = false;
122	m_disabled.insert(layer->id);
123}
124
125void FrameView::updateTilesGBA(bool force) {
126	if (m_ui.freeze->checkState() == Qt::Checked) {
127		return;
128	}
129	m_queue.clear();
130	{
131		CoreController::Interrupter interrupter(m_controller);
132		updateRendered();
133
134		uint16_t* io = static_cast<GBA*>(m_controller->thread()->core->board)->memory.io;
135		QRgb backdrop = M_RGB5_TO_RGB8(static_cast<GBA*>(m_controller->thread()->core->board)->video.palette[0]);
136		int mode = GBARegisterDISPCNTGetMode(io[REG_DISPCNT >> 1]);
137
138		std::array<bool, 4> enabled{
139			bool(GBARegisterDISPCNTIsBg0Enable(io[REG_DISPCNT >> 1])),
140			bool(GBARegisterDISPCNTIsBg1Enable(io[REG_DISPCNT >> 1])),
141			bool(GBARegisterDISPCNTIsBg2Enable(io[REG_DISPCNT >> 1])),
142			bool(GBARegisterDISPCNTIsBg3Enable(io[REG_DISPCNT >> 1])),
143		};
144
145		for (int priority = 0; priority < 4; ++priority) {
146			for (int sprite = 0; sprite < 128; ++sprite) {
147				ObjInfo info;
148				lookupObj(sprite, &info);
149
150				if (!info.enabled || info.priority != priority) {
151					continue;
152				}
153
154				QPointF offset(info.x, info.y);
155				QImage obj(compositeObj(info));
156				if (info.hflip || info.vflip) {
157					obj = obj.mirrored(info.hflip, info.vflip);
158				}
159				m_queue.append({
160					{ LayerId::SPRITE, sprite },
161					!m_disabled.contains({ LayerId::SPRITE, sprite }),
162					QPixmap::fromImage(obj),
163					{}, offset, false
164				});
165				if (m_queue.back().image.hasAlpha()) {
166					m_queue.back().mask = QRegion(m_queue.back().image.mask());
167				} else {
168					m_queue.back().mask = QRegion(0, 0, m_queue.back().image.width(), m_queue.back().image.height());
169				}
170			}
171
172			for (int bg = 0; bg < 4; ++bg) {
173				if (!enabled[bg]) {
174					continue;
175				}
176				if (GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + bg]) != priority) {
177					continue;
178				}
179
180				QPointF offset;
181				if (mode == 0) {
182					offset.setX(-(io[(REG_BG0HOFS >> 1) + (bg << 1)] & 0x1FF));
183					offset.setY(-(io[(REG_BG0VOFS >> 1) + (bg << 1)] & 0x1FF));
184				};
185				m_queue.append({
186					{ LayerId::BACKGROUND, bg },
187					!m_disabled.contains({ LayerId::BACKGROUND, bg }),
188					QPixmap::fromImage(compositeMap(bg, m_mapStatus[bg])),
189					{}, offset, true
190				});
191				if (m_queue.back().image.hasAlpha()) {
192					m_queue.back().mask = QRegion(m_queue.back().image.mask());
193				} else {
194					m_queue.back().mask = QRegion(0, 0, m_queue.back().image.width(), m_queue.back().image.height());
195				}
196			}
197		}
198		QImage backdropImage(QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS), QImage::Format_Mono);
199		backdropImage.fill(1);
200		backdropImage.setColorTable({backdrop, backdrop | 0xFF000000 });
201		m_queue.append({
202			{ LayerId::BACKDROP },
203			!m_disabled.contains({ LayerId::BACKDROP }),
204			QPixmap::fromImage(backdropImage),
205			{}, {0, 0}, false
206		});
207	}
208	invalidateQueue(QSize(GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS));
209}
210
211void FrameView::updateTilesGB(bool force) {
212	if (m_ui.freeze->checkState() == Qt::Checked) {
213		return;
214	}
215	m_queue.clear();
216	{
217		CoreController::Interrupter interrupter(m_controller);
218		updateRendered();
219	}
220	invalidateQueue(m_controller->screenDimensions());
221}
222
223void FrameView::invalidateQueue(const QSize& dims) {
224	if (dims.isValid()) {
225		m_dims = dims;
226	}
227	bool blockSignals = m_ui.queue->blockSignals(true);
228	QPixmap composited(m_dims);
229
230	QPainter painter(&composited);
231	QPalette palette;
232	QColor activeColor = palette.color(QPalette::HighlightedText);
233	activeColor.setAlpha(sin(m_glowFrame * M_PI / 60) * 16 + 96);
234
235	QRectF rect(0, 0, m_dims.width(), m_dims.height());
236	painter.setCompositionMode(QPainter::CompositionMode_Source);
237	painter.fillRect(rect, QColor(0, 0, 0, 0));
238
239	painter.setCompositionMode(QPainter::CompositionMode_DestinationOver);
240	for (int i = 0; i < m_queue.count(); ++i) {
241		const Layer& layer = m_queue[i];
242		QListWidgetItem* item;
243		if (i >= m_ui.queue->count()) {
244			item = new QListWidgetItem;
245			m_ui.queue->addItem(item);
246		} else {
247			item = m_ui.queue->item(i);
248		}
249		item->setText(layer.id.readable());
250		item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
251		item->setCheckState(layer.enabled ? Qt::Checked : Qt::Unchecked);
252		item->setData(Qt::UserRole, i);
253		item->setSelected(layer.id == m_active);
254
255		if (!layer.enabled) {
256			continue;
257		}
258
259		QPointF location = layer.location;
260		QSizeF layerDims(layer.image.width(), layer.image.height());
261		QRegion region;
262		if (layer.repeats) {
263			if (location.x() + layerDims.width() < 0) {
264				location.setX(std::fmod(location.x(), layerDims.width()));
265			}
266			if (location.y() + layerDims.height() < 0) {
267				location.setY(std::fmod(location.y(), layerDims.height()));
268			}
269
270			if (layer.id == m_active) {
271				region = layer.mask.translated(location.x(), location.y());
272				region += layer.mask.translated(location.x() + layerDims.width(), location.y());
273				region += layer.mask.translated(location.x(), location.y() + layerDims.height());
274				region += layer.mask.translated(location.x() + layerDims.width(), location.y() + layerDims.height());
275			}
276		} else {
277			QRectF layerRect(location, layerDims);
278			if (!rect.intersects(layerRect)) {
279				continue;
280			}
281			if (layer.id == m_active) {
282				region = layer.mask.translated(location.x(), location.y());
283			}
284		}
285
286		if (layer.id == m_active) {
287			painter.setClipping(true);
288			painter.setClipRegion(region);
289			painter.fillRect(rect, activeColor);
290			painter.setClipping(false);
291		}
292
293		if (layer.repeats) {
294			painter.drawPixmap(location, layer.image);
295			painter.drawPixmap(location + QPointF(layerDims.width(), 0), layer.image);
296			painter.drawPixmap(location + QPointF(0, layerDims.height()), layer.image);
297			painter.drawPixmap(location + QPointF(layerDims.width(), layerDims.height()), layer.image);
298		} else {
299			painter.drawPixmap(location, layer.image);
300		}
301	}
302	painter.end();
303
304	while (m_ui.queue->count() > m_queue.count()) {
305		delete m_ui.queue->takeItem(m_queue.count());
306	}
307	m_ui.queue->blockSignals(blockSignals);
308
309	m_composited = composited.scaled(m_dims * m_ui.magnification->value());
310	m_ui.compositedView->setPixmap(m_composited);
311}
312
313void FrameView::updateRendered() {
314	if (m_ui.freeze->checkState() == Qt::Checked) {
315		return;
316	}
317	m_rendered.convertFromImage(m_controller->getPixels());
318	QPixmap rendered = m_rendered.scaledToHeight(m_rendered.height() * m_ui.magnification->value());
319	m_ui.renderedView->setPixmap(rendered);
320}
321
322bool FrameView::eventFilter(QObject* obj, QEvent* event) {
323	QPointF pos;
324	switch (event->type()) {
325	case QEvent::MouseButtonPress:
326		pos = static_cast<QMouseEvent*>(event)->localPos();
327		pos /= m_ui.magnification->value();
328		selectLayer(pos);
329		return true;
330	case QEvent::MouseButtonDblClick:
331		pos = static_cast<QMouseEvent*>(event)->localPos();
332		pos /= m_ui.magnification->value();
333		disableLayer(pos);
334		return true;
335	}
336	return false;
337}
338
339QString FrameView::LayerId::readable() const {
340	QString typeStr;
341	switch (type) {
342	case NONE:
343		return tr("None");
344	case BACKGROUND:
345		typeStr = tr("Background");
346		break;
347	case WINDOW:
348		typeStr = tr("Window");
349		break;
350	case SPRITE:
351		typeStr = tr("Sprite");
352		break;
353	case BACKDROP:
354		typeStr = tr("Backdrop");
355		break;
356	}
357	if (index < 0) {
358		return typeStr;
359	}
360	return tr("%1 %2").arg(typeStr).arg(index);
361}