feat(qt): add custom screen layout editor

This commit is contained in:
Johel Ernesto Guerrero Peña 2021-04-03 07:43:56 -04:00
parent 8e3c7674d8
commit 85063565b7
18 changed files with 842 additions and 124 deletions

View File

@ -160,7 +160,6 @@ void Config::ReadValues() {
static_cast<Settings::LayoutOption>(sdl2_config->GetInteger("Layout", "layout_option", 0)); static_cast<Settings::LayoutOption>(sdl2_config->GetInteger("Layout", "layout_option", 0));
Settings::values.swap_screen = sdl2_config->GetBoolean("Layout", "swap_screen", false); Settings::values.swap_screen = sdl2_config->GetBoolean("Layout", "swap_screen", false);
Settings::values.upright_screen = sdl2_config->GetBoolean("Layout", "upright_screen", false); Settings::values.upright_screen = sdl2_config->GetBoolean("Layout", "upright_screen", false);
Settings::values.custom_layout = sdl2_config->GetBoolean("Layout", "custom_layout", false);
Settings::values.custom_top_left = Settings::values.custom_top_left =
static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_left", 0)); static_cast<u16>(sdl2_config->GetInteger("Layout", "custom_top_left", 0));
Settings::values.custom_top_top = Settings::values.custom_top_top =

View File

@ -79,6 +79,12 @@ add_executable(citra-qt
configuration/configure_web.cpp configuration/configure_web.cpp
configuration/configure_web.h configuration/configure_web.h
configuration/configure_web.ui configuration/configure_web.ui
configuration/custom_screen.cpp
configuration/custom_screen.h
configuration/custom_screen.ui
configuration/custom_screen_layout_editor.cpp
configuration/custom_screen_layout_editor.h
configuration/custom_screen_layout_editor.ui
debugger/console.h debugger/console.h
debugger/console.cpp debugger/console.cpp
debugger/graphics/graphics.cpp debugger/graphics/graphics.cpp

View File

@ -343,7 +343,6 @@ void Config::ReadLayoutValues() {
static_cast<Settings::LayoutOption>(ReadSetting(QStringLiteral("layout_option")).toInt()); static_cast<Settings::LayoutOption>(ReadSetting(QStringLiteral("layout_option")).toInt());
Settings::values.swap_screen = ReadSetting(QStringLiteral("swap_screen"), false).toBool(); Settings::values.swap_screen = ReadSetting(QStringLiteral("swap_screen"), false).toBool();
Settings::values.upright_screen = ReadSetting(QStringLiteral("upright_screen"), false).toBool(); Settings::values.upright_screen = ReadSetting(QStringLiteral("upright_screen"), false).toBool();
Settings::values.custom_layout = ReadSetting(QStringLiteral("custom_layout"), false).toBool();
Settings::values.custom_top_left = ReadSetting(QStringLiteral("custom_top_left"), 0).toInt(); Settings::values.custom_top_left = ReadSetting(QStringLiteral("custom_top_left"), 0).toInt();
Settings::values.custom_top_top = ReadSetting(QStringLiteral("custom_top_top"), 0).toInt(); Settings::values.custom_top_top = ReadSetting(QStringLiteral("custom_top_top"), 0).toInt();
Settings::values.custom_top_right = Settings::values.custom_top_right =
@ -887,7 +886,6 @@ void Config::SaveLayoutValues() {
WriteSetting(QStringLiteral("layout_option"), static_cast<int>(Settings::values.layout_option)); WriteSetting(QStringLiteral("layout_option"), static_cast<int>(Settings::values.layout_option));
WriteSetting(QStringLiteral("swap_screen"), Settings::values.swap_screen, false); WriteSetting(QStringLiteral("swap_screen"), Settings::values.swap_screen, false);
WriteSetting(QStringLiteral("upright_screen"), Settings::values.upright_screen, false); WriteSetting(QStringLiteral("upright_screen"), Settings::values.upright_screen, false);
WriteSetting(QStringLiteral("custom_layout"), Settings::values.custom_layout, false);
WriteSetting(QStringLiteral("custom_top_left"), Settings::values.custom_top_left, 0); WriteSetting(QStringLiteral("custom_top_left"), Settings::values.custom_top_left, 0);
WriteSetting(QStringLiteral("custom_top_top"), Settings::values.custom_top_top, 0); WriteSetting(QStringLiteral("custom_top_top"), Settings::values.custom_top_top, 0);
WriteSetting(QStringLiteral("custom_top_right"), Settings::values.custom_top_right, 400); WriteSetting(QStringLiteral("custom_top_right"), Settings::values.custom_top_right, 400);

View File

@ -2,16 +2,41 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <cassert>
#include <QColorDialog> #include <QColorDialog>
#include <QPoint>
#include <QString>
#include "citra_qt/configuration/configure_enhancements.h" #include "citra_qt/configuration/configure_enhancements.h"
#include "core/3ds.h"
#include "core/core.h" #include "core/core.h"
#include "core/settings.h" #include "core/settings.h"
#include "ui_configure_enhancements.h" #include "ui_configure_enhancements.h"
#include "video_core/renderer_opengl/post_processing_opengl.h" #include "video_core/renderer_opengl/post_processing_opengl.h"
#include "video_core/renderer_opengl/texture_filters/texture_filterer.h" #include "video_core/renderer_opengl/texture_filters/texture_filterer.h"
namespace {
const QString g_top_screen_name{QObject::tr("Top")};
const QString g_bottom_screen_name{QObject::tr("Bottom")};
void AddScreens(CustomScreenLayoutEditor& layout_editor) {
layout_editor.addScreen(
g_top_screen_name, {0, 0, Core::kScreenTopWidth, Core::kScreenTopHeight},
{QPoint{Settings::values.custom_top_left, Settings::values.custom_top_top},
QPoint{Settings::values.custom_top_right - 1, Settings::values.custom_top_bottom - 1}});
layout_editor.addScreen(
g_bottom_screen_name,
{40, Core::kScreenTopHeight, Core::kScreenBottomWidth, Core::kScreenBottomHeight},
{QPoint{Settings::values.custom_bottom_left, Settings::values.custom_bottom_top},
QPoint{Settings::values.custom_bottom_right - 1,
Settings::values.custom_bottom_bottom - 1}});
}
} // namespace
ConfigureEnhancements::ConfigureEnhancements(QWidget* parent) ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureEnhancements>()) { : QWidget(parent), ui(std::make_unique<Ui::ConfigureEnhancements>()),
layout_editor(std::make_unique<CustomScreenLayoutEditor>(this)) {
ui->setupUi(this); ui->setupUi(this);
for (const auto& filter : OpenGL::TextureFilterer::GetFilterNames()) for (const auto& filter : OpenGL::TextureFilterer::GetFilterNames())
@ -19,16 +44,19 @@ ConfigureEnhancements::ConfigureEnhancements(QWidget* parent)
SetConfiguration(); SetConfiguration();
ui->layoutBox->setEnabled(!Settings::values.custom_layout);
ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer); ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer);
AddScreens(*layout_editor);
connect(ui->render_3d_combobox, connect(ui->render_3d_combobox,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
[this](int currentIndex) { [this](int currentIndex) {
updateShaders(static_cast<Settings::StereoRenderOption>(currentIndex)); updateShaders(static_cast<Settings::StereoRenderOption>(currentIndex));
}); });
connect(ui->editor_button, &QPushButton::clicked, this,
[this] { layout_editor->showMaximized(); });
connect(ui->bg_button, &QPushButton::clicked, this, [this] { connect(ui->bg_button, &QPushButton::clicked, this, [this] {
const QColor new_bg_color = QColorDialog::getColor(bg_color); const QColor new_bg_color = QColorDialog::getColor(bg_color);
if (!new_bg_color.isValid()) { if (!new_bg_color.isValid()) {
@ -113,6 +141,23 @@ void ConfigureEnhancements::ApplyConfiguration() {
Settings::values.texture_filter_name = ui->texture_filter_combobox->currentText().toStdString(); Settings::values.texture_filter_name = ui->texture_filter_combobox->currentText().toStdString();
Settings::values.layout_option = Settings::values.layout_option =
static_cast<Settings::LayoutOption>(ui->layout_combobox->currentIndex()); static_cast<Settings::LayoutOption>(ui->layout_combobox->currentIndex());
{
const auto geometry = [this](const QString& screen_name) {
const auto* const screen{layout_editor->getScreen(screen_name)};
assert(screen);
return screen->geometry();
};
const QRect top_screen{geometry(g_top_screen_name)};
Settings::values.custom_top_left = top_screen.left();
Settings::values.custom_top_top = top_screen.top();
Settings::values.custom_top_right = top_screen.right() + 1;
Settings::values.custom_top_bottom = top_screen.bottom() + 1;
const QRect bottom_screen{geometry(g_bottom_screen_name)};
Settings::values.custom_bottom_left = bottom_screen.left();
Settings::values.custom_bottom_top = bottom_screen.top();
Settings::values.custom_bottom_right = bottom_screen.right() + 1;
Settings::values.custom_bottom_bottom = bottom_screen.bottom() + 1;
}
Settings::values.swap_screen = ui->swap_screen->isChecked(); Settings::values.swap_screen = ui->swap_screen->isChecked();
Settings::values.upright_screen = ui->upright_screen->isChecked(); Settings::values.upright_screen = ui->upright_screen->isChecked();
Settings::values.dump_textures = ui->toggle_dump_textures->isChecked(); Settings::values.dump_textures = ui->toggle_dump_textures->isChecked();

View File

@ -6,6 +6,7 @@
#include <memory> #include <memory>
#include <QWidget> #include <QWidget>
#include "citra_qt/configuration/custom_screen_layout_editor.h"
namespace Settings { namespace Settings {
enum class StereoRenderOption; enum class StereoRenderOption;
@ -31,5 +32,6 @@ private:
void updateTextureFilter(int index); void updateTextureFilter(int index);
std::unique_ptr<Ui::ConfigureEnhancements> ui; std::unique_ptr<Ui::ConfigureEnhancements> ui;
std::unique_ptr<CustomScreenLayoutEditor> layout_editor;
QColor bg_color; QColor bg_color;
}; };

View File

@ -216,6 +216,13 @@
<string>Layout</string> <string>Layout</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QPushButton" name="editor_button">
<property name="text">
<string>Edit Custom Screen Layout</string>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item> <item>
@ -247,6 +254,11 @@
<string>Side by Side</string> <string>Side by Side</string>
</property> </property>
</item> </item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget> </widget>
</item> </item>
</layout> </layout>

View File

@ -0,0 +1,261 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cassert>
#include <cmath>
#include <numeric>
#include <utility>
#include <QMouseEvent>
#include <QMoveEvent>
#include <QResizeEvent>
#include <QSpinBox>
#include <QtGlobal>
#include "citra_qt/configuration/custom_screen.h"
#include "common/diagnostic.h"
#include "ui_custom_screen.h"
CITRA_DIAGNOSTIC_IGNORE_SWITCH
constexpr unsigned to_underlying(RectangleSides s) {
return static_cast<unsigned>(s);
}
constexpr RectangleSides operator|(RectangleSides l, RectangleSides r) {
return RectangleSides{to_underlying(l) | to_underlying(r)};
}
constexpr RectangleSides operator&(RectangleSides l, RectangleSides r) {
return RectangleSides{to_underlying(l) & to_underlying(r)};
}
CustomScreen::CustomScreen(QWidget* parent)
: QFrame(parent), ui(std::make_unique<Ui::CustomScreen>()) {
ui->setupUi(this);
const auto value_changed{QOverload<int>::of(&QSpinBox::valueChanged)};
connect(ui->left, value_changed, this, [this](int x) { move(x, y()); });
connect(ui->top, value_changed, this, [this](int y) { move(x(), y); });
connect(ui->width, value_changed, this, [this](int width) {
resize(width, height());
ui->width->setValue(this->width());
});
connect(ui->height, value_changed, this, [this](int height) {
resize(width(), height);
ui->height->setValue(this->height());
});
}
QString CustomScreen::name() const {
return ui->name->text();
}
void CustomScreen::setName(const QString& name) {
ui->name->setText(name);
}
namespace {
bool IsClamped(int v, int lo, int hi) {
return lo <= v && v < hi;
}
} // namespace
auto CustomScreen::SidesAt(QPoint pt) -> RectangleSides {
return RectangleSides{
(IsClamped(pt.x(), 0, lineWidth()) ? RectangleSides::left : RectangleSides{}) |
(IsClamped(pt.y(), 0, lineWidth()) ? RectangleSides::top : RectangleSides{}) |
(IsClamped(pt.x(), width() - lineWidth(), width()) ? RectangleSides::right
: RectangleSides{}) |
(IsClamped(pt.y(), height() - lineWidth(), height()) ? RectangleSides::bottom
: RectangleSides{})};
}
void CustomScreen::mousePressEvent(QMouseEvent* event) {
switch (event->buttons()) {
case Qt::MouseButton::LeftButton:
drag = {SidesAt(event->pos()), geometry(), event->globalPos()};
if (drag.sides == RectangleSides{})
setCursor(Qt::CursorShape::ClosedHandCursor);
return event->accept();
default:
return event->ignore();
}
}
void CustomScreen::mouseMoveEvent(QMouseEvent* event) {
switch (event->buttons()) {
case Qt::MouseButton::NoButton:
switch (SidesAt(event->pos())) {
case RectangleSides::left:
case RectangleSides::right:
setCursor(Qt::CursorShape::SizeHorCursor);
break;
case RectangleSides::top:
case RectangleSides::bottom:
setCursor(Qt::CursorShape::SizeVerCursor);
break;
case RectangleSides::top | RectangleSides::left:
case RectangleSides::bottom | RectangleSides::right:
setCursor(Qt::CursorShape::SizeFDiagCursor);
break;
case RectangleSides::top | RectangleSides::right:
case RectangleSides::bottom | RectangleSides::left:
setCursor(Qt::CursorShape::SizeBDiagCursor);
break;
default:
setCursor(Qt::CursorShape::OpenHandCursor);
break;
}
break;
case Qt::MouseButton::LeftButton:
drag(*this, event->globalPos());
break;
default:
return event->ignore();
}
event->accept();
}
namespace {
bool HasSingleBit(unsigned x) {
return x != 0 && (x & (x - 1)) == 0;
}
unsigned BitWidth(unsigned x) {
return x == 0 ? 0 : 1 + static_cast<unsigned>(std::log2(x));
}
struct RectangleSideOffsets {
int sides[4]{};
int& operator[](RectangleSides s) {
assert(HasSingleBit(to_underlying(s)));
return sides[BitWidth(to_underlying(s)) - 1];
}
int& left() {
return operator[](RectangleSides::left);
}
int& top() {
return operator[](RectangleSides::top);
}
int& right() {
return operator[](RectangleSides::right);
}
int& bottom() {
return operator[](RectangleSides::bottom);
}
};
RectangleSides prev(RectangleSides side) {
assert(HasSingleBit(to_underlying(side)));
return side == RectangleSides::left ? RectangleSides::bottom
: RectangleSides{to_underlying(side) >> 1};
}
RectangleSides next(RectangleSides side) {
assert(HasSingleBit(to_underlying(side)));
return side == RectangleSides::bottom ? RectangleSides::left
: RectangleSides{to_underlying(side) << 1};
}
bool HaveEqualOutwardsDirectionSign(RectangleSides l, RectangleSides r) {
assert(l != r);
return (l | r) == (RectangleSides::top | RectangleSides::left) ||
(l | r) == (RectangleSides::bottom | RectangleSides::right);
}
void GrowAdjacents(double aspect_ratio, RectangleSideOffsets& offsets, RectangleSides side) {
assert(HasSingleBit(to_underlying(side)));
const int adjacent_growth{static_cast<int>(aspect_ratio * offsets[side])};
RectangleSides adjacent_sides[2]{prev(side), next(side)};
if (!HaveEqualOutwardsDirectionSign(side, adjacent_sides[0]))
std::swap(adjacent_sides[0], adjacent_sides[1]);
offsets[adjacent_sides[0]] = adjacent_growth / 2;
offsets[adjacent_sides[1]] = -adjacent_growth / 2;
}
int FurthestOffset(RectangleSides side, int l, int r) {
assert(HasSingleBit(to_underlying(side)));
return (side & (RectangleSides::top | RectangleSides::left)) != RectangleSides{}
? std::min(l, r)
: std::max(l, r);
}
constexpr RectangleSides vertical_sides{RectangleSides::left | RectangleSides::right};
constexpr RectangleSides horizontal_sides{RectangleSides::top | RectangleSides::bottom};
[[maybe_unused]] bool IsCorner(RectangleSides sides) {
return HasSingleBit(to_underlying(sides & vertical_sides)) &&
HasSingleBit(to_underlying(sides & horizontal_sides));
}
void GrowCorner(double aspect_ratio, RectangleSideOffsets& offsets, RectangleSides corner) {
assert(IsCorner(corner));
const RectangleSides vertical_side{corner & vertical_sides};
const RectangleSides horizontal_side{corner & horizontal_sides};
int& vertical_offset{offsets[vertical_side]};
int& horizontal_offset{offsets[horizontal_side]};
const int adjust{HaveEqualOutwardsDirectionSign(vertical_side, horizontal_side) ? 1 : -1};
const int grown_vertical_offset{static_cast<int>(horizontal_offset * aspect_ratio) * adjust};
const int grown_horizontal_offset{static_cast<int>(vertical_offset / aspect_ratio) * adjust};
vertical_offset = FurthestOffset(vertical_side, vertical_offset, grown_vertical_offset);
horizontal_offset = FurthestOffset(horizontal_side, horizontal_offset, grown_horizontal_offset);
}
} // namespace
void CustomScreen::Drag::operator()(CustomScreen& screen, QPoint move_position) const {
const auto drag_offset{move_position - press_position};
if (sides == RectangleSides{})
return screen.move(original_screen_geometry.topLeft() + drag_offset);
RectangleSideOffsets offsets{
(sides & RectangleSides::left) != RectangleSides{} ? drag_offset.x() : 0,
(sides & RectangleSides::top) != RectangleSides{} ? drag_offset.y() : 0,
(sides & RectangleSides::right) != RectangleSides{} ? drag_offset.x() : 0,
(sides & RectangleSides::bottom) != RectangleSides{} ? drag_offset.y() : 0};
if (screen.ui->keep_aspect_ratio->isChecked()) {
const double aspect_ratio{original_screen_geometry.width() * 1.0 /
original_screen_geometry.height()};
(HasSingleBit(to_underlying(sides)) ? GrowAdjacents : GrowCorner)(aspect_ratio, offsets,
sides);
}
screen.move(std::min(original_screen_geometry.x() + offsets.left(),
original_screen_geometry.right() - screen.minimumWidth() + 1),
std::min(original_screen_geometry.y() + offsets.top(),
original_screen_geometry.bottom() - screen.minimumHeight() + 1));
screen.resize(original_screen_geometry.width() + offsets.right() - offsets.left(),
original_screen_geometry.height() + offsets.bottom() - offsets.top());
}
void CustomScreen::moveEvent(QMoveEvent*) {
move(std::max(0, x()), std::max(0, y()));
ui->left->setValue(x());
ui->top->setValue(y());
}
void CustomScreen::resizeEvent(QResizeEvent*) {
moveEvent(nullptr);
ui->width->setValue(width());
ui->height->setValue(height());
#if !defined(NDEBUG)
const int num{width()};
const int den{height()};
const int gcd{std::gcd(num, den)};
ui->keep_aspect_ratio->setText(
tr("Keep aspect ratio (%1, %2:%3)").arg(1.0 * num / den).arg(num / gcd).arg(den / gcd));
#endif
}
CustomScreen::~CustomScreen() {}

View File

@ -0,0 +1,57 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QFrame>
#include <QPoint>
#include <QRect>
#include <QString>
enum class RectangleSides : unsigned {
left = (1 << 0),
top = (1 << 1),
right = (1 << 2),
bottom = (1 << 3)
};
namespace Ui {
class CustomScreen;
}
class CustomScreen : public QFrame {
Q_OBJECT
public:
explicit CustomScreen(QWidget* parent = nullptr);
~CustomScreen();
QString name() const;
void setName(const QString&);
protected:
void mousePressEvent(QMouseEvent*) override;
void mouseMoveEvent(QMouseEvent*) override;
void moveEvent(QMoveEvent*) override;
void resizeEvent(QResizeEvent*) override;
private:
RectangleSides SidesAt(QPoint);
struct Drag {
RectangleSides sides;
QRect original_screen_geometry;
QPoint press_position;
void operator()(CustomScreen& screen, QPoint move_position) const;
};
friend Drag;
std::unique_ptr<Ui::CustomScreen> ui;
Drag drag;
};

View File

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CustomScreen</class>
<widget class="QFrame" name="CustomScreen">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="lineWidth">
<number>5</number>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<item>
<widget class="QLabel" name="name">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="keep_aspect_ratio">
<property name="text">
<string>Keep aspect ratio</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Left</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="left">
<property name="maximum">
<number>16777215</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Top</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="top">
<property name="maximum">
<number>16777215</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="width">
<property name="maximum">
<number>16777215</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="height">
<property name="maximum">
<number>16777215</number>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,47 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <cstddef>
#include <utility>
#include <QMainWindow>
#include <QPushButton>
#include "citra_qt/configuration/custom_screen_layout_editor.h"
#include "ui_custom_screen_layout_editor.h"
CustomScreenLayoutEditor::CustomScreenLayoutEditor(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::CustomScreenLayoutEditor>()) {
ui->setupUi(this);
auto* const framebuffer = new QMainWindow;
ui->scroll_area->setWidget(framebuffer);
const auto set = [this](QRect RestorableScreen::*geometry) {
return [this, geometry] {
for (auto& s : screens)
s.screen->setGeometry(s.*geometry);
};
};
connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QPushButton::clicked, this,
set(&RestorableScreen::reset_geometry));
connect(ui->buttonBox->button(QDialogButtonBox::RestoreDefaults), &QPushButton::clicked, this,
set(&RestorableScreen::default_geometry));
}
void CustomScreenLayoutEditor::addScreen(QString name, QRect default_geometry,
QRect current_geometry) {
auto s{std::make_unique<CustomScreen>(ui->scroll_area->widget())};
s->setGeometry(current_geometry);
s->setName(name);
screens.emplace_back(RestorableScreen{std::move(s), default_geometry});
}
CustomScreen* CustomScreenLayoutEditor::getScreen(QString name) const {
auto s{std::find_if(screens.begin(), screens.end(),
[&](auto& s) { return s.screen->name() == name; })};
return s == screens.end() ? nullptr : s->screen.get();
}
CustomScreenLayoutEditor::~CustomScreenLayoutEditor() {}

View File

@ -0,0 +1,38 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include <QDialog>
#include <QRect>
#include <QString>
#include "citra_qt/configuration/custom_screen.h"
namespace Ui {
class CustomScreenLayoutEditor;
}
class CustomScreenLayoutEditor : public QDialog {
Q_OBJECT
public:
explicit CustomScreenLayoutEditor(QWidget* parent = nullptr);
~CustomScreenLayoutEditor();
void addScreen(QString name, QRect default_geometry, QRect current_geometry);
CustomScreen* getScreen(QString name) const;
private:
struct RestorableScreen {
std::unique_ptr<CustomScreen> screen;
QRect default_geometry;
QRect reset_geometry{screen->geometry()};
};
std::unique_ptr<Ui::CustomScreenLayoutEditor> ui;
std::vector<RestorableScreen> screens;
};

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CustomScreenLayoutEditor</class>
<widget class="QDialog" name="CustomScreenLayoutEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>490</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Custom Screen Layout Editor</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QScrollArea" name="scroll_area">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>470</width>
<height>250</height>
</rect>
</property>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset|QDialogButtonBox::RestoreDefaults</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>CustomScreenLayoutEditor</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>257</x>
<y>290</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>CustomScreenLayoutEditor</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>325</x>
<y>290</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -301,6 +301,7 @@ void GMainWindow::InitializeWidgets() {
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Single_Screen); 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_Large_Screen);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Side_by_Side); actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Side_by_Side);
actionGroup_ScreenLayouts->addAction(ui->action_Screen_Layout_Custom);
} }
void GMainWindow::InitializeDebugWidgets() { void GMainWindow::InitializeDebugWidgets() {
@ -734,6 +735,8 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::ChangeScreenLayout); &GMainWindow::ChangeScreenLayout);
connect(ui->action_Screen_Layout_Side_by_Side, &QAction::triggered, this, connect(ui->action_Screen_Layout_Side_by_Side, &QAction::triggered, this,
&GMainWindow::ChangeScreenLayout); &GMainWindow::ChangeScreenLayout);
connect(ui->action_Screen_Layout_Custom, &QAction::triggered, this,
&GMainWindow::ChangeScreenLayout);
connect(ui->action_Screen_Layout_Swap_Screens, &QAction::triggered, this, connect(ui->action_Screen_Layout_Swap_Screens, &QAction::triggered, this,
&GMainWindow::OnSwapScreens); &GMainWindow::OnSwapScreens);
connect(ui->action_Screen_Layout_Upright_Screens, &QAction::triggered, this, connect(ui->action_Screen_Layout_Upright_Screens, &QAction::triggered, this,
@ -1664,6 +1667,8 @@ void GMainWindow::ChangeScreenLayout() {
new_layout = Settings::LayoutOption::LargeScreen; new_layout = Settings::LayoutOption::LargeScreen;
} else if (ui->action_Screen_Layout_Side_by_Side->isChecked()) { } else if (ui->action_Screen_Layout_Side_by_Side->isChecked()) {
new_layout = Settings::LayoutOption::SideScreen; new_layout = Settings::LayoutOption::SideScreen;
} else if (ui->action_Screen_Layout_Custom->isChecked()) {
new_layout = Settings::LayoutOption::Custom;
} }
Settings::values.layout_option = new_layout; Settings::values.layout_option = new_layout;
@ -1671,24 +1676,9 @@ void GMainWindow::ChangeScreenLayout() {
} }
void GMainWindow::ToggleScreenLayout() { void GMainWindow::ToggleScreenLayout() {
Settings::LayoutOption new_layout = Settings::LayoutOption::Default; Settings::values.layout_option =
static_cast<Settings::LayoutOption>((static_cast<int>(Settings::values.layout_option) + 1) %
switch (Settings::values.layout_option) { (static_cast<int>(Settings::LayoutOption::Custom) + 1)),
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;
}
Settings::values.layout_option = new_layout;
SyncMenuUISettings(); SyncMenuUISettings();
Settings::Apply(); Settings::Apply();
} }
@ -2387,6 +2377,8 @@ void GMainWindow::SyncMenuUISettings() {
Settings::LayoutOption::LargeScreen); Settings::LayoutOption::LargeScreen);
ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option == ui->action_Screen_Layout_Side_by_Side->setChecked(Settings::values.layout_option ==
Settings::LayoutOption::SideScreen); Settings::LayoutOption::SideScreen);
ui->action_Screen_Layout_Custom->setChecked(Settings::values.layout_option ==
Settings::LayoutOption::Custom);
ui->action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen); ui->action_Screen_Layout_Swap_Screens->setChecked(Settings::values.swap_screen);
ui->action_Screen_Layout_Upright_Screens->setChecked(Settings::values.upright_screen); ui->action_Screen_Layout_Upright_Screens->setChecked(Settings::values.upright_screen);
} }

