commit
3e04cb6e6b
|
@ -292,6 +292,25 @@ bool Core::IsStarted() const
|
||||||
return m_started;
|
return m_started;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CoreInfo Core::GetCoreInfo() const
|
||||||
|
{
|
||||||
|
CoreInfo info{};
|
||||||
|
info.device_number = m_device_number;
|
||||||
|
info.width = GBA_VIDEO_HORIZONTAL_PIXELS;
|
||||||
|
info.height = GBA_VIDEO_VERTICAL_PIXELS;
|
||||||
|
|
||||||
|
if (!IsStarted())
|
||||||
|
return info;
|
||||||
|
|
||||||
|
info.is_gba = m_core->platform(m_core) == mPlatform::mPLATFORM_GBA;
|
||||||
|
info.has_rom = !m_rom_path.empty();
|
||||||
|
info.has_ereader =
|
||||||
|
info.is_gba && static_cast<::GBA*>(m_core->board)->memory.hw.devices & HW_EREADER;
|
||||||
|
m_core->desiredVideoDimensions(m_core, &info.width, &info.height);
|
||||||
|
info.game_title = m_game_title;
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
void Core::SetHost(std::weak_ptr<GBAHostInterface> host)
|
void Core::SetHost(std::weak_ptr<GBAHostInterface> host)
|
||||||
{
|
{
|
||||||
m_host = std::move(host);
|
m_host = std::move(host);
|
||||||
|
@ -305,7 +324,7 @@ void Core::SetForceDisconnect(bool force_disconnect)
|
||||||
void Core::EReaderQueueCard(std::string_view card_path)
|
void Core::EReaderQueueCard(std::string_view card_path)
|
||||||
{
|
{
|
||||||
Flush();
|
Flush();
|
||||||
if (!IsStarted() || m_core->platform(m_core) != mPlatform::mPLATFORM_GBA)
|
if (!GetCoreInfo().has_ereader)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
File::IOFile file(std::string(card_path), "rb");
|
File::IOFile file(std::string(card_path), "rb");
|
||||||
|
@ -438,28 +457,6 @@ void Core::SetupEvent()
|
||||||
m_event.priority = 0x80;
|
m_event.priority = 0x80;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Core::GetDeviceNumber() const
|
|
||||||
{
|
|
||||||
return m_device_number;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::GetVideoDimensions(u32* width, u32* height) const
|
|
||||||
{
|
|
||||||
if (!IsStarted())
|
|
||||||
{
|
|
||||||
*width = GBA_VIDEO_HORIZONTAL_PIXELS;
|
|
||||||
*height = GBA_VIDEO_VERTICAL_PIXELS;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_core->desiredVideoDimensions(m_core, width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Core::GetGameTitle() const
|
|
||||||
{
|
|
||||||
return m_game_title;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Core::SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys)
|
void Core::SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys)
|
||||||
{
|
{
|
||||||
if (!IsStarted())
|
if (!IsStarted())
|
||||||
|
|
|
@ -36,6 +36,17 @@ struct AVStream : mAVStream
|
||||||
Core* core;
|
Core* core;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CoreInfo
|
||||||
|
{
|
||||||
|
int device_number;
|
||||||
|
bool is_gba;
|
||||||
|
bool has_rom;
|
||||||
|
bool has_ereader;
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
std::string game_title;
|
||||||
|
};
|
||||||
|
|
||||||
class Core final
|
class Core final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -46,15 +57,12 @@ public:
|
||||||
void Stop();
|
void Stop();
|
||||||
void Reset();
|
void Reset();
|
||||||
bool IsStarted() const;
|
bool IsStarted() const;
|
||||||
|
CoreInfo GetCoreInfo() const;
|
||||||
|
|
||||||
void SetHost(std::weak_ptr<GBAHostInterface> host);
|
void SetHost(std::weak_ptr<GBAHostInterface> host);
|
||||||
void SetForceDisconnect(bool force_disconnect);
|
void SetForceDisconnect(bool force_disconnect);
|
||||||
void EReaderQueueCard(std::string_view card_path);
|
void EReaderQueueCard(std::string_view card_path);
|
||||||
|
|
||||||
int GetDeviceNumber() const;
|
|
||||||
void GetVideoDimensions(u32* width, u32* height) const;
|
|
||||||
std::string GetGameTitle() const;
|
|
||||||
|
|
||||||
void SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys);
|
void SendJoybusCommand(u64 gc_ticks, int transfer_time, u8* buffer, u16 keys);
|
||||||
std::vector<u8> GetJoybusResponse();
|
std::vector<u8> GetJoybusResponse();
|
||||||
|
|
||||||
|
|
|
@ -15,16 +15,9 @@ GBAHost::GBAHost(std::weak_ptr<HW::GBA::Core> core)
|
||||||
m_widget_controller->moveToThread(qApp->thread());
|
m_widget_controller->moveToThread(qApp->thread());
|
||||||
m_core = std::move(core);
|
m_core = std::move(core);
|
||||||
auto core_ptr = m_core.lock();
|
auto core_ptr = m_core.lock();
|
||||||
|
HW::GBA::CoreInfo info = core_ptr->GetCoreInfo();
|
||||||
int device_number = core_ptr->GetDeviceNumber();
|
|
||||||
std::string game_title = core_ptr->GetGameTitle();
|
|
||||||
u32 width, height;
|
|
||||||
core_ptr->GetVideoDimensions(&width, &height);
|
|
||||||
|
|
||||||
QueueOnObject(m_widget_controller, [widget_controller = m_widget_controller, core = m_core,
|
QueueOnObject(m_widget_controller, [widget_controller = m_widget_controller, core = m_core,
|
||||||
device_number, game_title, width, height] {
|
info] { widget_controller->Create(core, info); });
|
||||||
widget_controller->Create(core, device_number, game_title, width, height);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GBAHost::~GBAHost()
|
GBAHost::~GBAHost()
|
||||||
|
@ -37,15 +30,10 @@ void GBAHost::GameChanged()
|
||||||
auto core_ptr = m_core.lock();
|
auto core_ptr = m_core.lock();
|
||||||
if (!core_ptr || !core_ptr->IsStarted())
|
if (!core_ptr || !core_ptr->IsStarted())
|
||||||
return;
|
return;
|
||||||
|
HW::GBA::CoreInfo info = core_ptr->GetCoreInfo();
|
||||||
std::string game_title = core_ptr->GetGameTitle();
|
QueueOnObject(m_widget_controller, [widget_controller = m_widget_controller, info] {
|
||||||
u32 width, height;
|
widget_controller->GameChanged(info);
|
||||||
core_ptr->GetVideoDimensions(&width, &height);
|
});
|
||||||
|
|
||||||
QueueOnObject(m_widget_controller,
|
|
||||||
[widget_controller = m_widget_controller, game_title, width, height] {
|
|
||||||
widget_controller->GameChanged(game_title, width, height);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAHost::FrameEnded(const std::vector<u32>& video_buffer)
|
void GBAHost::FrameEnded(const std::vector<u32>& video_buffer)
|
||||||
|
|
|
@ -12,16 +12,15 @@
|
||||||
#include <QDropEvent>
|
#include <QDropEvent>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
#include <QImage>
|
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMimeData>
|
#include <QMimeData>
|
||||||
|
#include <QMouseEvent>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
#include "AudioCommon/AudioCommon.h"
|
#include "AudioCommon/AudioCommon.h"
|
||||||
#include "Core/Config/MainSettings.h"
|
#include "Core/Config/MainSettings.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/CoreTiming.h"
|
#include "Core/CoreTiming.h"
|
||||||
#include "Core/HW/GBACore.h"
|
|
||||||
#include "Core/HW/GBAPad.h"
|
#include "Core/HW/GBAPad.h"
|
||||||
#include "Core/HW/SI/SI.h"
|
#include "Core/HW/SI/SI.h"
|
||||||
#include "Core/HW/SI/SI_Device.h"
|
#include "Core/HW/SI/SI_Device.h"
|
||||||
|
@ -38,7 +37,7 @@ static void RestartCore(const std::weak_ptr<HW::GBA::Core>& core, std::string_vi
|
||||||
[core, rom_path = std::string(rom_path)] {
|
[core, rom_path = std::string(rom_path)] {
|
||||||
if (auto core_ptr = core.lock())
|
if (auto core_ptr = core.lock())
|
||||||
{
|
{
|
||||||
auto& info = Config::MAIN_GBA_ROM_PATHS[core_ptr->GetDeviceNumber()];
|
auto& info = Config::MAIN_GBA_ROM_PATHS[core_ptr->GetCoreInfo().device_number];
|
||||||
core_ptr->Stop();
|
core_ptr->Stop();
|
||||||
Config::SetCurrent(info, rom_path);
|
Config::SetCurrent(info, rom_path);
|
||||||
if (core_ptr->Start(CoreTiming::GetTicks()))
|
if (core_ptr->Start(CoreTiming::GetTicks()))
|
||||||
|
@ -60,56 +59,62 @@ static void QueueEReaderCard(const std::weak_ptr<HW::GBA::Core>& core, std::stri
|
||||||
false);
|
false);
|
||||||
}
|
}
|
||||||
|
|
||||||
GBAWidget::GBAWidget(std::weak_ptr<HW::GBA::Core> core, int device_number,
|
GBAWidget::GBAWidget(std::weak_ptr<HW::GBA::Core> core, const HW::GBA::CoreInfo& info,
|
||||||
std::string_view game_title, int width, int height, QWidget* parent,
|
const std::optional<NetPlay::PadDetails>& netplay_pad)
|
||||||
Qt::WindowFlags flags)
|
: QWidget(nullptr, LoadWindowFlags(netplay_pad ? netplay_pad->local_pad : info.device_number)),
|
||||||
: QWidget(parent, flags), m_core(std::move(core)), m_device_number(device_number),
|
m_core(std::move(core)), m_core_info(info), m_local_pad(info.device_number),
|
||||||
m_local_pad(device_number), m_game_title(game_title), m_width(width), m_height(height),
|
m_is_local_pad(true), m_volume(0), m_muted(false), m_force_disconnect(false), m_moving(false),
|
||||||
m_is_local_pad(true), m_volume(0), m_muted(false), m_force_disconnect(false)
|
m_interframe_blending(false)
|
||||||
{
|
{
|
||||||
bool visible = true;
|
bool visible = true;
|
||||||
if (NetPlay::IsNetPlayRunning())
|
if (netplay_pad)
|
||||||
{
|
{
|
||||||
NetPlay::PadDetails details = NetPlay::GetPadDetails(m_device_number);
|
m_netplayer_name = netplay_pad->player_name;
|
||||||
if (details.local_pad < 4)
|
m_is_local_pad = netplay_pad->is_local;
|
||||||
{
|
m_local_pad = netplay_pad->local_pad;
|
||||||
m_netplayer_name = details.player_name;
|
visible = !netplay_pad->hide_gba;
|
||||||
m_is_local_pad = details.is_local;
|
|
||||||
m_local_pad = details.local_pad;
|
|
||||||
visible = !details.hide_gba;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setWindowIcon(Resources::GetAppIcon());
|
setWindowIcon(Resources::GetAppIcon());
|
||||||
setAcceptDrops(true);
|
setAcceptDrops(true);
|
||||||
resize(m_width, m_height);
|
resize(m_core_info.width, m_core_info.height);
|
||||||
setVisible(visible);
|
setVisible(visible);
|
||||||
|
|
||||||
SetVolume(100);
|
SetVolume(100);
|
||||||
if (!visible)
|
if (!visible)
|
||||||
ToggleMute();
|
ToggleMute();
|
||||||
|
|
||||||
LoadGeometry();
|
LoadSettings();
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
GBAWidget::~GBAWidget()
|
GBAWidget::~GBAWidget()
|
||||||
{
|
{
|
||||||
SaveGeometry();
|
SaveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAWidget::GameChanged(std::string_view game_title, int width, int height)
|
void GBAWidget::GameChanged(const HW::GBA::CoreInfo& info)
|
||||||
{
|
{
|
||||||
m_game_title = game_title;
|
m_core_info = info;
|
||||||
m_width = width;
|
m_previous_frame = QImage();
|
||||||
m_height = height;
|
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAWidget::SetVideoBuffer(std::vector<u32> video_buffer)
|
void GBAWidget::SetVideoBuffer(std::vector<u32> video_buffer)
|
||||||
{
|
{
|
||||||
m_video_buffer = std::move(video_buffer);
|
m_previous_frame = std::move(m_last_frame);
|
||||||
|
if (video_buffer.size() == static_cast<size_t>(m_core_info.width * m_core_info.height))
|
||||||
|
{
|
||||||
|
m_last_frame = QImage(reinterpret_cast<const uchar*>(video_buffer.data()), m_core_info.width,
|
||||||
|
m_core_info.height, QImage::Format_ARGB32)
|
||||||
|
.convertToFormat(QImage::Format_RGB32)
|
||||||
|
.rgbSwapped();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_last_frame = QImage();
|
||||||
|
}
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,7 +175,7 @@ void GBAWidget::LoadROM()
|
||||||
|
|
||||||
void GBAWidget::UnloadROM()
|
void GBAWidget::UnloadROM()
|
||||||
{
|
{
|
||||||
if (!CanControlCore() || m_game_title.empty())
|
if (!CanControlCore() || !m_core_info.has_rom)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
RestartCore(m_core);
|
RestartCore(m_core);
|
||||||
|
@ -224,17 +229,57 @@ void GBAWidget::DoState(bool export_state)
|
||||||
|
|
||||||
void GBAWidget::Resize(int scale)
|
void GBAWidget::Resize(int scale)
|
||||||
{
|
{
|
||||||
resize(m_width * scale, m_height * scale);
|
resize(m_core_info.width * scale, m_core_info.height * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAWidget::IsBorderless() const
|
||||||
|
{
|
||||||
|
return windowFlags().testFlag(Qt::FramelessWindowHint) ||
|
||||||
|
windowState().testFlag(Qt::WindowFullScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAWidget::SetBorderless(bool enable)
|
||||||
|
{
|
||||||
|
if (windowState().testFlag(Qt::WindowFullScreen))
|
||||||
|
{
|
||||||
|
if (!enable)
|
||||||
|
setWindowState((windowState() ^ Qt::WindowFullScreen) | Qt::WindowMaximized);
|
||||||
|
}
|
||||||
|
else if (windowState().testFlag(Qt::WindowMaximized))
|
||||||
|
{
|
||||||
|
if (enable)
|
||||||
|
setWindowState((windowState() ^ Qt::WindowMaximized) | Qt::WindowFullScreen);
|
||||||
|
}
|
||||||
|
else if (windowFlags().testFlag(Qt::FramelessWindowHint) != enable)
|
||||||
|
{
|
||||||
|
QRect saved_geometry = geometry();
|
||||||
|
setWindowFlag(Qt::FramelessWindowHint, enable);
|
||||||
|
setGeometry(saved_geometry);
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GBAWidget::IsAlwaysOnTop() const
|
||||||
|
{
|
||||||
|
return windowFlags().testFlag(Qt::WindowStaysOnTopHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAWidget::SetAlwaysOnTop(bool enable)
|
||||||
|
{
|
||||||
|
if (windowFlags().testFlag(Qt::WindowStaysOnTopHint) == enable)
|
||||||
|
return;
|
||||||
|
setWindowFlag(Qt::WindowStaysOnTopHint, enable);
|
||||||
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAWidget::UpdateTitle()
|
void GBAWidget::UpdateTitle()
|
||||||
{
|
{
|
||||||
std::string title = fmt::format("GBA{}", m_device_number + 1);
|
std::string title = fmt::format("GBA{}", m_core_info.device_number + 1);
|
||||||
if (!m_netplayer_name.empty())
|
if (!m_netplayer_name.empty())
|
||||||
title += " " + m_netplayer_name;
|
title += " " + m_netplayer_name;
|
||||||
|
|
||||||
if (!m_game_title.empty())
|
if (!m_core_info.game_title.empty())
|
||||||
title += " | " + m_game_title;
|
title += " | " + m_core_info.game_title;
|
||||||
|
|
||||||
if (m_muted)
|
if (m_muted)
|
||||||
title += " | Muted";
|
title += " | Muted";
|
||||||
|
@ -247,23 +292,37 @@ void GBAWidget::UpdateTitle()
|
||||||
void GBAWidget::UpdateVolume()
|
void GBAWidget::UpdateVolume()
|
||||||
{
|
{
|
||||||
int volume = m_muted ? 0 : m_volume * 256 / 100;
|
int volume = m_muted ? 0 : m_volume * 256 / 100;
|
||||||
g_sound_stream->GetMixer()->SetGBAVolume(m_device_number, volume, volume);
|
g_sound_stream->GetMixer()->SetGBAVolume(m_core_info.device_number, volume, volume);
|
||||||
UpdateTitle();
|
UpdateTitle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAWidget::LoadGeometry()
|
Qt::WindowFlags GBAWidget::LoadWindowFlags(int device_number)
|
||||||
{
|
{
|
||||||
const QSettings& settings = Settings::GetQSettings();
|
const QSettings& settings = Settings::GetQSettings();
|
||||||
const QString key = QStringLiteral("gbawidget/geometry%1").arg(m_local_pad + 1);
|
const QString key = QStringLiteral("gbawidget/flags%1").arg(device_number + 1);
|
||||||
if (settings.contains(key))
|
return settings.contains(key) ? Qt::WindowFlags{settings.value(key).toInt()} : Qt::WindowFlags{};
|
||||||
restoreGeometry(settings.value(key).toByteArray());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAWidget::SaveGeometry()
|
void GBAWidget::LoadSettings()
|
||||||
|
{
|
||||||
|
const QSettings& settings = Settings::GetQSettings();
|
||||||
|
QString key = QStringLiteral("gbawidget/geometry%1").arg(m_local_pad + 1);
|
||||||
|
if (settings.contains(key))
|
||||||
|
restoreGeometry(settings.value(key).toByteArray());
|
||||||
|
|
||||||
|
key = QStringLiteral("gbawidget/interframeblending%1").arg(m_local_pad + 1);
|
||||||
|
if (settings.contains(key))
|
||||||
|
m_interframe_blending = settings.value(key).toBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAWidget::SaveSettings()
|
||||||
{
|
{
|
||||||
QSettings& settings = Settings::GetQSettings();
|
QSettings& settings = Settings::GetQSettings();
|
||||||
const QString key = QStringLiteral("gbawidget/geometry%1").arg(m_local_pad + 1);
|
settings.setValue(QStringLiteral("gbawidget/flags%1").arg(m_local_pad + 1),
|
||||||
settings.setValue(key, saveGeometry());
|
static_cast<int>(windowFlags()));
|
||||||
|
settings.setValue(QStringLiteral("gbawidget/geometry%1").arg(m_local_pad + 1), saveGeometry());
|
||||||
|
settings.setValue(QStringLiteral("gbawidget/interframeblending%1").arg(m_local_pad + 1),
|
||||||
|
m_interframe_blending);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GBAWidget::CanControlCore()
|
bool GBAWidget::CanControlCore()
|
||||||
|
@ -298,24 +357,32 @@ void GBAWidget::contextMenuEvent(QContextMenuEvent* event)
|
||||||
connect(load_action, &QAction::triggered, this, &GBAWidget::LoadROM);
|
connect(load_action, &QAction::triggered, this, &GBAWidget::LoadROM);
|
||||||
|
|
||||||
auto* unload_action = new QAction(tr("&Unload ROM"), menu);
|
auto* unload_action = new QAction(tr("&Unload ROM"), menu);
|
||||||
unload_action->setEnabled(CanControlCore() && !m_game_title.empty());
|
unload_action->setEnabled(CanControlCore() && m_core_info.has_rom);
|
||||||
connect(unload_action, &QAction::triggered, this, &GBAWidget::UnloadROM);
|
connect(unload_action, &QAction::triggered, this, &GBAWidget::UnloadROM);
|
||||||
|
|
||||||
auto* card_action = new QAction(tr("&Scan e-Reader Card(s)"), menu);
|
auto* card_action = new QAction(tr("&Scan e-Reader Card(s)"), menu);
|
||||||
card_action->setEnabled(CanControlCore() && !m_game_title.empty());
|
card_action->setEnabled(CanControlCore() && m_core_info.has_ereader);
|
||||||
connect(card_action, &QAction::triggered, this, &GBAWidget::PromptForEReaderCards);
|
connect(card_action, &QAction::triggered, this, &GBAWidget::PromptForEReaderCards);
|
||||||
|
|
||||||
auto* reset_action = new QAction(tr("&Reset"), menu);
|
auto* reset_action = new QAction(tr("&Reset"), menu);
|
||||||
reset_action->setEnabled(CanResetCore());
|
reset_action->setEnabled(CanResetCore());
|
||||||
connect(reset_action, &QAction::triggered, this, &GBAWidget::ResetCore);
|
connect(reset_action, &QAction::triggered, this, &GBAWidget::ResetCore);
|
||||||
|
|
||||||
|
auto* state_menu = new QMenu(tr("Save State"), menu);
|
||||||
|
auto* import_action = new QAction(tr("&Import State"), state_menu);
|
||||||
|
import_action->setEnabled(CanControlCore());
|
||||||
|
connect(import_action, &QAction::triggered, this, [this] { DoState(false); });
|
||||||
|
auto* export_state = new QAction(tr("&Export State"), state_menu);
|
||||||
|
connect(export_state, &QAction::triggered, this, [this] { DoState(true); });
|
||||||
|
|
||||||
auto* mute_action = new QAction(tr("&Mute"), menu);
|
auto* mute_action = new QAction(tr("&Mute"), menu);
|
||||||
mute_action->setCheckable(true);
|
mute_action->setCheckable(true);
|
||||||
mute_action->setChecked(m_muted);
|
mute_action->setChecked(m_muted);
|
||||||
connect(mute_action, &QAction::triggered, this, &GBAWidget::ToggleMute);
|
connect(mute_action, &QAction::triggered, this, &GBAWidget::ToggleMute);
|
||||||
|
|
||||||
auto* size_menu = new QMenu(tr("Window Size"), menu);
|
auto* options_menu = new QMenu(tr("Options"), menu);
|
||||||
|
|
||||||
|
auto* size_menu = new QMenu(tr("Window Size"), options_menu);
|
||||||
auto* x1_action = new QAction(tr("&1x"), size_menu);
|
auto* x1_action = new QAction(tr("&1x"), size_menu);
|
||||||
connect(x1_action, &QAction::triggered, this, [this] { Resize(1); });
|
connect(x1_action, &QAction::triggered, this, [this] { Resize(1); });
|
||||||
auto* x2_action = new QAction(tr("&2x"), size_menu);
|
auto* x2_action = new QAction(tr("&2x"), size_menu);
|
||||||
|
@ -325,22 +392,21 @@ void GBAWidget::contextMenuEvent(QContextMenuEvent* event)
|
||||||
auto* x4_action = new QAction(tr("&4x"), size_menu);
|
auto* x4_action = new QAction(tr("&4x"), size_menu);
|
||||||
connect(x4_action, &QAction::triggered, this, [this] { Resize(4); });
|
connect(x4_action, &QAction::triggered, this, [this] { Resize(4); });
|
||||||
|
|
||||||
size_menu->addAction(x1_action);
|
auto* borderless_action = new QAction(tr("&Borderless Window"), options_menu);
|
||||||
size_menu->addAction(x2_action);
|
borderless_action->setCheckable(true);
|
||||||
size_menu->addAction(x3_action);
|
borderless_action->setChecked(IsBorderless());
|
||||||
size_menu->addAction(x4_action);
|
connect(borderless_action, &QAction::triggered, this, [this] { SetBorderless(!IsBorderless()); });
|
||||||
|
|
||||||
auto* state_menu = new QMenu(tr("Save State"), menu);
|
auto* topmost_action = new QAction(tr("Always on &Top"), options_menu);
|
||||||
|
topmost_action->setCheckable(true);
|
||||||
|
topmost_action->setChecked(IsAlwaysOnTop());
|
||||||
|
connect(topmost_action, &QAction::triggered, this, [this] { SetAlwaysOnTop(!IsAlwaysOnTop()); });
|
||||||
|
|
||||||
auto* import_action = new QAction(tr("&Import State"), state_menu);
|
auto* blending_action = new QAction(tr("&Interframe Blending"), options_menu);
|
||||||
import_action->setEnabled(CanControlCore());
|
blending_action->setCheckable(true);
|
||||||
connect(import_action, &QAction::triggered, this, [this] { DoState(false); });
|
blending_action->setChecked(m_interframe_blending);
|
||||||
|
connect(blending_action, &QAction::triggered, this,
|
||||||
auto* export_state = new QAction(tr("&Export State"), state_menu);
|
[this] { m_interframe_blending = !m_interframe_blending; });
|
||||||
connect(export_state, &QAction::triggered, this, [this] { DoState(true); });
|
|
||||||
|
|
||||||
state_menu->addAction(import_action);
|
|
||||||
state_menu->addAction(export_state);
|
|
||||||
|
|
||||||
menu->addAction(disconnect_action);
|
menu->addAction(disconnect_action);
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
|
@ -353,44 +419,85 @@ void GBAWidget::contextMenuEvent(QContextMenuEvent* event)
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
menu->addAction(mute_action);
|
menu->addAction(mute_action);
|
||||||
menu->addSeparator();
|
menu->addSeparator();
|
||||||
menu->addMenu(size_menu);
|
menu->addMenu(options_menu);
|
||||||
|
|
||||||
|
state_menu->addAction(import_action);
|
||||||
|
state_menu->addAction(export_state);
|
||||||
|
|
||||||
|
options_menu->addMenu(size_menu);
|
||||||
|
options_menu->addSeparator();
|
||||||
|
options_menu->addAction(borderless_action);
|
||||||
|
options_menu->addAction(topmost_action);
|
||||||
|
options_menu->addAction(blending_action);
|
||||||
|
|
||||||
|
size_menu->addAction(x1_action);
|
||||||
|
size_menu->addAction(x2_action);
|
||||||
|
size_menu->addAction(x3_action);
|
||||||
|
size_menu->addAction(x4_action);
|
||||||
|
|
||||||
menu->move(event->globalPos());
|
menu->move(event->globalPos());
|
||||||
menu->show();
|
menu->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GBAWidget::mouseDoubleClickEvent(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
SetBorderless(!IsBorderless());
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAWidget::mousePressEvent(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
if (event->button() != Qt::MouseButton::LeftButton ||
|
||||||
|
!windowFlags().testFlag(Qt::FramelessWindowHint))
|
||||||
|
return;
|
||||||
|
m_moving = true;
|
||||||
|
m_move_pos = event->pos();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAWidget::mouseReleaseEvent(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
if (event->button() != Qt::MouseButton::LeftButton)
|
||||||
|
return;
|
||||||
|
m_moving = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GBAWidget::mouseMoveEvent(QMouseEvent* event)
|
||||||
|
{
|
||||||
|
if (!m_moving)
|
||||||
|
return;
|
||||||
|
move(event->globalPos() - m_move_pos - (geometry().topLeft() - pos()));
|
||||||
|
}
|
||||||
|
|
||||||
void GBAWidget::paintEvent(QPaintEvent* event)
|
void GBAWidget::paintEvent(QPaintEvent* event)
|
||||||
{
|
{
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
painter.fillRect(QRect(QPoint(), size()), Qt::black);
|
painter.fillRect(QRect(QPoint(), size()), Qt::black);
|
||||||
|
|
||||||
if (m_video_buffer.size() == static_cast<size_t>(m_width * m_height))
|
const QRect src_rect(0, 0, m_core_info.width, m_core_info.height);
|
||||||
|
QRect target_rect{};
|
||||||
|
if (size() == QSize(m_core_info.width, m_core_info.height))
|
||||||
{
|
{
|
||||||
QImage image(reinterpret_cast<const uchar*>(m_video_buffer.data()), m_width, m_height,
|
target_rect = QRect(0, 0, m_core_info.width, m_core_info.height);
|
||||||
QImage::Format_ARGB32);
|
}
|
||||||
image = image.convertToFormat(QImage::Format_RGB32);
|
else if (static_cast<float>(m_core_info.width) / m_core_info.height >
|
||||||
image = image.rgbSwapped();
|
static_cast<float>(width()) / height())
|
||||||
|
{
|
||||||
|
int new_height = width() * m_core_info.height / m_core_info.width;
|
||||||
|
target_rect = QRect(0, (height() - new_height) / 2, width(), new_height);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int new_width = height() * m_core_info.width / m_core_info.height;
|
||||||
|
target_rect = QRect((width() - new_width) / 2, 0, new_width, height());
|
||||||
|
}
|
||||||
|
|
||||||
QSize widget_size = size();
|
if (m_interframe_blending && m_previous_frame.size() == src_rect.size())
|
||||||
if (widget_size == QSize(m_width, m_height))
|
{
|
||||||
{
|
painter.drawImage(target_rect, m_previous_frame, src_rect);
|
||||||
painter.drawImage(QPoint(), image, QRect(0, 0, m_width, m_height));
|
painter.setOpacity(0.5);
|
||||||
}
|
}
|
||||||
else if (static_cast<float>(m_width) / m_height >
|
if (m_last_frame.size() == src_rect.size())
|
||||||
static_cast<float>(widget_size.width()) / widget_size.height())
|
{
|
||||||
{
|
painter.drawImage(target_rect, m_last_frame, src_rect);
|
||||||
int new_height = widget_size.width() * m_height / m_width;
|
|
||||||
painter.drawImage(
|
|
||||||
QRect(0, (widget_size.height() - new_height) / 2, widget_size.width(), new_height), image,
|
|
||||||
QRect(0, 0, m_width, m_height));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int new_width = widget_size.height() * m_width / m_height;
|
|
||||||
painter.drawImage(
|
|
||||||
QRect((widget_size.width() - new_width) / 2, 0, new_width, widget_size.height()), image,
|
|
||||||
QRect(0, 0, m_width, m_height));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,15 +538,21 @@ GBAWidgetController::~GBAWidgetController()
|
||||||
m_widget->deleteLater();
|
m_widget->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAWidgetController::Create(std::weak_ptr<HW::GBA::Core> core, int device_number,
|
void GBAWidgetController::Create(std::weak_ptr<HW::GBA::Core> core, const HW::GBA::CoreInfo& info)
|
||||||
std::string_view game_title, int width, int height)
|
|
||||||
{
|
{
|
||||||
m_widget = new GBAWidget(std::move(core), device_number, game_title, width, height);
|
std::optional<NetPlay::PadDetails> netplay_pad;
|
||||||
|
if (NetPlay::IsNetPlayRunning())
|
||||||
|
{
|
||||||
|
const NetPlay::PadDetails details = NetPlay::GetPadDetails(info.device_number);
|
||||||
|
if (details.local_pad < 4)
|
||||||
|
netplay_pad = details;
|
||||||
|
}
|
||||||
|
m_widget = new GBAWidget(std::move(core), info, netplay_pad);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAWidgetController::GameChanged(std::string_view game_title, int width, int height)
|
void GBAWidgetController::GameChanged(const HW::GBA::CoreInfo& info)
|
||||||
{
|
{
|
||||||
m_widget->GameChanged(game_title, width, height);
|
m_widget->GameChanged(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAWidgetController::FrameEnded(std::vector<u32> video_buffer)
|
void GBAWidgetController::FrameEnded(std::vector<u32> video_buffer)
|
||||||
|
|
|
@ -4,35 +4,39 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QPoint>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
#include "Common/CommonTypes.h"
|
#include "Common/CommonTypes.h"
|
||||||
|
#include "Core/HW/GBACore.h"
|
||||||
namespace HW::GBA
|
|
||||||
{
|
|
||||||
class Core;
|
|
||||||
} // namespace HW::GBA
|
|
||||||
|
|
||||||
class QCloseEvent;
|
class QCloseEvent;
|
||||||
class QContextMenuEvent;
|
class QContextMenuEvent;
|
||||||
class QDragEnterEvent;
|
class QDragEnterEvent;
|
||||||
class QDropEvent;
|
class QDropEvent;
|
||||||
|
class QMouseEvent;
|
||||||
class QPaintEvent;
|
class QPaintEvent;
|
||||||
|
|
||||||
|
namespace NetPlay
|
||||||
|
{
|
||||||
|
struct PadDetails;
|
||||||
|
} // namespace NetPlay
|
||||||
|
|
||||||
class GBAWidget : public QWidget
|
class GBAWidget : public QWidget
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit GBAWidget(std::weak_ptr<HW::GBA::Core> core, int device_number,
|
explicit GBAWidget(std::weak_ptr<HW::GBA::Core> core, const HW::GBA::CoreInfo& info,
|
||||||
std::string_view game_title, int width, int height, QWidget* parent = nullptr,
|
const std::optional<NetPlay::PadDetails>& netplay_pad);
|
||||||
Qt::WindowFlags flags = {});
|
|
||||||
~GBAWidget();
|
~GBAWidget();
|
||||||
|
|
||||||
void GameChanged(std::string_view game_title, int width, int height);
|
void GameChanged(const HW::GBA::CoreInfo& info);
|
||||||
void SetVideoBuffer(std::vector<u32> video_buffer);
|
void SetVideoBuffer(std::vector<u32> video_buffer);
|
||||||
|
|
||||||
void SetVolume(int volume);
|
void SetVolume(int volume);
|
||||||
|
@ -49,35 +53,47 @@ public:
|
||||||
void DoState(bool export_state);
|
void DoState(bool export_state);
|
||||||
void Resize(int scale);
|
void Resize(int scale);
|
||||||
|
|
||||||
|
bool IsBorderless() const;
|
||||||
|
void SetBorderless(bool enable);
|
||||||
|
|
||||||
|
bool IsAlwaysOnTop() const;
|
||||||
|
void SetAlwaysOnTop(bool enable);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateTitle();
|
void UpdateTitle();
|
||||||
void UpdateVolume();
|
void UpdateVolume();
|
||||||
|
|
||||||
void LoadGeometry();
|
static Qt::WindowFlags LoadWindowFlags(int device_number);
|
||||||
void SaveGeometry();
|
void LoadSettings();
|
||||||
|
void SaveSettings();
|
||||||
|
|
||||||
bool CanControlCore();
|
bool CanControlCore();
|
||||||
bool CanResetCore();
|
bool CanResetCore();
|
||||||
|
|
||||||
void closeEvent(QCloseEvent* event) override;
|
void closeEvent(QCloseEvent* event) override;
|
||||||
void contextMenuEvent(QContextMenuEvent* event) override;
|
void contextMenuEvent(QContextMenuEvent* event) override;
|
||||||
|
void mouseDoubleClickEvent(QMouseEvent* event) override;
|
||||||
|
void mousePressEvent(QMouseEvent* event) override;
|
||||||
|
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||||
|
void mouseMoveEvent(QMouseEvent* event) override;
|
||||||
void paintEvent(QPaintEvent* event) override;
|
void paintEvent(QPaintEvent* event) override;
|
||||||
|
|
||||||
void dragEnterEvent(QDragEnterEvent* event) override;
|
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||||
void dropEvent(QDropEvent* event) override;
|
void dropEvent(QDropEvent* event) override;
|
||||||
|
|
||||||
std::weak_ptr<HW::GBA::Core> m_core;
|
std::weak_ptr<HW::GBA::Core> m_core;
|
||||||
std::vector<u32> m_video_buffer;
|
HW::GBA::CoreInfo m_core_info;
|
||||||
int m_device_number;
|
QImage m_last_frame;
|
||||||
|
QImage m_previous_frame;
|
||||||
int m_local_pad;
|
int m_local_pad;
|
||||||
std::string m_game_title;
|
|
||||||
int m_width;
|
|
||||||
int m_height;
|
|
||||||
std::string m_netplayer_name;
|
|
||||||
bool m_is_local_pad;
|
bool m_is_local_pad;
|
||||||
|
std::string m_netplayer_name;
|
||||||
int m_volume;
|
int m_volume;
|
||||||
bool m_muted;
|
bool m_muted;
|
||||||
bool m_force_disconnect;
|
bool m_force_disconnect;
|
||||||
|
bool m_moving;
|
||||||
|
QPoint m_move_pos;
|
||||||
|
bool m_interframe_blending;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GBAWidgetController : public QObject
|
class GBAWidgetController : public QObject
|
||||||
|
@ -87,9 +103,8 @@ public:
|
||||||
explicit GBAWidgetController() = default;
|
explicit GBAWidgetController() = default;
|
||||||
~GBAWidgetController();
|
~GBAWidgetController();
|
||||||
|
|
||||||
void Create(std::weak_ptr<HW::GBA::Core> core, int device_number, std::string_view game_title,
|
void Create(std::weak_ptr<HW::GBA::Core> core, const HW::GBA::CoreInfo& info);
|
||||||
int width, int height);
|
void GameChanged(const HW::GBA::CoreInfo& info);
|
||||||
void GameChanged(std::string_view game_title, int width, int height);
|
|
||||||
void FrameEnded(std::vector<u32> video_buffer);
|
void FrameEnded(std::vector<u32> video_buffer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
Loading…
Reference in New Issue