Add "Separate Windows" LayoutOption (#6177)

This commit is contained in:
Ameer J
2022-11-17 10:37:30 -05:00
committed by GitHub
parent 4f715b6718
commit f44c95d638
24 changed files with 358 additions and 124 deletions

View File

@@ -113,9 +113,10 @@ void EmuThread::run() {
#endif
}
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context)
OpenGLWindow::OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
bool is_secondary)
: QWindow(parent), context(std::make_unique<QOpenGLContext>(shared_context->parent())),
event_handler(event_handler) {
event_handler(event_handler), is_secondary{is_secondary} {
// disable vsync for any shared contexts
auto format = shared_context->format();
@@ -143,7 +144,7 @@ void OpenGLWindow::Present() {
context->makeCurrent(this);
if (VideoCore::g_renderer) {
VideoCore::g_renderer->TryPresent(100);
VideoCore::g_renderer->TryPresent(100, is_secondary);
}
context->swapBuffers(this);
auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>();
@@ -196,8 +197,8 @@ void OpenGLWindow::exposeEvent(QExposeEvent* event) {
QWindow::exposeEvent(event);
}
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
: QWidget(parent_), emu_thread(emu_thread) {
GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread, bool is_secondary_)
: QWidget(parent_), EmuWindow(is_secondary_), emu_thread(emu_thread) {
setWindowTitle(QStringLiteral("Citra %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name),
@@ -207,7 +208,6 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
auto layout = new QHBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
setLayout(layout);
InputCommon::Init();
this->setMouseTracking(true);
@@ -215,9 +215,7 @@ GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread)
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
}
GRenderWindow::~GRenderWindow() {
InputCommon::Shutdown();
}
GRenderWindow::~GRenderWindow() = default;
void GRenderWindow::MakeCurrent() {
core_context->MakeCurrent();
@@ -382,6 +380,12 @@ bool GRenderWindow::event(QEvent* event) {
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
QWidget::focusOutEvent(event);
InputCommon::GetKeyboard()->ReleaseAllKeys();
has_focus = false;
}
void GRenderWindow::focusInEvent(QFocusEvent* event) {
QWidget::focusInEvent(event);
has_focus = true;
}
void GRenderWindow::resizeEvent(QResizeEvent* event) {
@@ -396,7 +400,8 @@ void GRenderWindow::InitRenderTarget() {
GMainWindow* parent = GetMainWindow();
QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr;
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext());
child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext(),
is_secondary);
child_window->create();
child_widget = createWindowContainer(child_window, this);
child_widget->resize(Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight);
@@ -421,7 +426,7 @@ void GRenderWindow::ReleaseRenderTarget() {
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
if (res_scale == 0)
res_scale = VideoCore::GetResolutionScaleFactor();
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
const auto layout{Layout::FrameLayoutFromResolutionScale(res_scale, is_secondary)};
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
VideoCore::RequestScreenshot(
screenshot_image.bits(),

View File

@@ -129,7 +129,8 @@ signals:
class OpenGLWindow : public QWindow {
Q_OBJECT
public:
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context);
explicit OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context,
bool is_secondary = false);
~OpenGLWindow();
@@ -142,13 +143,14 @@ protected:
private:
std::unique_ptr<QOpenGLContext> context;
QWidget* event_handler;
bool is_secondary;
};
class GRenderWindow : public QWidget, public Frontend::EmuWindow {
Q_OBJECT
public:
GRenderWindow(QWidget* parent, EmuThread* emu_thread);
GRenderWindow(QWidget* parent, EmuThread* emu_thread, bool is_secondary);
~GRenderWindow() override;
// EmuWindow implementation.
@@ -178,6 +180,10 @@ public:
bool event(QEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
bool HasFocus() const {
return has_focus;
}
void InitRenderTarget();
@@ -229,6 +235,7 @@ private:
/// Temporary storage of the screenshot taken
QImage screenshot_image;
bool first_frame = false;
bool has_focus = false;
protected:
void showEvent(QShowEvent* event) override;

View File

@@ -261,6 +261,11 @@
<string>Side by Side</string>
</property>
</item>
<item>
<property name="text">
<string>Separate Windows</string>
</property>
</item>
</widget>
</item>
</layout>

View File

@@ -87,6 +87,7 @@
#include "core/savestate.h"
#include "core/settings.h"
#include "game_list_p.h"
#include "input_common/main.h"
#include "network/network_settings.h"
#include "ui_main.h"
#include "video_core/renderer_base.h"
@@ -256,8 +257,11 @@ void GMainWindow::InitializeWidgets() {
#ifdef CITRA_ENABLE_COMPATIBILITY_REPORTING
ui->action_Report_Compatibility->setVisible(true);
#endif
render_window = new GRenderWindow(this, emu_thread.get());
render_window = new GRenderWindow(this, emu_thread.get(), false);
secondary_window = new GRenderWindow(this, emu_thread.get(), true);
render_window->hide();
secondary_window->hide();
secondary_window->setParent(nullptr);
game_list = new GameList(this);
ui->horizontalLayout->addWidget(game_list);
@@ -277,6 +281,7 @@ void GMainWindow::InitializeWidgets() {
}
});
InputCommon::Init();
multiplayer_state = new MultiplayerState(this, game_list->GetModel(), ui->action_Leave_Room,
ui->action_Show_Room);
multiplayer_state->setVisible(false);
@@ -327,6 +332,7 @@ void GMainWindow::InitializeWidgets() {
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Single_Screen);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Large_Screen);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Side_by_Side);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Separate_Windows);
}
void GMainWindow::InitializeDebugWidgets() {
@@ -516,6 +522,17 @@ void GMainWindow::InitializeHotkeys() {
&QShortcut::activated, ui->action_Fullscreen, &QAction::trigger);
connect(hotkey_registry.GetHotkey(main_window, fullscreen, render_window),
&QShortcut::activatedAmbiguously, ui->action_Fullscreen, &QAction::trigger);
// This action will fire specifically when secondary_window is in focus
QAction* secondary_fullscreen_action = new QAction(secondary_window);
// Use the same fullscreen hotkey as the main window
const auto fullscreen_hotkey = hotkey_registry.GetKeySequence(main_window, fullscreen);
secondary_fullscreen_action->setShortcut(fullscreen_hotkey);
connect(secondary_fullscreen_action, SIGNAL(triggered()), this,
SLOT(ToggleSecondaryFullscreen()));
secondary_window->addAction(secondary_fullscreen_action);
connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Exit Fullscreen"), this),
&QShortcut::activated, this, [&] {
if (emulation_running) {
@@ -690,6 +707,10 @@ void GMainWindow::ConnectWidgetEvents() {
&GRenderWindow::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, render_window,
&GRenderWindow::OnEmulationStopping);
connect(this, &GMainWindow::EmulationStarting, secondary_window,
&GRenderWindow::OnEmulationStarting);
connect(this, &GMainWindow::EmulationStopping, secondary_window,
&GRenderWindow::OnEmulationStopping);
connect(&status_bar_update_timer, &QTimer::timeout, this, &GMainWindow::UpdateStatusBar);
@@ -763,6 +784,8 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::ChangeScreenLayout);
connect(ui->action_Screen_Layout_Side_by_Side, &QAction::triggered, this,
&GMainWindow::ChangeScreenLayout);
connect(ui->action_Screen_Layout_Separate_Windows, &QAction::triggered, this,
&GMainWindow::ChangeScreenLayout);
connect(ui->action_Screen_Layout_Swap_Screens, &QAction::triggered, this,
&GMainWindow::OnSwapScreens);
connect(ui->action_Screen_Layout_Upright_Screens, &QAction::triggered, this,
@@ -922,6 +945,7 @@ bool GMainWindow::LoadROM(const QString& filename) {
ShutdownGame();
render_window->InitRenderTarget();
secondary_window->InitRenderTarget();
Frontend::ScopeAcquireContext scope(*render_window);
@@ -936,7 +960,8 @@ bool GMainWindow::LoadROM(const QString& filename) {
Core::System& system{Core::System::GetInstance()};
const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
const Core::System::ResultStatus result{
system.Load(*render_window, filename.toStdString(), secondary_window)};
if (result != Core::System::ResultStatus::Success) {
switch (result) {
@@ -1098,6 +1123,8 @@ void GMainWindow::BootGame(const QString& filename) {
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
connect(secondary_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
connect(secondary_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
// before the CPU continues
@@ -1189,6 +1216,7 @@ void GMainWindow::ShutdownGame() {
// The emulation is stopped, so closing the window or not does not matter anymore
disconnect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
disconnect(secondary_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
// Update the GUI
ui->action_Start->setEnabled(false);
@@ -1203,6 +1231,7 @@ void GMainWindow::ShutdownGame() {
ui->action_Advance_Frame->setEnabled(false);
ui->action_Capture_Screenshot->setEnabled(false);
render_window->hide();
secondary_window->hide();
loading_screen->hide();
loading_screen->Clear();
if (game_list->IsEmpty())
@@ -1236,6 +1265,7 @@ void GMainWindow::ShutdownGame() {
// When closing the game, destroy the GLWindow to clear the context after the game is closed
render_window->ReleaseRenderTarget();
secondary_window->ReleaseRenderTarget();
}
void GMainWindow::StoreRecentFile(const QString& filename) {
@@ -1636,6 +1666,7 @@ void GMainWindow::OnStopGame() {
void GMainWindow::OnLoadComplete() {
loading_screen->OnLoadComplete();
UpdateSecondaryWindowVisibility();
}
void GMainWindow::OnMenuReportCompatibility() {
@@ -1660,6 +1691,17 @@ void GMainWindow::ToggleFullscreen() {
}
}
void GMainWindow::ToggleSecondaryFullscreen() {
if (!emulation_running) {
return;
}
if (secondary_window->isFullScreen()) {
secondary_window->showNormal();
} else {
secondary_window->showFullScreen();
}
}
void GMainWindow::ShowFullscreen() {
if (ui->action_Single_Window_Mode->isChecked()) {
UISettings::values.geometry = saveGeometry();
@@ -1709,6 +1751,19 @@ void GMainWindow::ToggleWindowMode() {
}
}
void GMainWindow::UpdateSecondaryWindowVisibility() {
if (!emulation_running) {
return;
}
if (Settings::values.layout_option == Settings::LayoutOption::SeparateWindows) {
secondary_window->RestoreGeometry();
secondary_window->show();
} else {
secondary_window->BackupGeometry();
secondary_window->hide();
}
}
void GMainWindow::ChangeScreenLayout() {
Settings::LayoutOption new_layout = Settings::LayoutOption::Default;
@@ -1720,35 +1775,38 @@ void GMainWindow::ChangeScreenLayout() {
new_layout = Settings::LayoutOption::LargeScreen;
} else if (ui->action_Screen_Layout_Side_by_Side->isChecked()) {
new_layout = Settings::LayoutOption::SideScreen;
} else if (ui->action_Screen_Layout_Separate_Windows->isChecked()) {
new_layout = Settings::LayoutOption::SeparateWindows;
}
Settings::values.layout_option = new_layout;
Settings::Apply();
UpdateSecondaryWindowVisibility();
}
void GMainWindow::ToggleScreenLayout() {
Settings::LayoutOption new_layout = Settings::LayoutOption::Default;
switch (Settings::values.layout_option) {
case Settings::LayoutOption::Default:
new_layout = Settings::LayoutOption::SingleScreen;
break;
case Settings::LayoutOption::SingleScreen:
new_layout = Settings::LayoutOption::LargeScreen;
break;
case Settings::LayoutOption::LargeScreen:
new_layout = Settings::LayoutOption::SideScreen;
break;
case Settings::LayoutOption::SideScreen:
new_layout = Settings::LayoutOption::Default;
break;
default:
LOG_ERROR(Frontend, "Unknown layout option {}", Settings::values.layout_option);
}
const Settings::LayoutOption new_layout = []() {
switch (Settings::values.layout_option) {
case Settings::LayoutOption::Default:
return Settings::LayoutOption::SingleScreen;
case Settings::LayoutOption::SingleScreen:
return Settings::LayoutOption::LargeScreen;
case Settings::LayoutOption::LargeScreen:
return Settings::LayoutOption::SideScreen;
case Settings::LayoutOption::SideScreen:
return Settings::LayoutOption::SeparateWindows;
case Settings::LayoutOption::SeparateWindows:
return Settings::LayoutOption::Default;
default:
LOG_ERROR(Frontend, "Unknown layout option {}", Settings::values.layout_option);
return Settings::LayoutOption::Default;
}
}();
Settings::values.layout_option = new_layout;
SyncMenuUISettings();
Settings::Apply();
UpdateSecondaryWindowVisibility();
}
void GMainWindow::OnSwapScreens() {
@@ -1813,6 +1871,7 @@ void GMainWindow::OnConfigure() {
} else {
setMouseTracking(false);
}
UpdateSecondaryWindowVisibility();
} else {
Settings::values.input_profiles = old_input_profiles;
Settings::values.touch_from_button_maps = old_touch_from_button_maps;
@@ -1991,7 +2050,9 @@ void GMainWindow::OnCaptureScreenshot() {
const QString timestamp =
QDateTime::currentDateTime().toString(QStringLiteral("dd.MM.yy_hh.mm.ss.z"));
path.append(QStringLiteral("/%1_%2.png").arg(filename, timestamp));
render_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
auto* const screenshot_window = secondary_window->HasFocus() ? secondary_window : render_window;
screenshot_window->CaptureScreenshot(UISettings::values.screenshot_resolution_factor, path);
OnStartGame();
}
@@ -2227,7 +2288,9 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
ShutdownGame();
render_window->close();
secondary_window->close();
multiplayer_state->Close();
InputCommon::Shutdown();
QWidget::closeEvent(event);
}
@@ -2412,6 +2475,8 @@ void GMainWindow::SyncMenuUISettings() {
Settings::LayoutOption::LargeScreen);
ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option ==
Settings::LayoutOption::SideScreen);
ui->action_Screen_Layout_Separate_Windows->setChecked(Settings::values.layout_option ==
Settings::LayoutOption::SeparateWindows);
ui->action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen);
ui->action_Screen_Layout_Upright_Screens->setChecked(Settings::values.upright_screen);
}

View File

@@ -197,7 +197,9 @@ private slots:
void OnDisplayTitleBars(bool);
void InitializeHotkeys();
void ToggleFullscreen();
void ToggleSecondaryFullscreen();
void ChangeScreenLayout();
void UpdateSecondaryWindowVisibility();
void ToggleScreenLayout();
void OnSwapScreens();
void OnRotateScreens();
@@ -238,6 +240,7 @@ private:
std::unique_ptr<Ui::MainWindow> ui;
GRenderWindow* render_window;
GRenderWindow* secondary_window;
GameListPlaceholder* game_list_placeholder;
LoadingScreen* loading_screen;

View File

@@ -125,6 +125,7 @@
<addaction name="action_Screen_Layout_Single_Screen"/>
<addaction name="action_Screen_Layout_Large_Screen"/>
<addaction name="action_Screen_Layout_Side_by_Side"/>
<addaction name="action_Screen_Layout_Separate_Windows"/>
<addaction name="separator"/>
<addaction name="action_Screen_Layout_Upright_Screens"/>
<addaction name="action_Screen_Layout_Swap_Screens"/>
@@ -471,6 +472,14 @@
<string>Side by Side</string>
</property>
</action>
<action name="action_Screen_Layout_Separate_Windows">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Separate Windows</string>
</property>
</action>
<action name="action_Screen_Layout_Swap_Screens">
<property name="checkable">
<bool>true</bool>