View File

@ -125,6 +125,7 @@
<addaction name="action_Screen_Layout_Single_Screen"/> <addaction name="action_Screen_Layout_Single_Screen"/>
<addaction name="action_Screen_Layout_Large_Screen"/> <addaction name="action_Screen_Layout_Large_Screen"/>
<addaction name="action_Screen_Layout_Side_by_Side"/> <addaction name="action_Screen_Layout_Side_by_Side"/>
<addaction name="action_Screen_Layout_Custom"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_Screen_Layout_Upright_Screens"/> <addaction name="action_Screen_Layout_Upright_Screens"/>
<addaction name="action_Screen_Layout_Swap_Screens"/> <addaction name="action_Screen_Layout_Swap_Screens"/>
@ -235,20 +236,20 @@
</property> </property>
</action> </action>
<action name="action_Save"> <action name="action_Save">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Save</string> <string>Save</string>
</property> </property>
</action> </action>
<action name="action_Load"> <action name="action_Load">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>false</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Load</string> <string>Load</string>
</property> </property>
</action> </action>
<action name="action_FAQ"> <action name="action_FAQ">
<property name="text"> <property name="text">
@ -461,6 +462,14 @@
<string>Side by Side</string> <string>Side by Side</string>
</property> </property>
</action> </action>
<action name="action_Screen_Layout_Custom">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Custom</string>
</property>
</action>
<action name="action_Screen_Layout_Swap_Screens"> <action name="action_Screen_Layout_Swap_Screens">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>

27
src/common/diagnostic.h Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2019 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
// Adapted from
// https://github.com/ericniebler/range-v3/blob/master/include/range/v3/detail/config.hpp#L185.
#define CITRA_PRAGMA(X) _Pragma(#X)
#if !CITRA_COMP_MSVC
#define CITRA_DIAGNOSTIC_PUSH CITRA_PRAGMA(GCC diagnostic push)
#define CITRA_DIAGNOSTIC_POP CITRA_PRAGMA(GCC diagnostic pop)
#define CITRA_DIAGNOSTIC_IGNORE_PRAGMAS CITRA_PRAGMA(GCC diagnostic ignored "-Wpragmas")
#define CITRA_DIAGNOSTIC_IGNORE(X) \
CITRA_DIAGNOSTIC_IGNORE_PRAGMAS \
CITRA_PRAGMA(GCC diagnostic ignored "-Wunknown-pragmas") \
CITRA_PRAGMA(GCC diagnostic ignored "-Wunknown-warning-option") \
CITRA_PRAGMA(GCC diagnostic ignored X)
#define CITRA_DIAGNOSTIC_IGNORE_SWITCH CITRA_DIAGNOSTIC_IGNORE("-Wswitch")
#else
#define CITRA_DIAGNOSTIC_PUSH CITRA_PRAGMA(warning(push))
#define CITRA_DIAGNOSTIC_POP CITRA_PRAGMA(warning(pop))
#define CITRA_DIAGNOSTIC_IGNORE_PRAGMAS CITRA_PRAGMA(warning(disable : 4068))
#define CITRA_DIAGNOSTIC_IGNORE(X) \
CITRA_DIAGNOSTIC_IGNORE_PRAGMAS CITRA_PRAGMA(warning(disable : X))
#define CITRA_DIAGNOSTIC_IGNORE_SWITCH
#endif

View File

@ -151,32 +151,31 @@ void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height)
const auto min_size = const auto min_size =
Layout::GetMinimumSizeFromLayout(layout_option, Settings::values.upright_screen); Layout::GetMinimumSizeFromLayout(layout_option, Settings::values.upright_screen);
if (Settings::values.custom_layout == true) { width = std::max(width, min_size.first);
height = std::max(height, min_size.second);
switch (layout_option) {
case Settings::LayoutOption::Custom:
layout = Layout::CustomFrameLayout(width, height); layout = Layout::CustomFrameLayout(width, height);
} else { break;
width = std::max(width, min_size.first); case Settings::LayoutOption::SingleScreen:
height = std::max(height, min_size.second); layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen,
switch (layout_option) { Settings::values.upright_screen);
case Settings::LayoutOption::SingleScreen: break;
layout = Layout::SingleFrameLayout(width, height, Settings::values.swap_screen, case Settings::LayoutOption::LargeScreen:
Settings::values.upright_screen); layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen,
break; Settings::values.upright_screen);
case Settings::LayoutOption::LargeScreen: break;
layout = Layout::LargeFrameLayout(width, height, Settings::values.swap_screen, case Settings::LayoutOption::SideScreen:
Settings::values.upright_screen); layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen,
break; Settings::values.upright_screen);
case Settings::LayoutOption::SideScreen: break;
layout = Layout::SideFrameLayout(width, height, Settings::values.swap_screen, case Settings::LayoutOption::Default:
Settings::values.upright_screen); default:
break; layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen,
case Settings::LayoutOption::Default: Settings::values.upright_screen);
default: break;
layout = Layout::DefaultFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
}
UpdateMinimumWindowSize(min_size);
} }
UpdateMinimumWindowSize(min_size);
NotifyFramebufferLayoutChanged(layout); NotifyFramebufferLayoutChanged(layout);
} }

View File

@ -286,79 +286,78 @@ FramebufferLayout CustomFrameLayout(u32 width, u32 height) {
FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) { FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale) {
FramebufferLayout layout; FramebufferLayout layout;
if (Settings::values.custom_layout == true) { int width, height;
switch (Settings::values.layout_option) {
case Settings::LayoutOption::Custom:
layout = CustomFrameLayout( layout = CustomFrameLayout(
std::max(Settings::values.custom_top_right, Settings::values.custom_bottom_right), std::max(Settings::values.custom_top_right, Settings::values.custom_bottom_right),
std::max(Settings::values.custom_top_bottom, Settings::values.custom_bottom_bottom)); std::max(Settings::values.custom_top_bottom, Settings::values.custom_bottom_bottom));
} else { break;
int width, height; case Settings::LayoutOption::SingleScreen:
switch (Settings::values.layout_option) { if (Settings::values.upright_screen) {
case Settings::LayoutOption::SingleScreen: if (Settings::values.swap_screen) {
if (Settings::values.upright_screen) { width = Core::kScreenBottomHeight * res_scale;
if (Settings::values.swap_screen) { height = Core::kScreenBottomWidth * res_scale;
width = Core::kScreenBottomHeight * res_scale;
height = Core::kScreenBottomWidth * res_scale;
} else {
width = Core::kScreenTopHeight * res_scale;
height = Core::kScreenTopWidth * res_scale;
}
} else { } else {
if (Settings::values.swap_screen) {
width = Core::kScreenBottomWidth * res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else {
width = Core::kScreenTopWidth * res_scale;
height = Core::kScreenTopHeight * res_scale;
}
}
layout = SingleFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::LargeScreen:
if (Settings::values.upright_screen) {
if (Settings::values.swap_screen) {
width = Core::kScreenBottomHeight * res_scale;
height = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
} else {
width = Core::kScreenTopHeight * res_scale;
height = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 4) * res_scale;
}
} else {
if (Settings::values.swap_screen) {
width = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else {
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 4) * res_scale;
height = Core::kScreenTopHeight * res_scale;
}
}
layout = LargeFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::SideScreen:
if (Settings::values.upright_screen) {
width = Core::kScreenTopHeight * res_scale; width = Core::kScreenTopHeight * res_scale;
height = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
} else {
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
height = Core::kScreenTopHeight * res_scale;
}
layout = SideFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::Default:
default:
if (Settings::values.upright_screen) {
width = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
height = Core::kScreenTopWidth * res_scale; height = Core::kScreenTopWidth * res_scale;
}
} else {
if (Settings::values.swap_screen) {
width = Core::kScreenBottomWidth * res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else { } else {
width = Core::kScreenTopWidth * res_scale; width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale; height = Core::kScreenTopHeight * res_scale;
} }
layout = DefaultFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
} }
layout = SingleFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::LargeScreen:
if (Settings::values.upright_screen) {
if (Settings::values.swap_screen) {
width = Core::kScreenBottomHeight * res_scale;
height = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
} else {
width = Core::kScreenTopHeight * res_scale;
height = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 4) * res_scale;
}
} else {
if (Settings::values.swap_screen) {
width = (Core::kScreenBottomWidth + Core::kScreenTopWidth / 4) * res_scale;
height = Core::kScreenBottomHeight * res_scale;
} else {
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth / 4) * res_scale;
height = Core::kScreenTopHeight * res_scale;
}
}
layout = LargeFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::SideScreen:
if (Settings::values.upright_screen) {
width = Core::kScreenTopHeight * res_scale;
height = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
} else {
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale;
height = Core::kScreenTopHeight * res_scale;
}
layout = SideFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
case Settings::LayoutOption::Default:
default:
if (Settings::values.upright_screen) {
width = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
height = Core::kScreenTopWidth * res_scale;
} else {
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
}
layout = DefaultFrameLayout(width, height, Settings::values.swap_screen,
Settings::values.upright_screen);
break;
} }
return layout; return layout;
} }

View File

@ -24,6 +24,7 @@ enum class LayoutOption {
SingleScreen, SingleScreen,
LargeScreen, LargeScreen,
SideScreen, SideScreen,
Custom,
}; };
enum class MicInputType { enum class MicInputType {
@ -163,7 +164,6 @@ struct Values {
LayoutOption layout_option; LayoutOption layout_option;
bool swap_screen; bool swap_screen;
bool upright_screen; bool upright_screen;
bool custom_layout;
u16 custom_top_left; u16 custom_top_left;
u16 custom_top_top; u16 custom_top_top;
u16 custom_top_right; u16 custom_top_right;