early-access version 1255

This commit is contained in:
pineappleEA
2020-12-28 15:15:37 +00:00
parent 84b39492d1
commit 78b48028e1
6254 changed files with 1868140 additions and 0 deletions
+283
View File
@@ -0,0 +1,283 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
add_executable(yuzu
Info.plist
about_dialog.cpp
about_dialog.h
aboutdialog.ui
applets/controller.cpp
applets/controller.h
applets/controller.ui
applets/error.cpp
applets/error.h
applets/profile_select.cpp
applets/profile_select.h
applets/software_keyboard.cpp
applets/software_keyboard.h
applets/web_browser.cpp
applets/web_browser.h
bootmanager.cpp
bootmanager.h
compatdb.ui
compatibility_list.cpp
compatibility_list.h
configuration/config.cpp
configuration/config.h
configuration/configuration_shared.cpp
configuration/configuration_shared.h
configuration/configure.ui
configuration/configure_audio.cpp
configuration/configure_audio.h
configuration/configure_audio.ui
configuration/configure_cpu.cpp
configuration/configure_cpu.h
configuration/configure_cpu.ui
configuration/configure_cpu_debug.cpp
configuration/configure_cpu_debug.h
configuration/configure_cpu_debug.ui
configuration/configure_debug.cpp
configuration/configure_debug.h
configuration/configure_debug.ui
configuration/configure_debug_controller.cpp
configuration/configure_debug_controller.h
configuration/configure_debug_controller.ui
configuration/configure_dialog.cpp
configuration/configure_dialog.h
configuration/configure_filesystem.cpp
configuration/configure_filesystem.h
configuration/configure_filesystem.ui
configuration/configure_general.cpp
configuration/configure_general.h
configuration/configure_general.ui
configuration/configure_graphics.cpp
configuration/configure_graphics.h
configuration/configure_graphics.ui
configuration/configure_graphics_advanced.cpp
configuration/configure_graphics_advanced.h
configuration/configure_graphics_advanced.ui
configuration/configure_hotkeys.cpp
configuration/configure_hotkeys.h
configuration/configure_hotkeys.ui
configuration/configure_input.cpp
configuration/configure_input.h
configuration/configure_input.ui
configuration/configure_input_advanced.cpp
configuration/configure_input_advanced.h
configuration/configure_input_advanced.ui
configuration/configure_input_player.cpp
configuration/configure_input_player.h
configuration/configure_input_player.ui
configuration/configure_input_profile_dialog.cpp
configuration/configure_input_profile_dialog.h
configuration/configure_input_profile_dialog.ui
configuration/configure_motion_touch.cpp
configuration/configure_motion_touch.h
configuration/configure_motion_touch.ui
configuration/configure_mouse_advanced.cpp
configuration/configure_mouse_advanced.h
configuration/configure_mouse_advanced.ui
configuration/configure_per_game.cpp
configuration/configure_per_game.h
configuration/configure_per_game.ui
configuration/configure_per_game_addons.cpp
configuration/configure_per_game_addons.h
configuration/configure_per_game_addons.ui
configuration/configure_profile_manager.cpp
configuration/configure_profile_manager.h
configuration/configure_profile_manager.ui
configuration/configure_service.cpp
configuration/configure_service.h
configuration/configure_service.ui
configuration/configure_system.cpp
configuration/configure_system.h
configuration/configure_system.ui
configuration/configure_touch_from_button.cpp
configuration/configure_touch_from_button.h
configuration/configure_touch_from_button.ui
configuration/configure_touchscreen_advanced.cpp
configuration/configure_touchscreen_advanced.h
configuration/configure_touchscreen_advanced.ui
configuration/configure_touch_widget.h
configuration/configure_ui.cpp
configuration/configure_ui.h
configuration/configure_ui.ui
configuration/configure_vibration.cpp
configuration/configure_vibration.h
configuration/configure_vibration.ui
configuration/configure_web.cpp
configuration/configure_web.h
configuration/configure_web.ui
configuration/input_profiles.cpp
configuration/input_profiles.h
debugger/console.cpp
debugger/console.h
debugger/profiler.cpp
debugger/profiler.h
debugger/wait_tree.cpp
debugger/wait_tree.h
discord.h
game_list.cpp
game_list.h
game_list_p.h
game_list_worker.cpp
game_list_worker.h
hotkeys.cpp
hotkeys.h
install_dialog.cpp
install_dialog.h
loading_screen.cpp
loading_screen.h
loading_screen.ui
main.cpp
main.h
main.ui
uisettings.cpp
uisettings.h
util/limitable_input_dialog.cpp
util/limitable_input_dialog.h
util/sequence_dialog/sequence_dialog.cpp
util/sequence_dialog/sequence_dialog.h
util/url_request_interceptor.cpp
util/url_request_interceptor.h
util/util.cpp
util/util.h
compatdb.cpp
compatdb.h
yuzu.rc
)
file(GLOB COMPAT_LIST
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*)
file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
if (ENABLE_QT_TRANSLATION)
set(YUZU_QT_LANGUAGES "${PROJECT_SOURCE_DIR}/dist/languages" CACHE PATH "Path to the translation bundle for the Qt frontend")
option(GENERATE_QT_TRANSLATION "Generate en.ts as the translation source file" OFF)
# Update source TS file if enabled
if (GENERATE_QT_TRANSLATION)
get_target_property(SRCS yuzu SOURCES)
qt5_create_translation(QM_FILES ${SRCS} ${UIS} ${YUZU_QT_LANGUAGES}/en.ts)
add_custom_target(translation ALL DEPENDS ${YUZU_QT_LANGUAGES}/en.ts)
endif()
# Find all TS files except en.ts
file(GLOB_RECURSE LANGUAGES_TS ${YUZU_QT_LANGUAGES}/*.ts)
list(REMOVE_ITEM LANGUAGES_TS ${YUZU_QT_LANGUAGES}/en.ts)
# Compile TS files to QM files
qt5_add_translation(LANGUAGES_QM ${LANGUAGES_TS})
# Build a QRC file from the QM file list
set(LANGUAGES_QRC ${CMAKE_CURRENT_BINARY_DIR}/languages.qrc)
file(WRITE ${LANGUAGES_QRC} "<RCC><qresource prefix=\"languages\">\n")
foreach (QM ${LANGUAGES_QM})
get_filename_component(QM_FILE ${QM} NAME)
file(APPEND ${LANGUAGES_QRC} "<file>${QM_FILE}</file>\n")
endforeach (QM)
file(APPEND ${LANGUAGES_QRC} "</qresource></RCC>")
# Add the QRC file to package in all QM files
qt5_add_resources(LANGUAGES ${LANGUAGES_QRC})
else()
set(LANGUAGES)
endif()
target_sources(yuzu
PRIVATE
${COMPAT_LIST}
${ICONS}
${LANGUAGES}
${THEMES}
)
if (APPLE)
set(MACOSX_ICON "../../dist/yuzu.icns")
set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
target_sources(yuzu PRIVATE ${MACOSX_ICON})
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(yuzu PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
elseif(WIN32)
# compile as a win32 gui application instead of a console application
target_link_libraries(yuzu PRIVATE Qt5::WinMain)
if(MSVC)
set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS")
elseif(MINGW)
set_target_properties(yuzu PROPERTIES LINK_FLAGS_RELEASE "-mwindows")
endif()
endif()
create_target_directory_groups(yuzu)
target_link_libraries(yuzu PRIVATE common core input_common video_core)
target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
target_include_directories(yuzu PRIVATE ../../externals/Vulkan-Headers/include)
if (NOT WIN32)
target_include_directories(yuzu PRIVATE ${Qt5Gui_PRIVATE_INCLUDE_DIRS})
endif()
target_compile_definitions(yuzu PRIVATE
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
-DQT_USE_QSTRINGBUILDER
# Disable implicit conversions from/to C strings
-DQT_NO_CAST_FROM_ASCII
-DQT_NO_CAST_TO_ASCII
# Disable implicit type narrowing in signal/slot connect() calls.
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable unsafe overloads of QProcess' start() function.
-DQT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
-DQT_NO_URL_CAST_FROM_STRING
)
if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_COMPATIBILITY_REPORTING)
endif()
if (USE_DISCORD_PRESENCE)
target_sources(yuzu PUBLIC
discord_impl.cpp
discord_impl.h
)
target_link_libraries(yuzu PRIVATE discord-rpc)
target_compile_definitions(yuzu PRIVATE -DUSE_DISCORD_PRESENCE)
endif()
if (YUZU_USE_QT_WEB_ENGINE)
target_link_libraries(yuzu PRIVATE Qt5::WebEngineCore Qt5::WebEngineWidgets)
target_compile_definitions(yuzu PRIVATE -DYUZU_USE_QT_WEB_ENGINE)
endif ()
if (YUZU_ENABLE_BOXCAT)
target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_BOXCAT)
endif ()
if(UNIX AND NOT APPLE)
install(TARGETS yuzu RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()
if (MSVC)
include(CopyYuzuQt5Deps)
include(CopyYuzuSDLDeps)
include(CopyYuzuFFmpegDeps)
copy_yuzu_Qt5_deps(yuzu)
copy_yuzu_SDL_deps(yuzu)
copy_yuzu_FFmpeg_deps(yuzu)
endif()
if (NOT APPLE)
target_compile_definitions(yuzu PRIVATE HAS_OPENGL)
endif()
+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string></string>
<key>CFBundleIconFile</key>
<string>yuzu.icns</string>
<key>CFBundleIdentifier</key>
<string>com.yuzu-emu.yuzu</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string></string>
<key>CFBundleName</key>
<string>yuzu</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string></string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string></string>
<key>CSResourcesFileMapped</key>
<true/>
<key>LSRequiresCarbon</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<string>True</string>
</dict>
</plist>
+25
View File
@@ -0,0 +1,25 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QIcon>
#include <fmt/format.h>
#include "common/scm_rev.h"
#include "ui_aboutdialog.h"
#include "yuzu/about_dialog.h"
AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) {
const auto build_id = std::string(Common::g_build_id);
const auto fmt = std::string(Common::g_title_bar_format_idle);
const auto yuzu_build_version =
fmt::format(fmt.empty() ? "yuzu Development Build" : fmt, std::string{}, std::string{},
std::string{}, std::string{}, std::string{}, build_id);
ui->setupUi(this);
ui->labelLogo->setPixmap(QIcon::fromTheme(QStringLiteral("yuzu")).pixmap(200));
ui->labelBuildInfo->setText(ui->labelBuildInfo->text().arg(
QString::fromStdString(yuzu_build_version), QString::fromUtf8(Common::g_scm_branch),
QString::fromUtf8(Common::g_scm_desc), QString::fromUtf8(Common::g_build_date).left(10)));
}
AboutDialog::~AboutDialog() = default;
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
namespace Ui {
class AboutDialog;
}
class AboutDialog : public QDialog {
Q_OBJECT
public:
explicit AboutDialog(QWidget* parent);
~AboutDialog() override;
private:
std::unique_ptr<Ui::AboutDialog> ui;
};
+171
View File
@@ -0,0 +1,171 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AboutDialog</class>
<widget class="QDialog" name="AboutDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>616</width>
<height>261</height>
</rect>
</property>
<property name="windowTitle">
<string>About yuzu</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="labelLogo">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;img src=&quot;:/icons/yuzu.png&quot;/&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="labelYuzu">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:28pt;&quot;&gt;yuzu&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelBuildInfo">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;%1 | %2-%3 (%4)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelAbout">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;yuzu is an experimental open-source emulator for the Nintendo Switch licensed under GPLv2.0.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:12pt;&quot;&gt;This software should not be used to play games you have not legally obtained.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="labelLinks">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Website&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Source Code&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/graphs/contributors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;Contributors&lt;/span&gt;&lt;/a&gt; | &lt;a href=&quot;https://github.com/yuzu-emu/yuzu/blob/master/license.txt&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#039be5;&quot;&gt;License&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelLiability">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;&amp;quot;Nintendo Switch&amp;quot; is a trademark of Nintendo. yuzu is not affiliated with Nintendo in any way.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../dist/icons/icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AboutDialog</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AboutDialog</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>
+672
View File
@@ -0,0 +1,672 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <thread>
#include "common/assert.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/lock.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/sm/sm.h"
#include "ui_controller.h"
#include "yuzu/applets/controller.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_input_profile_dialog.h"
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
#include "yuzu/main.h"
namespace {
constexpr std::size_t HANDHELD_INDEX = 8;
constexpr std::array<std::array<bool, 4>, 8> led_patterns{{
{true, false, false, false},
{true, true, false, false},
{true, true, true, false},
{true, true, true, true},
{true, false, false, true},
{true, false, true, false},
{true, false, true, true},
{false, true, true, false},
}};
void UpdateController(Settings::ControllerType controller_type, std::size_t npad_index,
bool connected) {
Core::System& system{Core::System::GetInstance()};
if (!system.IsPoweredOn()) {
return;
}
Service::SM::ServiceManager& sm = system.ServiceManager();
auto& npad =
sm.GetService<Service::HID::Hid>("hid")
->GetAppletResource()
->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad);
npad.UpdateControllerAt(npad.MapSettingsTypeToNPad(controller_type), npad_index, connected);
}
// Returns true if the given controller type is compatible with the given parameters.
bool IsControllerCompatible(Settings::ControllerType controller_type,
Core::Frontend::ControllerParameters parameters) {
switch (controller_type) {
case Settings::ControllerType::ProController:
return parameters.allow_pro_controller;
case Settings::ControllerType::DualJoyconDetached:
return parameters.allow_dual_joycons;
case Settings::ControllerType::LeftJoycon:
return parameters.allow_left_joycon;
case Settings::ControllerType::RightJoycon:
return parameters.allow_right_joycon;
case Settings::ControllerType::Handheld:
return parameters.enable_single_mode && parameters.allow_handheld;
default:
return false;
}
}
} // namespace
QtControllerSelectorDialog::QtControllerSelectorDialog(
QWidget* parent, Core::Frontend::ControllerParameters parameters_,
InputCommon::InputSubsystem* input_subsystem_)
: QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()),
parameters(std::move(parameters_)), input_subsystem{input_subsystem_},
input_profiles(std::make_unique<InputProfiles>()) {
ui->setupUi(this);
player_widgets = {
ui->widgetPlayer1, ui->widgetPlayer2, ui->widgetPlayer3, ui->widgetPlayer4,
ui->widgetPlayer5, ui->widgetPlayer6, ui->widgetPlayer7, ui->widgetPlayer8,
};
player_groupboxes = {
ui->groupPlayer1Connected, ui->groupPlayer2Connected, ui->groupPlayer3Connected,
ui->groupPlayer4Connected, ui->groupPlayer5Connected, ui->groupPlayer6Connected,
ui->groupPlayer7Connected, ui->groupPlayer8Connected,
};
connected_controller_icons = {
ui->controllerPlayer1, ui->controllerPlayer2, ui->controllerPlayer3, ui->controllerPlayer4,
ui->controllerPlayer5, ui->controllerPlayer6, ui->controllerPlayer7, ui->controllerPlayer8,
};
led_patterns_boxes = {{
{ui->checkboxPlayer1LED1, ui->checkboxPlayer1LED2, ui->checkboxPlayer1LED3,
ui->checkboxPlayer1LED4},
{ui->checkboxPlayer2LED1, ui->checkboxPlayer2LED2, ui->checkboxPlayer2LED3,
ui->checkboxPlayer2LED4},
{ui->checkboxPlayer3LED1, ui->checkboxPlayer3LED2, ui->checkboxPlayer3LED3,
ui->checkboxPlayer3LED4},
{ui->checkboxPlayer4LED1, ui->checkboxPlayer4LED2, ui->checkboxPlayer4LED3,
ui->checkboxPlayer4LED4},
{ui->checkboxPlayer5LED1, ui->checkboxPlayer5LED2, ui->checkboxPlayer5LED3,
ui->checkboxPlayer5LED4},
{ui->checkboxPlayer6LED1, ui->checkboxPlayer6LED2, ui->checkboxPlayer6LED3,
ui->checkboxPlayer6LED4},
{ui->checkboxPlayer7LED1, ui->checkboxPlayer7LED2, ui->checkboxPlayer7LED3,
ui->checkboxPlayer7LED4},
{ui->checkboxPlayer8LED1, ui->checkboxPlayer8LED2, ui->checkboxPlayer8LED3,
ui->checkboxPlayer8LED4},
}};
explain_text_labels = {
ui->labelPlayer1Explain, ui->labelPlayer2Explain, ui->labelPlayer3Explain,
ui->labelPlayer4Explain, ui->labelPlayer5Explain, ui->labelPlayer6Explain,
ui->labelPlayer7Explain, ui->labelPlayer8Explain,
};
emulated_controllers = {
ui->comboPlayer1Emulated, ui->comboPlayer2Emulated, ui->comboPlayer3Emulated,
ui->comboPlayer4Emulated, ui->comboPlayer5Emulated, ui->comboPlayer6Emulated,
ui->comboPlayer7Emulated, ui->comboPlayer8Emulated,
};
player_labels = {
ui->labelPlayer1, ui->labelPlayer2, ui->labelPlayer3, ui->labelPlayer4,
ui->labelPlayer5, ui->labelPlayer6, ui->labelPlayer7, ui->labelPlayer8,
};
connected_controller_labels = {
ui->labelConnectedPlayer1, ui->labelConnectedPlayer2, ui->labelConnectedPlayer3,
ui->labelConnectedPlayer4, ui->labelConnectedPlayer5, ui->labelConnectedPlayer6,
ui->labelConnectedPlayer7, ui->labelConnectedPlayer8,
};
connected_controller_checkboxes = {
ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
};
// Setup/load everything prior to setting up connections.
// This avoids unintentionally changing the states of elements while loading them in.
SetSupportedControllers();
DisableUnsupportedPlayers();
for (std::size_t player_index = 0; player_index < NUM_PLAYERS; ++player_index) {
SetEmulatedControllers(player_index);
}
LoadConfiguration();
for (std::size_t i = 0; i < NUM_PLAYERS; ++i) {
SetExplainText(i);
UpdateControllerIcon(i);
UpdateLEDPattern(i);
UpdateBorderColor(i);
connect(player_groupboxes[i], &QGroupBox::toggled, [this, i](bool checked) {
if (checked) {
for (std::size_t index = 0; index <= i; ++index) {
connected_controller_checkboxes[index]->setChecked(checked);
}
} else {
for (std::size_t index = i; index < NUM_PLAYERS; ++index) {
connected_controller_checkboxes[index]->setChecked(checked);
}
}
});
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
[this, i](int) {
UpdateControllerIcon(i);
UpdateControllerState(i);
UpdateLEDPattern(i);
CheckIfParametersMet();
});
connect(connected_controller_checkboxes[i], &QCheckBox::stateChanged, [this, i](int state) {
player_groupboxes[i]->setChecked(state == Qt::Checked);
UpdateControllerIcon(i);
UpdateControllerState(i);
UpdateLEDPattern(i);
UpdateBorderColor(i);
CheckIfParametersMet();
});
if (i == 0) {
connect(emulated_controllers[i], qOverload<int>(&QComboBox::currentIndexChanged),
[this, i](int index) {
UpdateDockedState(GetControllerTypeFromIndex(index, i) ==
Settings::ControllerType::Handheld);
});
}
}
connect(ui->vibrationButton, &QPushButton::clicked, this,
&QtControllerSelectorDialog::CallConfigureVibrationDialog);
connect(ui->inputConfigButton, &QPushButton::clicked, this,
&QtControllerSelectorDialog::CallConfigureInputProfileDialog);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
&QtControllerSelectorDialog::ApplyConfiguration);
// Enhancement: Check if the parameters have already been met before disconnecting controllers.
// If all the parameters are met AND only allows a single player,
// stop the constructor here as we do not need to continue.
if (CheckIfParametersMet() && parameters.enable_single_mode) {
return;
}
// If keep_controllers_connected is false, forcefully disconnect all controllers
if (!parameters.keep_controllers_connected) {
for (auto player : player_groupboxes) {
player->setChecked(false);
}
}
resize(0, 0);
}
QtControllerSelectorDialog::~QtControllerSelectorDialog() = default;
int QtControllerSelectorDialog::exec() {
if (parameters_met && parameters.enable_single_mode) {
return QDialog::Accepted;
}
return QDialog::exec();
}
void QtControllerSelectorDialog::ApplyConfiguration() {
const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
}
void QtControllerSelectorDialog::LoadConfiguration() {
for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
const auto connected =
Settings::values.players.GetValue()[index].connected ||
(index == 0 && Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
player_groupboxes[index]->setChecked(connected);
connected_controller_checkboxes[index]->setChecked(connected);
emulated_controllers[index]->setCurrentIndex(GetIndexFromControllerType(
Settings::values.players.GetValue()[index].controller_type, index));
}
UpdateDockedState(Settings::values.players.GetValue()[HANDHELD_INDEX].connected);
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
}
void QtControllerSelectorDialog::CallConfigureVibrationDialog() {
ConfigureVibration dialog(this);
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint);
dialog.setWindowModality(Qt::WindowModal);
if (dialog.exec() == QDialog::Accepted) {
dialog.ApplyConfiguration();
}
}
void QtControllerSelectorDialog::CallConfigureInputProfileDialog() {
ConfigureInputProfileDialog dialog(this, input_subsystem, input_profiles.get());
dialog.setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint |
Qt::WindowSystemMenuHint);
dialog.setWindowModality(Qt::WindowModal);
dialog.exec();
}
bool QtControllerSelectorDialog::CheckIfParametersMet() {
// Here, we check and validate the current configuration against all applicable parameters.
const auto num_connected_players = static_cast<int>(
std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
[this](const QGroupBox* player) { return player->isChecked(); }));
const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
// First, check against the number of connected players.
if (num_connected_players < min_supported_players ||
num_connected_players > max_supported_players) {
parameters_met = false;
ui->buttonBox->setEnabled(parameters_met);
return parameters_met;
}
// Next, check against all connected controllers.
const auto all_controllers_compatible = [this] {
for (std::size_t index = 0; index < NUM_PLAYERS; ++index) {
// Skip controllers that are not used, we only care about the currently connected ones.
if (!player_groupboxes[index]->isChecked() || !player_groupboxes[index]->isEnabled()) {
continue;
}
const auto compatible = IsControllerCompatible(
GetControllerTypeFromIndex(emulated_controllers[index]->currentIndex(), index),
parameters);
// If any controller is found to be incompatible, return false early.
if (!compatible) {
return false;
}
}
// Reaching here means all currently connected controllers are compatible.
return true;
}();
parameters_met = all_controllers_compatible;
ui->buttonBox->setEnabled(parameters_met);
return parameters_met;
}
void QtControllerSelectorDialog::SetSupportedControllers() {
const QString theme = [] {
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
return QStringLiteral("_dark");
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
return QStringLiteral("_midnight");
} else {
return QString{};
}
}();
if (parameters.enable_single_mode && parameters.allow_handheld) {
ui->controllerSupported1->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_handheld%0); ").arg(theme));
} else {
ui->controllerSupported1->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_handheld%0_disabled); ").arg(theme));
}
if (parameters.allow_dual_joycons) {
ui->controllerSupported2->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ").arg(theme));
} else {
ui->controllerSupported2->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_dual_joycon%0_disabled); ").arg(theme));
}
if (parameters.allow_left_joycon) {
ui->controllerSupported3->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_joycon_left%0); ").arg(theme));
} else {
ui->controllerSupported3->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_joycon_left%0_disabled); ").arg(theme));
}
if (parameters.allow_right_joycon) {
ui->controllerSupported4->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_joycon_right%0); ").arg(theme));
} else {
ui->controllerSupported4->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_joycon_right%0_disabled); ").arg(theme));
}
if (parameters.allow_pro_controller) {
ui->controllerSupported5->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_pro_controller%0); ").arg(theme));
} else {
ui->controllerSupported5->setStyleSheet(
QStringLiteral("image: url(:/controller/applet_pro_controller%0_disabled); ")
.arg(theme));
}
// enable_single_mode overrides min_players and max_players.
if (parameters.enable_single_mode) {
ui->numberSupportedLabel->setText(QStringLiteral("1"));
return;
}
if (parameters.min_players == parameters.max_players) {
ui->numberSupportedLabel->setText(QStringLiteral("%1").arg(parameters.max_players));
} else {
ui->numberSupportedLabel->setText(
QStringLiteral("%1 - %2").arg(parameters.min_players).arg(parameters.max_players));
}
}
void QtControllerSelectorDialog::SetEmulatedControllers(std::size_t player_index) {
auto& pairs = index_controller_type_pairs[player_index];
pairs.clear();
emulated_controllers[player_index]->clear();
pairs.emplace_back(emulated_controllers[player_index]->count(),
Settings::ControllerType::ProController);
emulated_controllers[player_index]->addItem(tr("Pro Controller"));
pairs.emplace_back(emulated_controllers[player_index]->count(),
Settings::ControllerType::DualJoyconDetached);
emulated_controllers[player_index]->addItem(tr("Dual Joycons"));
pairs.emplace_back(emulated_controllers[player_index]->count(),
Settings::ControllerType::LeftJoycon);
emulated_controllers[player_index]->addItem(tr("Left Joycon"));
pairs.emplace_back(emulated_controllers[player_index]->count(),
Settings::ControllerType::RightJoycon);
emulated_controllers[player_index]->addItem(tr("Right Joycon"));
if (player_index == 0) {
pairs.emplace_back(emulated_controllers[player_index]->count(),
Settings::ControllerType::Handheld);
emulated_controllers[player_index]->addItem(tr("Handheld"));
}
}
Settings::ControllerType QtControllerSelectorDialog::GetControllerTypeFromIndex(
int index, std::size_t player_index) const {
const auto& pairs = index_controller_type_pairs[player_index];
const auto it = std::find_if(pairs.begin(), pairs.end(),
[index](const auto& pair) { return pair.first == index; });
if (it == pairs.end()) {
return Settings::ControllerType::ProController;
}
return it->second;
}
int QtControllerSelectorDialog::GetIndexFromControllerType(Settings::ControllerType type,
std::size_t player_index) const {
const auto& pairs = index_controller_type_pairs[player_index];
const auto it = std::find_if(pairs.begin(), pairs.end(),
[type](const auto& pair) { return pair.second == type; });
if (it == pairs.end()) {
return 0;
}
return it->first;
}
void QtControllerSelectorDialog::UpdateControllerIcon(std::size_t player_index) {
if (!player_groupboxes[player_index]->isChecked()) {
connected_controller_icons[player_index]->setStyleSheet(QString{});
player_labels[player_index]->show();
return;
}
const QString stylesheet = [this, player_index] {
switch (GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
player_index)) {
case Settings::ControllerType::ProController:
return QStringLiteral("image: url(:/controller/applet_pro_controller%0); ");
case Settings::ControllerType::DualJoyconDetached:
return QStringLiteral("image: url(:/controller/applet_dual_joycon%0); ");
case Settings::ControllerType::LeftJoycon:
return QStringLiteral("image: url(:/controller/applet_joycon_left%0); ");
case Settings::ControllerType::RightJoycon:
return QStringLiteral("image: url(:/controller/applet_joycon_right%0); ");
case Settings::ControllerType::Handheld:
return QStringLiteral("image: url(:/controller/applet_handheld%0); ");
default:
return QString{};
}
}();
if (stylesheet.isEmpty()) {
connected_controller_icons[player_index]->setStyleSheet(QString{});
player_labels[player_index]->show();
return;
}
const QString theme = [] {
if (QIcon::themeName().contains(QStringLiteral("dark"))) {
return QStringLiteral("_dark");
} else if (QIcon::themeName().contains(QStringLiteral("midnight"))) {
return QStringLiteral("_midnight");
} else {
return QString{};
}
}();
connected_controller_icons[player_index]->setStyleSheet(stylesheet.arg(theme));
player_labels[player_index]->hide();
}
void QtControllerSelectorDialog::UpdateControllerState(std::size_t player_index) {
auto& player = Settings::values.players.GetValue()[player_index];
const auto controller_type = GetControllerTypeFromIndex(
emulated_controllers[player_index]->currentIndex(), player_index);
const auto player_connected = player_groupboxes[player_index]->isChecked() &&
controller_type != Settings::ControllerType::Handheld;
if (player.controller_type == controller_type && player.connected == player_connected) {
// Set vibration devices in the event that the input device has changed.
ConfigureVibration::SetVibrationDevices(player_index);
return;
}
// Disconnect the controller first.
UpdateController(controller_type, player_index, false);
player.controller_type = controller_type;
player.connected = player_connected;
ConfigureVibration::SetVibrationDevices(player_index);
// Handheld
if (player_index == 0) {
auto& handheld = Settings::values.players.GetValue()[HANDHELD_INDEX];
if (controller_type == Settings::ControllerType::Handheld) {
handheld = player;
}
handheld.connected = player_groupboxes[player_index]->isChecked() &&
controller_type == Settings::ControllerType::Handheld;
UpdateController(Settings::ControllerType::Handheld, 8, handheld.connected);
}
if (!player.connected) {
return;
}
// This emulates a delay between disconnecting and reconnecting controllers as some games
// do not respond to a change in controller type if it was instantaneous.
using namespace std::chrono_literals;
std::this_thread::sleep_for(20ms);
UpdateController(controller_type, player_index, player_connected);
}
void QtControllerSelectorDialog::UpdateLEDPattern(std::size_t player_index) {
if (!player_groupboxes[player_index]->isChecked() ||
GetControllerTypeFromIndex(emulated_controllers[player_index]->currentIndex(),
player_index) == Settings::ControllerType::Handheld) {
led_patterns_boxes[player_index][0]->setChecked(false);
led_patterns_boxes[player_index][1]->setChecked(false);
led_patterns_boxes[player_index][2]->setChecked(false);
led_patterns_boxes[player_index][3]->setChecked(false);
return;
}
led_patterns_boxes[player_index][0]->setChecked(led_patterns[player_index][0]);
led_patterns_boxes[player_index][1]->setChecked(led_patterns[player_index][1]);
led_patterns_boxes[player_index][2]->setChecked(led_patterns[player_index][2]);
led_patterns_boxes[player_index][3]->setChecked(led_patterns[player_index][3]);
}
void QtControllerSelectorDialog::UpdateBorderColor(std::size_t player_index) {
if (!parameters.enable_border_color ||
player_index >= static_cast<std::size_t>(parameters.max_players) ||
player_groupboxes[player_index]->styleSheet().contains(QStringLiteral("QGroupBox"))) {
return;
}
player_groupboxes[player_index]->setStyleSheet(
player_groupboxes[player_index]->styleSheet().append(
QStringLiteral("QGroupBox#groupPlayer%1Connected:checked "
"{ border: 1px solid rgba(%2, %3, %4, %5); }")
.arg(player_index + 1)
.arg(parameters.border_colors[player_index][0])
.arg(parameters.border_colors[player_index][1])
.arg(parameters.border_colors[player_index][2])
.arg(parameters.border_colors[player_index][3])));
}
void QtControllerSelectorDialog::SetExplainText(std::size_t player_index) {
if (!parameters.enable_explain_text ||
player_index >= static_cast<std::size_t>(parameters.max_players)) {
return;
}
explain_text_labels[player_index]->setText(QString::fromStdString(
Common::StringFromFixedZeroTerminatedBuffer(parameters.explain_text[player_index].data(),
parameters.explain_text[player_index].size())));
}
void QtControllerSelectorDialog::UpdateDockedState(bool is_handheld) {
// Disallow changing the console mode if the controller type is handheld.
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
// Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
ui->radioUndocked->setChecked(true);
}
}
void QtControllerSelectorDialog::DisableUnsupportedPlayers() {
const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
switch (max_supported_players) {
case 0:
default:
UNREACHABLE();
return;
case 1:
ui->widgetSpacer->hide();
ui->widgetSpacer2->hide();
ui->widgetSpacer3->hide();
ui->widgetSpacer4->hide();
break;
case 2:
ui->widgetSpacer->hide();
ui->widgetSpacer2->hide();
ui->widgetSpacer3->hide();
break;
case 3:
ui->widgetSpacer->hide();
ui->widgetSpacer2->hide();
break;
case 4:
ui->widgetSpacer->hide();
break;
case 5:
case 6:
case 7:
case 8:
break;
}
for (std::size_t index = max_supported_players; index < NUM_PLAYERS; ++index) {
// Disconnect any unsupported players here and disable or hide them if applicable.
Settings::values.players.GetValue()[index].connected = false;
UpdateController(Settings::values.players.GetValue()[index].controller_type, index, false);
// Hide the player widgets when max_supported_controllers is less than or equal to 4.
if (max_supported_players <= 4) {
player_widgets[index]->hide();
}
// Disable and hide the following to prevent these from interaction.
player_widgets[index]->setDisabled(true);
connected_controller_checkboxes[index]->setDisabled(true);
connected_controller_labels[index]->hide();
connected_controller_checkboxes[index]->hide();
}
}
QtControllerSelector::QtControllerSelector(GMainWindow& parent) {
connect(this, &QtControllerSelector::MainWindowReconfigureControllers, &parent,
&GMainWindow::ControllerSelectorReconfigureControllers, Qt::QueuedConnection);
connect(&parent, &GMainWindow::ControllerSelectorReconfigureFinished, this,
&QtControllerSelector::MainWindowReconfigureFinished, Qt::QueuedConnection);
}
QtControllerSelector::~QtControllerSelector() = default;
void QtControllerSelector::ReconfigureControllers(
std::function<void()> callback_, const Core::Frontend::ControllerParameters& parameters) const {
callback = std::move(callback_);
emit MainWindowReconfigureControllers(parameters);
}
void QtControllerSelector::MainWindowReconfigureFinished() {
// Acquire the HLE mutex
std::lock_guard lock(HLE::g_hle_lock);
callback();
}
+161
View File
@@ -0,0 +1,161 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <QDialog>
#include "core/frontend/applets/controller.h"
class GMainWindow;
class QCheckBox;
class QComboBox;
class QDialogButtonBox;
class QGroupBox;
class QLabel;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
namespace Settings {
enum class ControllerType;
}
namespace Ui {
class QtControllerSelectorDialog;
}
class QtControllerSelectorDialog final : public QDialog {
Q_OBJECT
public:
explicit QtControllerSelectorDialog(QWidget* parent,
Core::Frontend::ControllerParameters parameters_,
InputCommon::InputSubsystem* input_subsystem_);
~QtControllerSelectorDialog() override;
int exec() override;
private:
// Applies the current configuration.
void ApplyConfiguration();
// Loads the current input configuration into the frontend applet.
void LoadConfiguration();
// Initializes the "Configure Vibration" Dialog.
void CallConfigureVibrationDialog();
// Initializes the "Create Input Profile" Dialog.
void CallConfigureInputProfileDialog();
// Checks the current configuration against the given parameters.
// This sets and returns the value of parameters_met.
bool CheckIfParametersMet();
// Sets the controller icons for "Supported Controller Types".
void SetSupportedControllers();
// Sets the emulated controllers per player.
void SetEmulatedControllers(std::size_t player_index);
// Gets the Controller Type for a given controller combobox index per player.
Settings::ControllerType GetControllerTypeFromIndex(int index, std::size_t player_index) const;
// Gets the controller combobox index for a given Controller Type per player.
int GetIndexFromControllerType(Settings::ControllerType type, std::size_t player_index) const;
// Updates the controller icons per player.
void UpdateControllerIcon(std::size_t player_index);
// Updates the controller state (type and connection status) per player.
void UpdateControllerState(std::size_t player_index);
// Updates the LED pattern per player.
void UpdateLEDPattern(std::size_t player_index);
// Updates the border color per player.
void UpdateBorderColor(std::size_t player_index);
// Sets the "Explain Text" per player.
void SetExplainText(std::size_t player_index);
// Updates the console mode.
void UpdateDockedState(bool is_handheld);
// Disables and disconnects unsupported players based on the given parameters.
void DisableUnsupportedPlayers();
std::unique_ptr<Ui::QtControllerSelectorDialog> ui;
// Parameters sent in from the backend HLE applet.
Core::Frontend::ControllerParameters parameters;
InputCommon::InputSubsystem* input_subsystem;
std::unique_ptr<InputProfiles> input_profiles;
// This is true if and only if all parameters are met. Otherwise, this is false.
// This determines whether the "OK" button can be clicked to exit the applet.
bool parameters_met{false};
static constexpr std::size_t NUM_PLAYERS = 8;
// Widgets encapsulating the groupboxes and comboboxes per player.
std::array<QWidget*, NUM_PLAYERS> player_widgets;
// Groupboxes encapsulating the controller icons and LED patterns per player.
std::array<QGroupBox*, NUM_PLAYERS> player_groupboxes;
// Icons for currently connected controllers/players.
std::array<QWidget*, NUM_PLAYERS> connected_controller_icons;
// Labels that represent the player numbers in place of the controller icons.
std::array<QLabel*, NUM_PLAYERS> player_labels;
// LED patterns for currently connected controllers/players.
std::array<std::array<QCheckBox*, 4>, NUM_PLAYERS> led_patterns_boxes;
// Labels representing additional information known as "Explain Text" per player.
std::array<QLabel*, NUM_PLAYERS> explain_text_labels;
// Comboboxes with a list of emulated controllers per player.
std::array<QComboBox*, NUM_PLAYERS> emulated_controllers;
/// Pairs of emulated controller index and Controller Type enum per player.
std::array<std::vector<std::pair<int, Settings::ControllerType>>, NUM_PLAYERS>
index_controller_type_pairs;
// Labels representing the number of connected controllers
// above the "Connected Controllers" checkboxes.
std::array<QLabel*, NUM_PLAYERS> connected_controller_labels;
// Checkboxes representing the "Connected Controllers".
std::array<QCheckBox*, NUM_PLAYERS> connected_controller_checkboxes;
};
class QtControllerSelector final : public QObject, public Core::Frontend::ControllerApplet {
Q_OBJECT
public:
explicit QtControllerSelector(GMainWindow& parent);
~QtControllerSelector() override;
void ReconfigureControllers(
std::function<void()> callback_,
const Core::Frontend::ControllerParameters& parameters) const override;
signals:
void MainWindowReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters) const;
private:
void MainWindowReconfigureFinished();
mutable std::function<void()> callback;
};
+2653
View File
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDateTime>
#include "core/hle/lock.h"
#include "yuzu/applets/error.h"
#include "yuzu/main.h"
QtErrorDisplay::QtErrorDisplay(GMainWindow& parent) {
connect(this, &QtErrorDisplay::MainWindowDisplayError, &parent,
&GMainWindow::ErrorDisplayDisplayError, Qt::QueuedConnection);
connect(&parent, &GMainWindow::ErrorDisplayFinished, this,
&QtErrorDisplay::MainWindowFinishedError, Qt::DirectConnection);
}
QtErrorDisplay::~QtErrorDisplay() = default;
void QtErrorDisplay::ShowError(ResultCode error, std::function<void()> finished) const {
callback = std::move(finished);
emit MainWindowDisplayError(
tr("An error has occured.\nPlease try again or contact the developer of the "
"software.\n\nError Code: %1-%2 (0x%3)")
.arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
.arg(error.raw, 8, 16, QChar::fromLatin1('0')));
}
void QtErrorDisplay::ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
std::function<void()> finished) const {
callback = std::move(finished);
const QDateTime date_time = QDateTime::fromSecsSinceEpoch(time.count());
emit MainWindowDisplayError(
tr("An error occured on %1 at %2.\nPlease try again or contact the "
"developer of the software.\n\nError Code: %3-%4 (0x%5)")
.arg(date_time.toString(QStringLiteral("dddd, MMMM d, yyyy")))
.arg(date_time.toString(QStringLiteral("h:mm:ss A")))
.arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
.arg(error.raw, 8, 16, QChar::fromLatin1('0')));
}
void QtErrorDisplay::ShowCustomErrorText(ResultCode error, std::string dialog_text,
std::string fullscreen_text,
std::function<void()> finished) const {
callback = std::move(finished);
emit MainWindowDisplayError(
tr("An error has occured.\nError Code: %1-%2 (0x%3)\n\n%4\n\n%5")
.arg(static_cast<u32>(error.module.Value()) + 2000, 4, 10, QChar::fromLatin1('0'))
.arg(error.description, 4, 10, QChar::fromLatin1('0'))
.arg(error.raw, 8, 16, QChar::fromLatin1('0'))
.arg(QString::fromStdString(dialog_text))
.arg(QString::fromStdString(fullscreen_text)));
}
void QtErrorDisplay::MainWindowFinishedError() {
// Acquire the HLE mutex
std::lock_guard lock{HLE::g_hle_lock};
callback();
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QObject>
#include "core/frontend/applets/error.h"
class GMainWindow;
class QtErrorDisplay final : public QObject, public Core::Frontend::ErrorApplet {
Q_OBJECT
public:
explicit QtErrorDisplay(GMainWindow& parent);
~QtErrorDisplay() override;
void ShowError(ResultCode error, std::function<void()> finished) const override;
void ShowErrorWithTimestamp(ResultCode error, std::chrono::seconds time,
std::function<void()> finished) const override;
void ShowCustomErrorText(ResultCode error, std::string dialog_text, std::string fullscreen_text,
std::function<void()> finished) const override;
signals:
void MainWindowDisplayError(QString error) const;
private:
void MainWindowFinishedError();
mutable std::function<void()> callback;
};
+162
View File
@@ -0,0 +1,162 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <mutex>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QScrollArea>
#include <QStandardItemModel>
#include <QVBoxLayout>
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/constants.h"
#include "core/hle/lock.h"
#include "yuzu/applets/profile_select.h"
#include "yuzu/main.h"
namespace {
QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
return QtProfileSelectionDialog::tr(
"%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. "
"00112233-4455-6677-8899-AABBCCDDEEFF))")
.arg(username, QString::fromStdString(uuid.FormatSwitch()));
}
QString GetImagePath(Common::UUID uuid) {
const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
QPixmap GetIcon(Common::UUID uuid) {
QPixmap icon{GetImagePath(uuid)};
if (!icon) {
icon.fill(Qt::black);
icon.loadFromData(Core::Constants::ACCOUNT_BACKUP_JPEG.data(),
static_cast<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size()));
}
return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
} // Anonymous namespace
QtProfileSelectionDialog::QtProfileSelectionDialog(QWidget* parent)
: QDialog(parent), profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
outer_layout = new QVBoxLayout;
instruction_label = new QLabel(tr("Select a user:"));
scroll_area = new QScrollArea;
buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
connect(buttons, &QDialogButtonBox::accepted, this, &QtProfileSelectionDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QtProfileSelectionDialog::reject);
outer_layout->addWidget(instruction_label);
outer_layout->addWidget(scroll_area);
outer_layout->addWidget(buttons);
layout = new QVBoxLayout;
tree_view = new QTreeView;
item_model = new QStandardItemModel(tree_view);
tree_view->setModel(item_model);
tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::SingleSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
tree_view->setIconSize({64, 64});
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
item_model->insertColumns(0, 1);
item_model->setHeaderData(0, Qt::Horizontal, tr("Users"));
// We must register all custom types with the Qt Automoc system so that we are able to use it
// with signals/slots. In this case, QList falls under the umbrella of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(tree_view);
scroll_area->setLayout(layout);
connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser);
const auto& profiles = profile_manager->GetAllUsers();
for (const auto& user : profiles) {
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(user, profile))
continue;
const auto username = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
list_items.push_back(QList<QStandardItem*>{new QStandardItem{
GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
}
for (const auto& item : list_items)
item_model->appendRow(item);
setLayout(outer_layout);
setWindowTitle(tr("Profile Selector"));
resize(550, 400);
}
QtProfileSelectionDialog::~QtProfileSelectionDialog() = default;
int QtProfileSelectionDialog::exec() {
// Skip profile selection when there's only one.
if (profile_manager->GetUserCount() == 1) {
user_index = 0;
return QDialog::Accepted;
}
return QDialog::exec();
}
void QtProfileSelectionDialog::accept() {
QDialog::accept();
}
void QtProfileSelectionDialog::reject() {
user_index = 0;
QDialog::reject();
}
int QtProfileSelectionDialog::GetIndex() const {
return user_index;
}
void QtProfileSelectionDialog::SelectUser(const QModelIndex& index) {
user_index = index.row();
}
QtProfileSelector::QtProfileSelector(GMainWindow& parent) {
connect(this, &QtProfileSelector::MainWindowSelectProfile, &parent,
&GMainWindow::ProfileSelectorSelectProfile, Qt::QueuedConnection);
connect(&parent, &GMainWindow::ProfileSelectorFinishedSelection, this,
&QtProfileSelector::MainWindowFinishedSelection, Qt::DirectConnection);
}
QtProfileSelector::~QtProfileSelector() = default;
void QtProfileSelector::SelectProfile(
std::function<void(std::optional<Common::UUID>)> callback_) const {
callback = std::move(callback_);
emit MainWindowSelectProfile();
}
void QtProfileSelector::MainWindowFinishedSelection(std::optional<Common::UUID> uuid) {
// Acquire the HLE mutex
std::lock_guard lock{HLE::g_hle_lock};
callback(uuid);
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include <QDialog>
#include <QList>
#include <QTreeView>
#include "core/frontend/applets/profile_select.h"
#include "core/hle/service/acc/profile_manager.h"
class GMainWindow;
class QDialogButtonBox;
class QGraphicsScene;
class QLabel;
class QScrollArea;
class QStandardItem;
class QStandardItemModel;
class QVBoxLayout;
class QtProfileSelectionDialog final : public QDialog {
Q_OBJECT
public:
explicit QtProfileSelectionDialog(QWidget* parent);
~QtProfileSelectionDialog() override;
int exec() override;
void accept() override;
void reject() override;
int GetIndex() const;
private:
void SelectUser(const QModelIndex& index);
int user_index = 0;
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
QGraphicsScene* scene;
std::vector<QList<QStandardItem*>> list_items;
QVBoxLayout* outer_layout;
QLabel* instruction_label;
QScrollArea* scroll_area;
QDialogButtonBox* buttons;
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};
class QtProfileSelector final : public QObject, public Core::Frontend::ProfileSelectApplet {
Q_OBJECT
public:
explicit QtProfileSelector(GMainWindow& parent);
~QtProfileSelector() override;
void SelectProfile(std::function<void(std::optional<Common::UUID>)> callback_) const override;
signals:
void MainWindowSelectProfile() const;
private:
void MainWindowFinishedSelection(std::optional<Common::UUID> uuid);
mutable std::function<void(std::optional<Common::UUID>)> callback;
};
+153
View File
@@ -0,0 +1,153 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <mutex>
#include <QDialogButtonBox>
#include <QFont>
#include <QLabel>
#include <QLineEdit>
#include <QVBoxLayout>
#include "core/hle/lock.h"
#include "yuzu/applets/software_keyboard.h"
#include "yuzu/main.h"
QtSoftwareKeyboardValidator::QtSoftwareKeyboardValidator(
Core::Frontend::SoftwareKeyboardParameters parameters)
: parameters(std::move(parameters)) {}
QValidator::State QtSoftwareKeyboardValidator::validate(QString& input, int& pos) const {
if (input.size() > static_cast<s64>(parameters.max_length)) {
return Invalid;
}
if (parameters.disable_space && input.contains(QLatin1Char{' '})) {
return Invalid;
}
if (parameters.disable_address && input.contains(QLatin1Char{'@'})) {
return Invalid;
}
if (parameters.disable_percent && input.contains(QLatin1Char{'%'})) {
return Invalid;
}
if (parameters.disable_slash &&
(input.contains(QLatin1Char{'/'}) || input.contains(QLatin1Char{'\\'}))) {
return Invalid;
}
if (parameters.disable_number &&
std::any_of(input.begin(), input.end(), [](QChar c) { return c.isDigit(); })) {
return Invalid;
}
if (parameters.disable_download_code && std::any_of(input.begin(), input.end(), [](QChar c) {
return c == QLatin1Char{'O'} || c == QLatin1Char{'I'};
})) {
return Invalid;
}
return Acceptable;
}
QtSoftwareKeyboardDialog::QtSoftwareKeyboardDialog(
QWidget* parent, Core::Frontend::SoftwareKeyboardParameters parameters_)
: QDialog(parent), parameters(std::move(parameters_)) {
layout = new QVBoxLayout;
header_label = new QLabel(QString::fromStdU16String(parameters.header_text));
header_label->setFont({header_label->font().family(), 11, QFont::Bold});
if (header_label->text().isEmpty())
header_label->setText(tr("Enter text:"));
sub_label = new QLabel(QString::fromStdU16String(parameters.sub_text));
sub_label->setFont({sub_label->font().family(), sub_label->font().pointSize(),
sub_label->font().weight(), true});
sub_label->setHidden(parameters.sub_text.empty());
guide_label = new QLabel(QString::fromStdU16String(parameters.guide_text));
guide_label->setHidden(parameters.guide_text.empty());
length_label = new QLabel(QStringLiteral("0/%1").arg(parameters.max_length));
length_label->setAlignment(Qt::AlignRight);
length_label->setFont({length_label->font().family(), 8});
line_edit = new QLineEdit;
line_edit->setValidator(new QtSoftwareKeyboardValidator(parameters));
line_edit->setMaxLength(static_cast<int>(parameters.max_length));
line_edit->setText(QString::fromStdU16String(parameters.initial_text));
line_edit->setCursorPosition(
parameters.cursor_at_beginning ? 0 : static_cast<int>(parameters.initial_text.size()));
line_edit->setEchoMode(parameters.password ? QLineEdit::Password : QLineEdit::Normal);
connect(line_edit, &QLineEdit::textChanged, this, [this](const QString& text) {
length_label->setText(QStringLiteral("%1/%2").arg(text.size()).arg(parameters.max_length));
});
buttons = new QDialogButtonBox(QDialogButtonBox::Cancel);
if (parameters.submit_text.empty()) {
buttons->addButton(QDialogButtonBox::Ok);
} else {
buttons->addButton(QString::fromStdU16String(parameters.submit_text),
QDialogButtonBox::AcceptRole);
}
connect(buttons, &QDialogButtonBox::accepted, this, &QtSoftwareKeyboardDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QtSoftwareKeyboardDialog::reject);
layout->addWidget(header_label);
layout->addWidget(sub_label);
layout->addWidget(guide_label);
layout->addWidget(length_label);
layout->addWidget(line_edit);
layout->addWidget(buttons);
setLayout(layout);
setWindowTitle(tr("Software Keyboard"));
}
QtSoftwareKeyboardDialog::~QtSoftwareKeyboardDialog() = default;
void QtSoftwareKeyboardDialog::accept() {
text = line_edit->text().toStdU16String();
QDialog::accept();
}
void QtSoftwareKeyboardDialog::reject() {
text.clear();
QDialog::reject();
}
std::u16string QtSoftwareKeyboardDialog::GetText() const {
return text;
}
QtSoftwareKeyboard::QtSoftwareKeyboard(GMainWindow& main_window) {
connect(this, &QtSoftwareKeyboard::MainWindowGetText, &main_window,
&GMainWindow::SoftwareKeyboardGetText, Qt::QueuedConnection);
connect(this, &QtSoftwareKeyboard::MainWindowTextCheckDialog, &main_window,
&GMainWindow::SoftwareKeyboardInvokeCheckDialog, Qt::BlockingQueuedConnection);
connect(&main_window, &GMainWindow::SoftwareKeyboardFinishedText, this,
&QtSoftwareKeyboard::MainWindowFinishedText, Qt::QueuedConnection);
}
QtSoftwareKeyboard::~QtSoftwareKeyboard() = default;
void QtSoftwareKeyboard::RequestText(std::function<void(std::optional<std::u16string>)> out,
Core::Frontend::SoftwareKeyboardParameters parameters) const {
text_output = std::move(out);
emit MainWindowGetText(parameters);
}
void QtSoftwareKeyboard::SendTextCheckDialog(std::u16string error_message,
std::function<void()> finished_check_) const {
finished_check = std::move(finished_check_);
emit MainWindowTextCheckDialog(error_message);
}
void QtSoftwareKeyboard::MainWindowFinishedText(std::optional<std::u16string> text) {
// Acquire the HLE mutex
std::lock_guard lock{HLE::g_hle_lock};
text_output(std::move(text));
}
void QtSoftwareKeyboard::MainWindowFinishedCheckDialog() {
// Acquire the HLE mutex
std::lock_guard lock{HLE::g_hle_lock};
finished_check();
}
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
#include <QValidator>
#include "core/frontend/applets/software_keyboard.h"
class GMainWindow;
class QDialogButtonBox;
class QLabel;
class QLineEdit;
class QVBoxLayout;
class QtSoftwareKeyboard;
class QtSoftwareKeyboardValidator final : public QValidator {
public:
explicit QtSoftwareKeyboardValidator(Core::Frontend::SoftwareKeyboardParameters parameters);
State validate(QString& input, int& pos) const override;
private:
Core::Frontend::SoftwareKeyboardParameters parameters;
};
class QtSoftwareKeyboardDialog final : public QDialog {
Q_OBJECT
public:
QtSoftwareKeyboardDialog(QWidget* parent,
Core::Frontend::SoftwareKeyboardParameters parameters);
~QtSoftwareKeyboardDialog() override;
void accept() override;
void reject() override;
std::u16string GetText() const;
private:
std::u16string text;
QDialogButtonBox* buttons;
QLabel* header_label;
QLabel* sub_label;
QLabel* guide_label;
QLabel* length_label;
QLineEdit* line_edit;
QVBoxLayout* layout;
Core::Frontend::SoftwareKeyboardParameters parameters;
};
class QtSoftwareKeyboard final : public QObject, public Core::Frontend::SoftwareKeyboardApplet {
Q_OBJECT
public:
explicit QtSoftwareKeyboard(GMainWindow& parent);
~QtSoftwareKeyboard() override;
void RequestText(std::function<void(std::optional<std::u16string>)> out,
Core::Frontend::SoftwareKeyboardParameters parameters) const override;
void SendTextCheckDialog(std::u16string error_message,
std::function<void()> finished_check_) const override;
signals:
void MainWindowGetText(Core::Frontend::SoftwareKeyboardParameters parameters) const;
void MainWindowTextCheckDialog(std::u16string error_message) const;
private:
void MainWindowFinishedText(std::optional<std::u16string> text);
void MainWindowFinishedCheckDialog();
mutable std::function<void(std::optional<std::u16string>)> text_output;
mutable std::function<void()> finished_check;
};
+414
View File
@@ -0,0 +1,414 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#ifdef YUZU_USE_QT_WEB_ENGINE
#include <QKeyEvent>
#include <QWebEngineProfile>
#include <QWebEngineScript>
#include <QWebEngineScriptCollection>
#include <QWebEngineSettings>
#include <QWebEngineUrlScheme>
#endif
#include "common/file_util.h"
#include "core/core.h"
#include "core/frontend/input_interpreter.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "yuzu/applets/web_browser.h"
#include "yuzu/applets/web_browser_scripts.h"
#include "yuzu/main.h"
#include "yuzu/util/url_request_interceptor.h"
#ifdef YUZU_USE_QT_WEB_ENGINE
namespace {
constexpr int HIDButtonToKey(HIDButton button) {
switch (button) {
case HIDButton::DLeft:
case HIDButton::LStickLeft:
return Qt::Key_Left;
case HIDButton::DUp:
case HIDButton::LStickUp:
return Qt::Key_Up;
case HIDButton::DRight:
case HIDButton::LStickRight:
return Qt::Key_Right;
case HIDButton::DDown:
case HIDButton::LStickDown:
return Qt::Key_Down;
default:
return 0;
}
}
} // Anonymous namespace
QtNXWebEngineView::QtNXWebEngineView(QWidget* parent, Core::System& system,
InputCommon::InputSubsystem* input_subsystem_)
: QWebEngineView(parent), input_subsystem{input_subsystem_},
url_interceptor(std::make_unique<UrlRequestInterceptor>()),
input_interpreter(std::make_unique<InputInterpreter>(system)),
default_profile{QWebEngineProfile::defaultProfile()},
global_settings{QWebEngineSettings::globalSettings()} {
QWebEngineScript gamepad;
QWebEngineScript window_nx;
gamepad.setName(QStringLiteral("gamepad_script.js"));
window_nx.setName(QStringLiteral("window_nx_script.js"));
gamepad.setSourceCode(QString::fromStdString(GAMEPAD_SCRIPT));
window_nx.setSourceCode(QString::fromStdString(WINDOW_NX_SCRIPT));
gamepad.setInjectionPoint(QWebEngineScript::DocumentCreation);
window_nx.setInjectionPoint(QWebEngineScript::DocumentCreation);
gamepad.setWorldId(QWebEngineScript::MainWorld);
window_nx.setWorldId(QWebEngineScript::MainWorld);
gamepad.setRunsOnSubFrames(true);
window_nx.setRunsOnSubFrames(true);
default_profile->scripts()->insert(gamepad);
default_profile->scripts()->insert(window_nx);
default_profile->setRequestInterceptor(url_interceptor.get());
global_settings->setAttribute(QWebEngineSettings::LocalContentCanAccessRemoteUrls, true);
global_settings->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);
global_settings->setAttribute(QWebEngineSettings::AllowRunningInsecureContent, true);
global_settings->setAttribute(QWebEngineSettings::FocusOnNavigationEnabled, true);
global_settings->setAttribute(QWebEngineSettings::AllowWindowActivationFromJavaScript, true);
global_settings->setAttribute(QWebEngineSettings::ShowScrollBars, false);
global_settings->setFontFamily(QWebEngineSettings::StandardFont, QStringLiteral("Roboto"));
connect(
page(), &QWebEnginePage::windowCloseRequested, page(),
[this] {
if (page()->url() == url_interceptor->GetRequestedURL()) {
SetFinished(true);
SetExitReason(Service::AM::Applets::WebExitReason::WindowClosed);
}
},
Qt::QueuedConnection);
}
QtNXWebEngineView::~QtNXWebEngineView() {
SetFinished(true);
StopInputThread();
}
void QtNXWebEngineView::LoadLocalWebPage(std::string_view main_url,
std::string_view additional_args) {
is_local = true;
LoadExtractedFonts();
SetUserAgent(UserAgent::WebApplet);
SetFinished(false);
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
SetLastURL("http://localhost/");
StartInputThread();
load(QUrl(QUrl::fromLocalFile(QString::fromStdString(std::string(main_url))).toString() +
QString::fromStdString(std::string(additional_args))));
}
void QtNXWebEngineView::LoadExternalWebPage(std::string_view main_url,
std::string_view additional_args) {
is_local = false;
SetUserAgent(UserAgent::WebApplet);
SetFinished(false);
SetExitReason(Service::AM::Applets::WebExitReason::EndButtonPressed);
SetLastURL("http://localhost/");
StartInputThread();
load(QUrl(QString::fromStdString(std::string(main_url)) +
QString::fromStdString(std::string(additional_args))));
}
void QtNXWebEngineView::SetUserAgent(UserAgent user_agent) {
const QString user_agent_str = [user_agent] {
switch (user_agent) {
case UserAgent::WebApplet:
default:
return QStringLiteral("WebApplet");
case UserAgent::ShopN:
return QStringLiteral("ShopN");
case UserAgent::LoginApplet:
return QStringLiteral("LoginApplet");
case UserAgent::ShareApplet:
return QStringLiteral("ShareApplet");
case UserAgent::LobbyApplet:
return QStringLiteral("LobbyApplet");
case UserAgent::WifiWebAuthApplet:
return QStringLiteral("WifiWebAuthApplet");
}
}();
QWebEngineProfile::defaultProfile()->setHttpUserAgent(
QStringLiteral("Mozilla/5.0 (Nintendo Switch; %1) AppleWebKit/606.4 "
"(KHTML, like Gecko) NF/6.0.1.15.4 NintendoBrowser/5.1.0.20389")
.arg(user_agent_str));
}
bool QtNXWebEngineView::IsFinished() const {
return finished;
}
void QtNXWebEngineView::SetFinished(bool finished_) {
finished = finished_;
}
Service::AM::Applets::WebExitReason QtNXWebEngineView::GetExitReason() const {
return exit_reason;
}
void QtNXWebEngineView::SetExitReason(Service::AM::Applets::WebExitReason exit_reason_) {
exit_reason = exit_reason_;
}
const std::string& QtNXWebEngineView::GetLastURL() const {
return last_url;
}
void QtNXWebEngineView::SetLastURL(std::string last_url_) {
last_url = std::move(last_url_);
}
QString QtNXWebEngineView::GetCurrentURL() const {
return url_interceptor->GetRequestedURL().toString();
}
void QtNXWebEngineView::hide() {
SetFinished(true);
StopInputThread();
QWidget::hide();
}
void QtNXWebEngineView::keyPressEvent(QKeyEvent* event) {
if (is_local) {
input_subsystem->GetKeyboard()->PressKey(event->key());
}
}
void QtNXWebEngineView::keyReleaseEvent(QKeyEvent* event) {
if (is_local) {
input_subsystem->GetKeyboard()->ReleaseKey(event->key());
}
}
template <HIDButton... T>
void QtNXWebEngineView::HandleWindowFooterButtonPressedOnce() {
const auto f = [this](HIDButton button) {
if (input_interpreter->IsButtonPressedOnce(button)) {
page()->runJavaScript(
QStringLiteral("yuzu_key_callbacks[%1] == null;").arg(static_cast<u8>(button)),
[&](const QVariant& variant) {
if (variant.toBool()) {
switch (button) {
case HIDButton::A:
SendMultipleKeyPressEvents<Qt::Key_A, Qt::Key_Space, Qt::Key_Return>();
break;
case HIDButton::B:
SendKeyPressEvent(Qt::Key_B);
break;
case HIDButton::X:
SendKeyPressEvent(Qt::Key_X);
break;
case HIDButton::Y:
SendKeyPressEvent(Qt::Key_Y);
break;
default:
break;
}
}
});
page()->runJavaScript(
QStringLiteral("if (yuzu_key_callbacks[%1] != null) { yuzu_key_callbacks[%1](); }")
.arg(static_cast<u8>(button)));
}
};
(f(T), ...);
}
template <HIDButton... T>
void QtNXWebEngineView::HandleWindowKeyButtonPressedOnce() {
const auto f = [this](HIDButton button) {
if (input_interpreter->IsButtonPressedOnce(button)) {
SendKeyPressEvent(HIDButtonToKey(button));
}
};
(f(T), ...);
}
template <HIDButton... T>
void QtNXWebEngineView::HandleWindowKeyButtonHold() {
const auto f = [this](HIDButton button) {
if (input_interpreter->IsButtonHeld(button)) {
SendKeyPressEvent(HIDButtonToKey(button));
}
};
(f(T), ...);
}
void QtNXWebEngineView::SendKeyPressEvent(int key) {
if (key == 0) {
return;
}
QCoreApplication::postEvent(focusProxy(),
new QKeyEvent(QKeyEvent::KeyPress, key, Qt::NoModifier));
QCoreApplication::postEvent(focusProxy(),
new QKeyEvent(QKeyEvent::KeyRelease, key, Qt::NoModifier));
}
void QtNXWebEngineView::StartInputThread() {
if (input_thread_running) {
return;
}
input_thread_running = true;
input_thread = std::thread(&QtNXWebEngineView::InputThread, this);
}
void QtNXWebEngineView::StopInputThread() {
if (is_local) {
QWidget::releaseKeyboard();
}
input_thread_running = false;
if (input_thread.joinable()) {
input_thread.join();
}
}
void QtNXWebEngineView::InputThread() {
// Wait for 1 second before allowing any inputs to be processed.
std::this_thread::sleep_for(std::chrono::seconds(1));
if (is_local) {
QWidget::grabKeyboard();
}
while (input_thread_running) {
input_interpreter->PollInput();
HandleWindowFooterButtonPressedOnce<HIDButton::A, HIDButton::B, HIDButton::X, HIDButton::Y,
HIDButton::L, HIDButton::R>();
HandleWindowKeyButtonPressedOnce<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
HIDButton::DDown, HIDButton::LStickLeft,
HIDButton::LStickUp, HIDButton::LStickRight,
HIDButton::LStickDown>();
HandleWindowKeyButtonHold<HIDButton::DLeft, HIDButton::DUp, HIDButton::DRight,
HIDButton::DDown, HIDButton::LStickLeft, HIDButton::LStickUp,
HIDButton::LStickRight, HIDButton::LStickDown>();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void QtNXWebEngineView::LoadExtractedFonts() {
QWebEngineScript nx_font_css;
QWebEngineScript load_nx_font;
const QString fonts_dir = QString::fromStdString(Common::FS::SanitizePath(
fmt::format("{}/fonts", Common::FS::GetUserPath(Common::FS::UserPath::CacheDir))));
nx_font_css.setName(QStringLiteral("nx_font_css.js"));
load_nx_font.setName(QStringLiteral("load_nx_font.js"));
nx_font_css.setSourceCode(
QString::fromStdString(NX_FONT_CSS)
.arg(fonts_dir + QStringLiteral("/FontStandard.ttf"))
.arg(fonts_dir + QStringLiteral("/FontChineseSimplified.ttf"))
.arg(fonts_dir + QStringLiteral("/FontExtendedChineseSimplified.ttf"))
.arg(fonts_dir + QStringLiteral("/FontChineseTraditional.ttf"))
.arg(fonts_dir + QStringLiteral("/FontKorean.ttf"))
.arg(fonts_dir + QStringLiteral("/FontNintendoExtended.ttf"))
.arg(fonts_dir + QStringLiteral("/FontNintendoExtended2.ttf")));
load_nx_font.setSourceCode(QString::fromStdString(LOAD_NX_FONT));
nx_font_css.setInjectionPoint(QWebEngineScript::DocumentReady);
load_nx_font.setInjectionPoint(QWebEngineScript::Deferred);
nx_font_css.setWorldId(QWebEngineScript::MainWorld);
load_nx_font.setWorldId(QWebEngineScript::MainWorld);
nx_font_css.setRunsOnSubFrames(true);
load_nx_font.setRunsOnSubFrames(true);
default_profile->scripts()->insert(nx_font_css);
default_profile->scripts()->insert(load_nx_font);
connect(
url_interceptor.get(), &UrlRequestInterceptor::FrameChanged, url_interceptor.get(),
[this] {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
page()->runJavaScript(QString::fromStdString(LOAD_NX_FONT));
},
Qt::QueuedConnection);
}
#endif
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) {
connect(this, &QtWebBrowser::MainWindowOpenWebPage, &main_window,
&GMainWindow::WebBrowserOpenWebPage, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserExtractOfflineRomFS, this,
&QtWebBrowser::MainWindowExtractOfflineRomFS, Qt::QueuedConnection);
connect(&main_window, &GMainWindow::WebBrowserClosed, this,
&QtWebBrowser::MainWindowWebBrowserClosed, Qt::QueuedConnection);
}
QtWebBrowser::~QtWebBrowser() = default;
void QtWebBrowser::OpenLocalWebPage(
std::string_view local_url, std::function<void()> extract_romfs_callback_,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
extract_romfs_callback = std::move(extract_romfs_callback_);
callback = std::move(callback_);
const auto index = local_url.find('?');
if (index == std::string::npos) {
emit MainWindowOpenWebPage(local_url, "", true);
} else {
emit MainWindowOpenWebPage(local_url.substr(0, index), local_url.substr(index), true);
}
}
void QtWebBrowser::OpenExternalWebPage(
std::string_view external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback_) const {
callback = std::move(callback_);
const auto index = external_url.find('?');
if (index == std::string::npos) {
emit MainWindowOpenWebPage(external_url, "", false);
} else {
emit MainWindowOpenWebPage(external_url.substr(0, index), external_url.substr(index),
false);
}
}
void QtWebBrowser::MainWindowExtractOfflineRomFS() {
extract_romfs_callback();
}
void QtWebBrowser::MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
std::string last_url) {
callback(exit_reason, last_url);
}
+217
View File
@@ -0,0 +1,217 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <memory>
#include <thread>
#include <QObject>
#ifdef YUZU_USE_QT_WEB_ENGINE
#include <QWebEngineView>
#endif
#include "core/frontend/applets/web_browser.h"
enum class HIDButton : u8;
class GMainWindow;
class InputInterpreter;
class UrlRequestInterceptor;
namespace Core {
class System;
}
namespace InputCommon {
class InputSubsystem;
}
#ifdef YUZU_USE_QT_WEB_ENGINE
enum class UserAgent {
WebApplet,
ShopN,
LoginApplet,
ShareApplet,
LobbyApplet,
WifiWebAuthApplet,
};
class QWebEngineProfile;
class QWebEngineSettings;
class QtNXWebEngineView : public QWebEngineView {
Q_OBJECT
public:
explicit QtNXWebEngineView(QWidget* parent, Core::System& system,
InputCommon::InputSubsystem* input_subsystem_);
~QtNXWebEngineView() override;
/**
* Loads a HTML document that exists locally. Cannot be used to load external websites.
*
* @param main_url The url to the file.
* @param additional_args Additional arguments appended to the main url.
*/
void LoadLocalWebPage(std::string_view main_url, std::string_view additional_args);
/**
* Loads an external website. Cannot be used to load local urls.
*
* @param main_url The url to the website.
* @param additional_args Additional arguments appended to the main url.
*/
void LoadExternalWebPage(std::string_view main_url, std::string_view additional_args);
/**
* Sets the background color of the web page.
*
* @param color The color to set.
*/
void SetBackgroundColor(QColor color);
/**
* Sets the user agent of the web browser.
*
* @param user_agent The user agent enum.
*/
void SetUserAgent(UserAgent user_agent);
[[nodiscard]] bool IsFinished() const;
void SetFinished(bool finished_);
[[nodiscard]] Service::AM::Applets::WebExitReason GetExitReason() const;
void SetExitReason(Service::AM::Applets::WebExitReason exit_reason_);
[[nodiscard]] const std::string& GetLastURL() const;
void SetLastURL(std::string last_url_);
/**
* This gets the current URL that has been requested by the webpage.
* This only applies to the main frame. Sub frames and other resources are ignored.
*
* @return Currently requested URL
*/
[[nodiscard]] QString GetCurrentURL() const;
public slots:
void hide();
protected:
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
private:
/**
* Handles button presses to execute functions assigned in yuzu_key_callbacks.
* yuzu_key_callbacks contains specialized functions for the buttons in the window footer
* that can be overriden by games to achieve desired functionality.
*
* @tparam HIDButton The list of buttons contained in yuzu_key_callbacks
*/
template <HIDButton... T>
void HandleWindowFooterButtonPressedOnce();
/**
* Handles button presses and converts them into keyboard input.
* This should only be used to convert D-Pad or Analog Stick input into arrow keys.
*
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
*/
template <HIDButton... T>
void HandleWindowKeyButtonPressedOnce();
/**
* Handles button holds and converts them into keyboard input.
* This should only be used to convert D-Pad or Analog Stick input into arrow keys.
*
* @tparam HIDButton The list of buttons that can be converted into keyboard input.
*/
template <HIDButton... T>
void HandleWindowKeyButtonHold();
/**
* Sends a key press event to QWebEngineView.
*
* @param key Qt key code.
*/
void SendKeyPressEvent(int key);
/**
* Sends multiple key press events to QWebEngineView.
*
* @tparam int Qt key code.
*/
template <int... T>
void SendMultipleKeyPressEvents() {
(SendKeyPressEvent(T), ...);
}
void StartInputThread();
void StopInputThread();
/// The thread where input is being polled and processed.
void InputThread();
/// Loads the extracted fonts using JavaScript.
void LoadExtractedFonts();
InputCommon::InputSubsystem* input_subsystem;
std::unique_ptr<UrlRequestInterceptor> url_interceptor;
std::unique_ptr<InputInterpreter> input_interpreter;
std::thread input_thread;
std::atomic<bool> input_thread_running{};
std::atomic<bool> finished{};
Service::AM::Applets::WebExitReason exit_reason{
Service::AM::Applets::WebExitReason::EndButtonPressed};
std::string last_url{"http://localhost/"};
bool is_local{};
QWebEngineProfile* default_profile;
QWebEngineSettings* global_settings;
};
#endif
class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet {
Q_OBJECT
public:
explicit QtWebBrowser(GMainWindow& parent);
~QtWebBrowser() override;
void OpenLocalWebPage(std::string_view local_url, std::function<void()> extract_romfs_callback_,
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
callback_) const override;
void OpenExternalWebPage(std::string_view external_url,
std::function<void(Service::AM::Applets::WebExitReason, std::string)>
callback_) const override;
signals:
void MainWindowOpenWebPage(std::string_view main_url, std::string_view additional_args,
bool is_local) const;
private:
void MainWindowExtractOfflineRomFS();
void MainWindowWebBrowserClosed(Service::AM::Applets::WebExitReason exit_reason,
std::string last_url);
mutable std::function<void()> extract_romfs_callback;
mutable std::function<void(Service::AM::Applets::WebExitReason, std::string)> callback;
};
+193
View File
@@ -0,0 +1,193 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
constexpr char NX_FONT_CSS[] = R"(
(function() {
css = document.createElement('style');
css.type = 'text/css';
css.id = 'nx_font';
css.innerText = `
/* FontStandard */
@font-face {
font-family: 'FontStandard';
src: url('%1') format('truetype');
}
/* FontChineseSimplified */
@font-face {
font-family: 'FontChineseSimplified';
src: url('%2') format('truetype');
}
/* FontExtendedChineseSimplified */
@font-face {
font-family: 'FontExtendedChineseSimplified';
src: url('%3') format('truetype');
}
/* FontChineseTraditional */
@font-face {
font-family: 'FontChineseTraditional';
src: url('%4') format('truetype');
}
/* FontKorean */
@font-face {
font-family: 'FontKorean';
src: url('%5') format('truetype');
}
/* FontNintendoExtended */
@font-face {
font-family: 'NintendoExt003';
src: url('%6') format('truetype');
}
/* FontNintendoExtended2 */
@font-face {
font-family: 'NintendoExt003';
src: url('%7') format('truetype');
}
`;
document.head.appendChild(css);
})();
)";
constexpr char LOAD_NX_FONT[] = R"(
(function() {
var elements = document.querySelectorAll("*");
for (var i = 0; i < elements.length; i++) {
var style = window.getComputedStyle(elements[i], null);
if (style.fontFamily.includes("Arial") || style.fontFamily.includes("Calibri") ||
style.fontFamily.includes("Century") || style.fontFamily.includes("Times New Roman")) {
elements[i].style.fontFamily = "FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003";
} else {
elements[i].style.fontFamily = style.fontFamily + ", FontStandard, FontChineseSimplified, FontExtendedChineseSimplified, FontChineseTraditional, FontKorean, NintendoExt003";
}
}
})();
)";
constexpr char GAMEPAD_SCRIPT[] = R"(
window.addEventListener("gamepadconnected", function(e) {
console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
e.gamepad.index, e.gamepad.id, e.gamepad.buttons.length, e.gamepad.axes.length);
});
window.addEventListener("gamepaddisconnected", function(e) {
console.log("Gamepad disconnected from index %d: %s", e.gamepad.index, e.gamepad.id);
});
)";
constexpr char WINDOW_NX_SCRIPT[] = R"(
var end_applet = false;
var yuzu_key_callbacks = [];
(function() {
class WindowNX {
constructor() {
yuzu_key_callbacks[1] = function() { window.history.back(); };
yuzu_key_callbacks[2] = function() { window.nx.endApplet(); };
}
addEventListener(type, listener, options) {
console.log("nx.addEventListener called, type=%s", type);
window.addEventListener(type, listener, options);
}
endApplet() {
console.log("nx.endApplet called");
end_applet = true;
}
playSystemSe(system_se) {
console.log("nx.playSystemSe is not implemented, system_se=%s", system_se);
}
sendMessage(message) {
console.log("nx.sendMessage is not implemented, message=%s", message);
}
setCursorScrollSpeed(scroll_speed) {
console.log("nx.setCursorScrollSpeed is not implemented, scroll_speed=%d", scroll_speed);
}
}
class WindowNXFooter {
setAssign(key, label, func, option) {
console.log("nx.footer.setAssign called, key=%s", key);
switch (key) {
case "A":
yuzu_key_callbacks[0] = func;
break;
case "B":
yuzu_key_callbacks[1] = func;
break;
case "X":
yuzu_key_callbacks[2] = func;
break;
case "Y":
yuzu_key_callbacks[3] = func;
break;
case "L":
yuzu_key_callbacks[6] = func;
break;
case "R":
yuzu_key_callbacks[7] = func;
break;
}
}
setFixed(kind) {
console.log("nx.footer.setFixed is not implemented, kind=%s", kind);
}
unsetAssign(key) {
console.log("nx.footer.unsetAssign called, key=%s", key);
switch (key) {
case "A":
yuzu_key_callbacks[0] = function() {};
break;
case "B":
yuzu_key_callbacks[1] = function() {};
break;
case "X":
yuzu_key_callbacks[2] = function() {};
break;
case "Y":
yuzu_key_callbacks[3] = function() {};
break;
case "L":
yuzu_key_callbacks[6] = function() {};
break;
case "R":
yuzu_key_callbacks[7] = function() {};
break;
}
}
}
class WindowNXPlayReport {
incrementCounter(counter_id) {
console.log("nx.playReport.incrementCounter is not implemented, counter_id=%d", counter_id);
}
setCounterSetIdentifier(counter_id) {
console.log("nx.playReport.setCounterSetIdentifier is not implemented, counter_id=%d", counter_id);
}
}
window.nx = new WindowNX();
window.nx.footer = new WindowNXFooter();
window.nx.playReport = new WindowNXPlayReport();
})();
)";
+690
View File
@@ -0,0 +1,690 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <glad/glad.h>
#include <QApplication>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QMessageBox>
#include <QPainter>
#include <QScreen>
#include <QString>
#include <QStringList>
#include <QWindow>
#ifdef HAS_OPENGL
#include <QOffscreenSurface>
#include <QOpenGLContext>
#endif
#if !defined(WIN32)
#include <qpa/qplatformnativeinterface.h>
#endif
#include <fmt/format.h>
#include "common/assert.h"
#include "common/microprofile.h"
#include "common/scm_rev.h"
#include "common/scope_exit.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/hle/kernel/process.h"
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/mouse/mouse_input.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
#include "yuzu/bootmanager.h"
#include "yuzu/main.h"
EmuThread::EmuThread() = default;
EmuThread::~EmuThread() = default;
void EmuThread::run() {
std::string name = "yuzu:EmuControlThread";
MicroProfileOnThreadCreate(name.c_str());
Common::SetCurrentThreadName(name.c_str());
auto& system = Core::System::GetInstance();
system.RegisterHostThread();
auto& gpu = system.GPU();
// Main process has been loaded. Make the context current to this thread and begin GPU and CPU
// execution.
gpu.Start();
gpu.ObtainContext();
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
system.Renderer().Rasterizer().LoadDiskResources(
system.CurrentProcess()->GetTitleID(), stop_run,
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
gpu.ReleaseContext();
// Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the
// next execution step
bool was_active = false;
while (!stop_run) {
if (running) {
if (was_active) {
emit DebugModeLeft();
}
running_guard = true;
Core::System::ResultStatus result = system.Run();
if (result != Core::System::ResultStatus::Success) {
running_guard = false;
this->SetRunning(false);
emit ErrorThrown(result, system.GetStatusDetails());
}
running_wait.Wait();
result = system.Pause();
if (result != Core::System::ResultStatus::Success) {
running_guard = false;
this->SetRunning(false);
emit ErrorThrown(result, system.GetStatusDetails());
}
running_guard = false;
if (!stop_run) {
was_active = true;
emit DebugModeEntered();
}
} else if (exec_step) {
UNIMPLEMENTED();
} else {
std::unique_lock lock{running_mutex};
running_cv.wait(lock, [this] { return IsRunning() || exec_step || stop_run; });
}
}
// Shutdown the core emulation
system.Shutdown();
#if MICROPROFILE_ENABLED
MicroProfileOnThreadExit();
#endif
}
#ifdef HAS_OPENGL
class OpenGLSharedContext : public Core::Frontend::GraphicsContext {
public:
/// Create the original context that should be shared from
explicit OpenGLSharedContext(QSurface* surface) : surface(surface) {
QSurfaceFormat format;
format.setVersion(4, 3);
format.setProfile(QSurfaceFormat::CompatibilityProfile);
format.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions);
if (Settings::values.renderer_debug) {
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
}
// TODO: expose a setting for buffer value (ie default/single/double/triple)
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
format.setSwapInterval(0);
context = std::make_unique<QOpenGLContext>();
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create main openGL context");
}
}
/// Create the shared contexts for rendering and presentation
explicit OpenGLSharedContext(QOpenGLContext* share_context, QSurface* main_surface = nullptr) {
// disable vsync for any shared contexts
auto format = share_context->format();
format.setSwapInterval(main_surface ? Settings::values.use_vsync.GetValue() : 0);
context = std::make_unique<QOpenGLContext>();
context->setShareContext(share_context);
context->setFormat(format);
if (!context->create()) {
LOG_ERROR(Frontend, "Unable to create shared openGL context");
}
if (!main_surface) {
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
offscreen_surface->setFormat(format);
offscreen_surface->create();
surface = offscreen_surface.get();
} else {
surface = main_surface;
}
}
~OpenGLSharedContext() {
DoneCurrent();
}
void SwapBuffers() override {
context->swapBuffers(surface);
}
void MakeCurrent() override {
// We can't track the current state of the underlying context in this wrapper class because
// Qt may make the underlying context not current for one reason or another. In particular,
// the WebBrowser uses GL, so it seems to conflict if we aren't careful.
// Instead of always just making the context current (which does not have any caching to
// check if the underlying context is already current) we can check for the current context
// in the thread local data by calling `currentContext()` and checking if its ours.
if (QOpenGLContext::currentContext() != context.get()) {
context->makeCurrent(surface);
}
}
void DoneCurrent() override {
context->doneCurrent();
}
QOpenGLContext* GetShareContext() {
return context.get();
}
const QOpenGLContext* GetShareContext() const {
return context.get();
}
private:
// Avoid using Qt parent system here since we might move the QObjects to new threads
// As a note, this means we should avoid using slots/signals with the objects too
std::unique_ptr<QOpenGLContext> context;
std::unique_ptr<QOffscreenSurface> offscreen_surface{};
QSurface* surface;
};
#endif
class DummyContext : public Core::Frontend::GraphicsContext {};
class RenderWidget : public QWidget {
public:
explicit RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) {
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
}
virtual ~RenderWidget() = default;
QPaintEngine* paintEngine() const override {
return nullptr;
}
private:
GRenderWindow* render_window;
};
class OpenGLRenderWidget : public RenderWidget {
public:
explicit OpenGLRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
windowHandle()->setSurfaceType(QWindow::OpenGLSurface);
}
void SetContext(std::unique_ptr<Core::Frontend::GraphicsContext>&& context_) {
context = std::move(context_);
}
private:
std::unique_ptr<Core::Frontend::GraphicsContext> context;
};
class VulkanRenderWidget : public RenderWidget {
public:
explicit VulkanRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {
windowHandle()->setSurfaceType(QWindow::VulkanSurface);
}
};
static Core::Frontend::WindowSystemType GetWindowSystemType() {
// Determine WSI type based on Qt platform.
QString platform_name = QGuiApplication::platformName();
if (platform_name == QStringLiteral("windows"))
return Core::Frontend::WindowSystemType::Windows;
else if (platform_name == QStringLiteral("xcb"))
return Core::Frontend::WindowSystemType::X11;
else if (platform_name == QStringLiteral("wayland"))
return Core::Frontend::WindowSystemType::Wayland;
LOG_CRITICAL(Frontend, "Unknown Qt platform!");
return Core::Frontend::WindowSystemType::Windows;
}
static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
Core::Frontend::EmuWindow::WindowSystemInfo wsi;
wsi.type = GetWindowSystemType();
// Our Win32 Qt external doesn't have the private API.
#if defined(WIN32) || defined(__APPLE__)
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
#else
QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
wsi.display_connection = pni->nativeResourceForWindow("display", window);
if (wsi.type == Core::Frontend::WindowSystemType::Wayland)
wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
else
wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
#endif
wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
return wsi;
}
GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_)
: QWidget(parent), emu_thread(emu_thread_), input_subsystem{std::move(input_subsystem_)} {
setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
.arg(QString::fromUtf8(Common::g_build_name),
QString::fromUtf8(Common::g_scm_branch),
QString::fromUtf8(Common::g_scm_desc)));
setAttribute(Qt::WA_AcceptTouchEvents);
auto layout = new QHBoxLayout(this);
layout->setMargin(0);
setLayout(layout);
input_subsystem->Initialize();
this->setMouseTracking(true);
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram,
Qt::QueuedConnection);
}
void GRenderWindow::ExecuteProgram(std::size_t program_index) {
emit ExecuteProgramSignal(program_index);
}
GRenderWindow::~GRenderWindow() {
input_subsystem->Shutdown();
}
void GRenderWindow::OnFrameDisplayed() {
if (!first_frame) {
first_frame = true;
emit FirstFrameDisplayed();
}
}
bool GRenderWindow::IsShown() const {
return !isMinimized();
}
// On Qt 5.0+, this correctly gets the size of the framebuffer (pixels).
//
// Older versions get the window size (density independent pixels),
// and hence, do not support DPI scaling ("retina" displays).
// The result will be a viewport that is smaller than the extent of the window.
void GRenderWindow::OnFramebufferSizeChanged() {
// Screen changes potentially incur a change in screen DPI, hence we should update the
// framebuffer size
const qreal pixel_ratio = windowPixelRatio();
const u32 width = this->width() * pixel_ratio;
const u32 height = this->height() * pixel_ratio;
UpdateCurrentFramebufferLayout(width, height);
}
void GRenderWindow::BackupGeometry() {
geometry = QWidget::saveGeometry();
}
void GRenderWindow::RestoreGeometry() {
// We don't want to back up the geometry here (obviously)
QWidget::restoreGeometry(geometry);
}
void GRenderWindow::restoreGeometry(const QByteArray& geometry) {
// Make sure users of this class don't need to deal with backing up the geometry themselves
QWidget::restoreGeometry(geometry);
BackupGeometry();
}
QByteArray GRenderWindow::saveGeometry() {
// If we are a top-level widget, store the current geometry
// otherwise, store the last backup
if (parent() == nullptr) {
return QWidget::saveGeometry();
}
return geometry;
}
qreal GRenderWindow::windowPixelRatio() const {
return devicePixelRatioF();
}
std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF& pos) const {
const qreal pixel_ratio = windowPixelRatio();
return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
}
void GRenderWindow::closeEvent(QCloseEvent* event) {
emit Closed();
QWidget::closeEvent(event);
}
void GRenderWindow::keyPressEvent(QKeyEvent* event) {
input_subsystem->GetKeyboard()->PressKey(event->key());
}
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
input_subsystem->GetKeyboard()->ReleaseKey(event->key());
}
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
// Touch input is handled in TouchBeginEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
auto pos = event->pos();
const auto [x, y] = ScaleTouch(pos);
input_subsystem->GetMouse()->PressButton(x, y, event->button());
if (event->button() == Qt::LeftButton) {
this->TouchPressed(x, y);
}
QWidget::mousePressEvent(event);
}
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
// Touch input is handled in TouchUpdateEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
auto pos = event->pos();
const auto [x, y] = ScaleTouch(pos);
input_subsystem->GetMouse()->MouseMove(x, y);
this->TouchMoved(x, y);
QWidget::mouseMoveEvent(event);
}
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
// Touch input is handled in TouchEndEvent
if (event->source() == Qt::MouseEventSynthesizedBySystem) {
return;
}
input_subsystem->GetMouse()->ReleaseButton(event->button());
if (event->button() == Qt::LeftButton) {
this->TouchReleased();
}
}
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
// TouchBegin always has exactly one touch point, so take the .first()
const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
this->TouchPressed(x, y);
}
void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
QPointF pos;
int active_points = 0;
// average all active touch points
for (const auto& tp : event->touchPoints()) {
if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
active_points++;
pos += tp.pos();
}
}
pos /= active_points;
const auto [x, y] = ScaleTouch(pos);
this->TouchMoved(x, y);
}
void GRenderWindow::TouchEndEvent() {
this->TouchReleased();
}
bool GRenderWindow::event(QEvent* event) {
if (event->type() == QEvent::TouchBegin) {
TouchBeginEvent(static_cast<QTouchEvent*>(event));
return true;
} else if (event->type() == QEvent::TouchUpdate) {
TouchUpdateEvent(static_cast<QTouchEvent*>(event));
return true;
} else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) {
TouchEndEvent();
return true;
}
return QWidget::event(event);
}
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
QWidget::focusOutEvent(event);
input_subsystem->GetKeyboard()->ReleaseAllKeys();
}
void GRenderWindow::resizeEvent(QResizeEvent* event) {
QWidget::resizeEvent(event);
OnFramebufferSizeChanged();
}
std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const {
#ifdef HAS_OPENGL
if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) {
auto c = static_cast<OpenGLSharedContext*>(main_context.get());
// Bind the shared contexts to the main surface in case the backend wants to take over
// presentation
return std::make_unique<OpenGLSharedContext>(c->GetShareContext(),
child_widget->windowHandle());
}
#endif
return std::make_unique<DummyContext>();
}
bool GRenderWindow::InitRenderTarget() {
ReleaseRenderTarget();
first_frame = false;
switch (Settings::values.renderer_backend.GetValue()) {
case Settings::RendererBackend::OpenGL:
if (!InitializeOpenGL()) {
return false;
}
break;
case Settings::RendererBackend::Vulkan:
if (!InitializeVulkan()) {
return false;
}
break;
}
// Update the Window System information with the new render target
window_info = GetWindowSystemInfo(child_widget->windowHandle());
child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
layout()->addWidget(child_widget);
// Reset minimum required size to avoid resizing issues on the main window after restarting.
setMinimumSize(1, 1);
resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size);
OnFramebufferSizeChanged();
BackupGeometry();
if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL) {
if (!LoadOpenGL()) {
return false;
}
}
return true;
}
void GRenderWindow::ReleaseRenderTarget() {
if (child_widget) {
layout()->removeWidget(child_widget);
child_widget->deleteLater();
child_widget = nullptr;
}
main_context.reset();
}
void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) {
auto& renderer = Core::System::GetInstance().Renderer();
if (res_scale == 0) {
res_scale = VideoCore::GetResolutionScaleFactor(renderer);
}
const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
renderer.RequestScreenshot(
screenshot_image.bits(),
[=, this] {
const std::string std_screenshot_path = screenshot_path.toStdString();
if (screenshot_image.mirrored(false, true).save(screenshot_path)) {
LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
} else {
LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path);
}
},
layout);
}
bool GRenderWindow::IsLoadingComplete() const {
return first_frame;
}
void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
setMinimumSize(minimal_size.first, minimal_size.second);
}
bool GRenderWindow::InitializeOpenGL() {
#ifdef HAS_OPENGL
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
auto child = new OpenGLRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
auto context = std::make_shared<OpenGLSharedContext>(child->windowHandle());
main_context = context;
child->SetContext(
std::make_unique<OpenGLSharedContext>(context->GetShareContext(), child->windowHandle()));
return true;
#else
QMessageBox::warning(this, tr("OpenGL not available!"),
tr("yuzu has not been compiled with OpenGL support."));
return false;
#endif
}
bool GRenderWindow::InitializeVulkan() {
auto child = new VulkanRenderWidget(this);
child_widget = child;
child_widget->windowHandle()->create();
main_context = std::make_unique<DummyContext>();
return true;
}
bool GRenderWindow::LoadOpenGL() {
auto context = CreateSharedContext();
auto scope = context->Acquire();
if (!gladLoadGL()) {
QMessageBox::warning(
this, tr("Error while initializing OpenGL!"),
tr("Your GPU may not support OpenGL, or you do not have the latest graphics driver."));
return false;
}
const QString renderer =
QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
if (!GLAD_GL_VERSION_4_3) {
LOG_ERROR(Frontend, "GPU does not support OpenGL 4.3: {}", renderer.toStdString());
QMessageBox::warning(this, tr("Error while initializing OpenGL 4.3!"),
tr("Your GPU may not support OpenGL 4.3, or you do not have the "
"latest graphics driver.<br><br>GL Renderer:<br>%1")
.arg(renderer));
return false;
}
QStringList unsupported_gl_extensions = GetUnsupportedGLExtensions();
if (!unsupported_gl_extensions.empty()) {
QMessageBox::warning(
this, tr("Error while initializing OpenGL!"),
tr("Your GPU may not support one or more required OpenGL extensions. Please ensure you "
"have the latest graphics driver.<br><br>GL Renderer:<br>%1<br><br>Unsupported "
"extensions:<br>%2")
.arg(renderer)
.arg(unsupported_gl_extensions.join(QStringLiteral("<br>"))));
return false;
}
return true;
}
QStringList GRenderWindow::GetUnsupportedGLExtensions() const {
QStringList unsupported_ext;
if (!GLAD_GL_ARB_buffer_storage)
unsupported_ext.append(QStringLiteral("ARB_buffer_storage"));
if (!GLAD_GL_ARB_direct_state_access)
unsupported_ext.append(QStringLiteral("ARB_direct_state_access"));
if (!GLAD_GL_ARB_vertex_type_10f_11f_11f_rev)
unsupported_ext.append(QStringLiteral("ARB_vertex_type_10f_11f_11f_rev"));
if (!GLAD_GL_ARB_texture_mirror_clamp_to_edge)
unsupported_ext.append(QStringLiteral("ARB_texture_mirror_clamp_to_edge"));
if (!GLAD_GL_ARB_multi_bind)
unsupported_ext.append(QStringLiteral("ARB_multi_bind"));
if (!GLAD_GL_ARB_clip_control)
unsupported_ext.append(QStringLiteral("ARB_clip_control"));
// Extensions required to support some texture formats.
if (!GLAD_GL_EXT_texture_compression_s3tc)
unsupported_ext.append(QStringLiteral("EXT_texture_compression_s3tc"));
if (!GLAD_GL_ARB_texture_compression_rgtc)
unsupported_ext.append(QStringLiteral("ARB_texture_compression_rgtc"));
if (!GLAD_GL_ARB_depth_buffer_float)
unsupported_ext.append(QStringLiteral("ARB_depth_buffer_float"));
if (!unsupported_ext.empty()) {
LOG_ERROR(Frontend, "GPU does not support all required extensions: {}",
glGetString(GL_RENDERER));
}
for (const QString& ext : unsupported_ext) {
LOG_ERROR(Frontend, "Unsupported GL extension: {}", ext.toStdString());
}
return unsupported_ext;
}
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
this->emu_thread = emu_thread;
}
void GRenderWindow::OnEmulationStopping() {
emu_thread = nullptr;
}
void GRenderWindow::showEvent(QShowEvent* event) {
QWidget::showEvent(event);
// windowHandle() is not initialized until the Window is shown, so we connect it here.
connect(windowHandle(), &QWindow::screenChanged, this, &GRenderWindow::OnFramebufferSizeChanged,
Qt::UniqueConnection);
}
+219
View File
@@ -0,0 +1,219 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
#include <QImage>
#include <QThread>
#include <QWidget>
#include <QWindow>
#include "common/thread.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
class GRenderWindow;
class GMainWindow;
class QKeyEvent;
class QTouchEvent;
class QStringList;
namespace InputCommon {
class InputSubsystem;
}
namespace VideoCore {
enum class LoadCallbackStage;
}
class EmuThread final : public QThread {
Q_OBJECT
public:
explicit EmuThread();
~EmuThread() override;
/**
* Start emulation (on new thread)
* @warning Only call when not running!
*/
void run() override;
/**
* Steps the emulation thread by a single CPU instruction (if the CPU is not already running)
* @note This function is thread-safe
*/
void ExecStep() {
exec_step = true;
running_cv.notify_all();
}
/**
* Sets whether the emulation thread is running or not
* @param running Boolean value, set the emulation thread to running if true
* @note This function is thread-safe
*/
void SetRunning(bool running) {
std::unique_lock lock{running_mutex};
this->running = running;
lock.unlock();
running_cv.notify_all();
if (!running) {
running_wait.Set();
/// Wait until effectively paused
while (running_guard)
;
}
}
/**
* Check if the emulation thread is running or not
* @return True if the emulation thread is running, otherwise false
* @note This function is thread-safe
*/
bool IsRunning() const {
return running;
}
/**
* Requests for the emulation thread to stop running
*/
void RequestStop() {
stop_run = true;
SetRunning(false);
}
private:
bool exec_step = false;
bool running = false;
std::atomic_bool stop_run{false};
std::mutex running_mutex;
std::condition_variable running_cv;
Common::Event running_wait{};
std::atomic_bool running_guard{false};
signals:
/**
* Emitted when the CPU has halted execution
*
* @warning When connecting to this signal from other threads, make sure to specify either
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
*/
void DebugModeEntered();
/**
* Emitted right before the CPU continues execution
*
* @warning When connecting to this signal from other threads, make sure to specify either
* Qt::QueuedConnection (invoke slot within the destination object's message thread) or even
* Qt::BlockingQueuedConnection (additionally block source thread until slot returns)
*/
void DebugModeLeft();
void ErrorThrown(Core::System::ResultStatus, std::string);
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
};
class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow {
Q_OBJECT
public:
explicit GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_);
~GRenderWindow() override;
// EmuWindow implementation.
void OnFrameDisplayed() override;
bool IsShown() const override;
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
void BackupGeometry();
void RestoreGeometry();
void restoreGeometry(const QByteArray& geometry); // overridden
QByteArray saveGeometry(); // overridden
qreal windowPixelRatio() const;
void closeEvent(QCloseEvent* event) override;
void resizeEvent(QResizeEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
bool event(QEvent* event) override;
void focusOutEvent(QFocusEvent* event) override;
bool InitRenderTarget();
/// Destroy the previous run's child_widget which should also destroy the child_window
void ReleaseRenderTarget();
bool IsLoadingComplete() const;
void CaptureScreenshot(u32 res_scale, const QString& screenshot_path);
std::pair<u32, u32> ScaleTouch(const QPointF& pos) const;
/**
* Instructs the window to re-launch the application using the specified program_index.
* @param program_index Specifies the index within the application of the program to launch.
*/
void ExecuteProgram(std::size_t program_index);
public slots:
void OnEmulationStarting(EmuThread* emu_thread);
void OnEmulationStopping();
void OnFramebufferSizeChanged();
signals:
/// Emitted when the window is closed
void Closed();
void FirstFrameDisplayed();
void ExecuteProgramSignal(std::size_t program_index);
private:
void TouchBeginEvent(const QTouchEvent* event);
void TouchUpdateEvent(const QTouchEvent* event);
void TouchEndEvent();
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
bool InitializeOpenGL();
bool InitializeVulkan();
bool LoadOpenGL();
QStringList GetUnsupportedGLExtensions() const;
EmuThread* emu_thread;
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem;
// Main context that will be shared with all other contexts that are requested.
// If this is used in a shared context setting, then this should not be used directly, but
// should instead be shared from
std::shared_ptr<Core::Frontend::GraphicsContext> main_context;
/// Temporary storage of the screenshot taken
QImage screenshot_image;
QByteArray geometry;
QWidget* child_widget = nullptr;
bool first_frame = false;
protected:
void showEvent(QShowEvent* event) override;
};
+89
View File
@@ -0,0 +1,89 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QButtonGroup>
#include <QMessageBox>
#include <QPushButton>
#include <QtConcurrent/qtconcurrentrun.h>
#include "common/logging/log.h"
#include "common/telemetry.h"
#include "core/core.h"
#include "core/telemetry_session.h"
#include "ui_compatdb.h"
#include "yuzu/compatdb.h"
CompatDB::CompatDB(QWidget* parent)
: QWizard(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
ui{std::make_unique<Ui::CompatDB>()} {
ui->setupUi(this);
connect(ui->radioButton_Perfect, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_Great, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_Okay, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_Bad, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit);
connect(&testcase_watcher, &QFutureWatcher<bool>::finished, this,
&CompatDB::OnTestcaseSubmitted);
}
CompatDB::~CompatDB() = default;
enum class CompatDBPage {
Intro = 0,
Selection = 1,
Final = 2,
};
void CompatDB::Submit() {
QButtonGroup* compatibility = new QButtonGroup(this);
compatibility->addButton(ui->radioButton_Perfect, 0);
compatibility->addButton(ui->radioButton_Great, 1);
compatibility->addButton(ui->radioButton_Okay, 2);
compatibility->addButton(ui->radioButton_Bad, 3);
compatibility->addButton(ui->radioButton_IntroMenu, 4);
compatibility->addButton(ui->radioButton_WontBoot, 5);
switch ((static_cast<CompatDBPage>(currentId()))) {
case CompatDBPage::Selection:
if (compatibility->checkedId() == -1) {
button(NextButton)->setEnabled(false);
}
break;
case CompatDBPage::Final:
back();
LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
Core::System::GetInstance().TelemetrySession().AddField(
Common::Telemetry::FieldType::UserFeedback, "Compatibility",
compatibility->checkedId());
button(NextButton)->setEnabled(false);
button(NextButton)->setText(tr("Submitting"));
button(CancelButton)->setVisible(false);
testcase_watcher.setFuture(QtConcurrent::run(
[] { return Core::System::GetInstance().TelemetrySession().SubmitTestcase(); }));
break;
default:
LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
}
}
void CompatDB::OnTestcaseSubmitted() {
if (!testcase_watcher.result()) {
QMessageBox::critical(this, tr("Communication error"),
tr("An error occured while sending the Testcase"));
button(NextButton)->setEnabled(true);
button(NextButton)->setText(tr("Next"));
button(CancelButton)->setVisible(true);
} else {
next();
// older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
// workaround
button(CancelButton)->setVisible(false);
}
}
void CompatDB::EnableNext() {
button(NextButton)->setEnabled(true);
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QFutureWatcher>
#include <QWizard>
namespace Ui {
class CompatDB;
}
class CompatDB : public QWizard {
Q_OBJECT
public:
explicit CompatDB(QWidget* parent = nullptr);
~CompatDB();
private:
QFutureWatcher<bool> testcase_watcher;
std::unique_ptr<Ui::CompatDB> ui;
void Submit();
void OnTestcaseSubmitted();
void EnableNext();
};
+215
View File
@@ -0,0 +1,215 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CompatDB</class>
<widget class="QWizard" name="CompatDB">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>482</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>410</height>
</size>
</property>
<property name="windowTitle">
<string>Report Compatibility</string>
</property>
<property name="options">
<set>QWizard::DisabledBackButtonOnLastPage|QWizard::HelpButtonOnRight|QWizard::NoBackButtonOnStartPage</set>
</property>
<widget class="QWizardPage" name="wizard_Info">
<property name="title">
<string>Report Game Compatibility</string>
</property>
<attribute name="pageId">
<string notr="true">0</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="lbl_Spiel">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;Should you choose to submit a test case to the &lt;/span&gt;&lt;a href=&quot;https://yuzu-emu.org/game/&quot;&gt;&lt;span style=&quot; font-size:10pt; text-decoration: underline; color:#0000ff;&quot;&gt;yuzu Compatibility List&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;, The following information will be collected and displayed on the site:&lt;/span&gt;&lt;/p&gt;&lt;ul style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Hardware Information (CPU / GPU / Operating System)&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Which version of yuzu you are running&lt;/li&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;The connected yuzu account&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="wizard_Report">
<property name="title">
<string>Report Game Compatibility</string>
</property>
<attribute name="pageId">
<string notr="true">1</string>
</attribute>
<layout class="QFormLayout" name="formLayout">
<item row="2" column="0">
<widget class="QRadioButton" name="radioButton_Perfect">
<property name="text">
<string>Perfect</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="lbl_Perfect">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions flawlessly with no audio or graphical glitches.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QRadioButton" name="radioButton_Great">
<property name="text">
<string>Great </string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="lbl_Great">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with minor graphical or audio glitches and is playable from start to finish. May require some workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QRadioButton" name="radioButton_Okay">
<property name="text">
<string>Okay</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="lbl_Okay">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions with major graphical or audio glitches, but game is playable from start to finish with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QRadioButton" name="radioButton_Bad">
<property name="text">
<string>Bad</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="lbl_Bad">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game functions, but with major graphical or audio glitches. Unable to progress in specific areas due to glitches even with workarounds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QRadioButton" name="radioButton_IntroMenu">
<property name="text">
<string>Intro/Menu</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLabel" name="lbl_IntroMenu">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Game is completely unplayable due to major graphical or audio glitches. Unable to progress past the Start Screen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QRadioButton" name="radioButton_WontBoot">
<property name="text">
<string>Won't Boot</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="lbl_WontBoot">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The game crashes when attempting to startup.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="lbl_Independent">
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Independent of speed or performance, how well does this game play from start to finish on this version of yuzu?&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWizardPage" name="wizard_ThankYou">
<property name="title">
<string>Thank you for your submission!</string>
</property>
<attribute name="pageId">
<string notr="true">2</string>
</attribute>
</widget>
</widget>
<resources/>
<connections/>
</ui>
+18
View File
@@ -0,0 +1,18 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <fmt/format.h>
#include "yuzu/compatibility_list.h"
CompatibilityList::const_iterator FindMatchingCompatibilityEntry(
const CompatibilityList& compatibility_list, u64 program_id) {
return std::find_if(compatibility_list.begin(), compatibility_list.end(),
[program_id](const auto& element) {
std::string pid = fmt::format("{:016X}", program_id);
return element.first == pid;
});
}
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <string>
#include <unordered_map>
#include <QString>
#include "common/common_types.h"
using CompatibilityList = std::unordered_map<std::string, std::pair<QString, QString>>;
CompatibilityList::const_iterator FindMatchingCompatibilityEntry(
const CompatibilityList& compatibility_list, u64 program_id);
+1615
View File
File diff suppressed because it is too large Load Diff
+136
View File
@@ -0,0 +1,136 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <string>
#include <QMetaType>
#include <QVariant>
#include "core/settings.h"
#include "yuzu/uisettings.h"
class QSettings;
class Config {
public:
enum class ConfigType {
GlobalConfig,
PerGameConfig,
InputProfile,
};
explicit Config(const std::string& config_name = "qt-config",
ConfigType config_type = ConfigType::GlobalConfig);
~Config();
void Reload();
void Save();
void ReadControlPlayerValue(std::size_t player_index);
void SaveControlPlayerValue(std::size_t player_index);
const std::string& GetConfigFilePath() const;
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<int, Settings::NativeMotion::NumMotions> default_motions;
static const std::array<std::array<int, 4>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<int, 2> default_stick_mod;
static const std::array<int, Settings::NativeMouseButton::NumMouseButtons>
default_mouse_buttons;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardKeys> default_keyboard_keys;
static const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> default_keyboard_mods;
static const std::array<UISettings::Shortcut, 16> default_hotkeys;
private:
void Initialize(const std::string& config_name);
void ReadValues();
void ReadPlayerValue(std::size_t player_index);
void ReadDebugValues();
void ReadKeyboardValues();
void ReadMouseValues();
void ReadTouchscreenValues();
void ReadMotionTouchValues();
// Read functions bases off the respective config section names.
void ReadAudioValues();
void ReadControlValues();
void ReadCoreValues();
void ReadDataStorageValues();
void ReadDebuggingValues();
void ReadServiceValues();
void ReadDisabledAddOnValues();
void ReadMiscellaneousValues();
void ReadPathValues();
void ReadCpuValues();
void ReadRendererValues();
void ReadScreenshotValues();
void ReadShortcutValues();
void ReadSystemValues();
void ReadUIValues();
void ReadUIGamelistValues();
void ReadUILayoutValues();
void ReadWebServiceValues();
void SaveValues();
void SavePlayerValue(std::size_t player_index);
void SaveDebugValues();
void SaveMouseValues();
void SaveTouchscreenValues();
void SaveMotionTouchValues();
// Save functions based off the respective config section names.
void SaveAudioValues();
void SaveControlValues();
void SaveCoreValues();
void SaveDataStorageValues();
void SaveDebuggingValues();
void SaveServiceValues();
void SaveDisabledAddOnValues();
void SaveMiscellaneousValues();
void SavePathValues();
void SaveCpuValues();
void SaveRendererValues();
void SaveScreenshotValues();
void SaveShortcutValues();
void SaveSystemValues();
void SaveUIValues();
void SaveUIGamelistValues();
void SaveUILayoutValues();
void SaveWebServiceValues();
QVariant ReadSetting(const QString& name) const;
QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
// Templated ReadSettingGlobal functions will also look for the use_global setting and set
// both the value and the global state properly
template <typename Type>
void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name);
template <typename Type>
void ReadSettingGlobal(Settings::Setting<Type>& setting, const QString& name,
const QVariant& default_value);
template <typename Type>
void ReadSettingGlobal(Type& setting, const QString& name, const QVariant& default_value) const;
// Templated WriteSettingGlobal functions will also write the global state if needed and will
// skip writing the actual setting if it defers to the global value
void WriteSetting(const QString& name, const QVariant& value);
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
template <typename Type>
void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting);
template <typename Type>
void WriteSettingGlobal(const QString& name, const Settings::Setting<Type>& setting,
const QVariant& default_value);
void WriteSettingGlobal(const QString& name, const QVariant& value, bool use_global,
const QVariant& default_value);
ConfigType type;
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
bool global;
};
// These metatype declarations cannot be in core/settings.h because core is devoid of QT
Q_DECLARE_METATYPE(Settings::RendererBackend);
Q_DECLARE_METATYPE(Settings::GPUAccuracy);
+134
View File
@@ -0,0 +1,134 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QCheckBox>
#include <QComboBox>
#include <QObject>
#include <QString>
#include "core/settings.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_per_game.h"
void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<bool>* setting,
const QCheckBox* checkbox,
const CheckState& tracker) {
if (tracker == CheckState::Global) {
setting->SetGlobal(true);
} else {
setting->SetGlobal(false);
setting->SetValue(checkbox->checkState());
}
}
void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<int>* setting,
const QComboBox* combobox) {
if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
setting->SetGlobal(true);
} else {
setting->SetGlobal(false);
setting->SetValue(combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET);
}
}
void ConfigurationShared::ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting,
const QComboBox* combobox) {
if (combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
setting->SetGlobal(true);
} else {
setting->SetGlobal(false);
setting->SetValue(static_cast<Settings::RendererBackend>(
combobox->currentIndex() - ConfigurationShared::USE_GLOBAL_OFFSET));
}
}
void ConfigurationShared::SetPerGameSetting(QCheckBox* checkbox,
const Settings::Setting<bool>* setting) {
if (setting->UsingGlobal()) {
checkbox->setCheckState(Qt::PartiallyChecked);
} else {
checkbox->setCheckState(setting->GetValue() ? Qt::Checked : Qt::Unchecked);
}
}
void ConfigurationShared::SetPerGameSetting(QComboBox* combobox,
const Settings::Setting<int>* setting) {
combobox->setCurrentIndex(setting->UsingGlobal()
? ConfigurationShared::USE_GLOBAL_INDEX
: setting->GetValue() + ConfigurationShared::USE_GLOBAL_OFFSET);
}
void ConfigurationShared::SetPerGameSetting(
QComboBox* combobox, const Settings::Setting<Settings::RendererBackend>* setting) {
combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
: static_cast<int>(setting->GetValue()) +
ConfigurationShared::USE_GLOBAL_OFFSET);
}
void ConfigurationShared::SetPerGameSetting(
QComboBox* combobox, const Settings::Setting<Settings::GPUAccuracy>* setting) {
combobox->setCurrentIndex(setting->UsingGlobal() ? ConfigurationShared::USE_GLOBAL_INDEX
: static_cast<int>(setting->GetValue()) +
ConfigurationShared::USE_GLOBAL_OFFSET);
}
void ConfigurationShared::SetHighlight(QWidget* widget, bool highlighted) {
if (highlighted) {
widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,203,255,0.5) }")
.arg(widget->objectName()));
} else {
widget->setStyleSheet(QStringLiteral("QWidget#%1 { background-color:rgba(0,0,0,0) }")
.arg(widget->objectName()));
}
widget->show();
}
void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox,
const Settings::Setting<bool>& setting,
CheckState& tracker) {
if (setting.UsingGlobal()) {
tracker = CheckState::Global;
} else {
tracker = (setting.GetValue() == setting.GetValue(true)) ? CheckState::On : CheckState::Off;
}
SetHighlight(checkbox, tracker != CheckState::Global);
QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, setting, &tracker] {
tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
static_cast<int>(CheckState::Count));
if (tracker == CheckState::Global) {
checkbox->setChecked(setting.GetValue(true));
}
SetHighlight(checkbox, tracker != CheckState::Global);
});
}
void ConfigurationShared::SetColoredTristate(QCheckBox* checkbox, bool global, bool state,
bool global_state, CheckState& tracker) {
if (global) {
tracker = CheckState::Global;
} else {
tracker = (state == global_state) ? CheckState::On : CheckState::Off;
}
SetHighlight(checkbox, tracker != CheckState::Global);
QObject::connect(checkbox, &QCheckBox::clicked, checkbox, [checkbox, global_state, &tracker] {
tracker = static_cast<CheckState>((static_cast<int>(tracker) + 1) %
static_cast<int>(CheckState::Count));
if (tracker == CheckState::Global) {
checkbox->setChecked(global_state);
}
SetHighlight(checkbox, tracker != CheckState::Global);
});
}
void ConfigurationShared::SetColoredComboBox(QComboBox* combobox, QWidget* target, int global) {
InsertGlobalItem(combobox, global);
QObject::connect(combobox, qOverload<int>(&QComboBox::activated), target,
[target](int index) { SetHighlight(target, index != 0); });
}
void ConfigurationShared::InsertGlobalItem(QComboBox* combobox, int global_index) {
const QString use_global_text =
ConfigurePerGame::tr("Use global configuration (%1)").arg(combobox->itemText(global_index));
combobox->insertItem(ConfigurationShared::USE_GLOBAL_INDEX, use_global_text);
combobox->insertSeparator(ConfigurationShared::USE_GLOBAL_SEPARATOR_INDEX);
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QCheckBox>
#include <QComboBox>
#include <QString>
#include "core/settings.h"
namespace ConfigurationShared {
constexpr int USE_GLOBAL_INDEX = 0;
constexpr int USE_GLOBAL_SEPARATOR_INDEX = 1;
constexpr int USE_GLOBAL_OFFSET = 2;
enum class CheckState {
Off,
On,
Global,
Count,
};
// Global-aware apply and set functions
void ApplyPerGameSetting(Settings::Setting<bool>* setting, const QCheckBox* checkbox,
const CheckState& tracker);
void ApplyPerGameSetting(Settings::Setting<int>* setting, const QComboBox* combobox);
void ApplyPerGameSetting(Settings::Setting<Settings::RendererBackend>* setting,
const QComboBox* combobox);
void ApplyPerGameSetting(Settings::Setting<Settings::GPUAccuracy>* setting,
const QComboBox* combobox);
void SetPerGameSetting(QCheckBox* checkbox, const Settings::Setting<bool>* setting);
void SetPerGameSetting(QComboBox* combobox, const Settings::Setting<int>* setting);
void SetPerGameSetting(QComboBox* combobox,
const Settings::Setting<Settings::RendererBackend>* setting);
void SetPerGameSetting(QComboBox* combobox,
const Settings::Setting<Settings::GPUAccuracy>* setting);
void SetHighlight(QWidget* widget, bool highlighted);
void SetColoredTristate(QCheckBox* checkbox, const Settings::Setting<bool>& setting,
CheckState& tracker);
void SetColoredTristate(QCheckBox* checkbox, bool global, bool state, bool global_state,
CheckState& tracker);
void SetColoredComboBox(QComboBox* combobox, QWidget* target, int global);
void InsertGlobalItem(QComboBox* combobox, int global_index);
} // namespace ConfigurationShared
+286
View File
@@ -0,0 +1,286 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureDialog</class>
<widget class="QDialog" name="ConfigureDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<height>650</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>650</height>
</size>
</property>
<property name="windowTitle">
<string>yuzu Configuration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListWidget" name="selectorList">
<property name="minimumSize">
<size>
<width>120</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>120</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="ConfigureGeneral" name="generalTab">
<property name="accessibleName">
<string>General</string>
</property>
<attribute name="title">
<string>General</string>
</attribute>
</widget>
<widget class="ConfigureUi" name="uiTab">
<property name="accessibleName">
<string>UI</string>
</property>
<attribute name="title">
<string>Game List</string>
</attribute>
</widget>
<widget class="ConfigureSystem" name="systemTab">
<property name="accessibleName">
<string>System</string>
</property>
<attribute name="title">
<string>System</string>
</attribute>
</widget>
<widget class="ConfigureProfileManager" name="profileManagerTab">
<property name="accessibleName">
<string>Profiles</string>
</property>
<attribute name="title">
<string>Profiles</string>
</attribute>
</widget>
<widget class="ConfigureFilesystem" name="filesystemTab">
<property name="accessibleName">
<string>Filesystem</string>
</property>
<attribute name="title">
<string>Filesystem</string>
</attribute>
</widget>
<widget class="ConfigureInput" name="inputTab">
<property name="accessibleName">
<string>Controls</string>
</property>
<attribute name="title">
<string>Controls</string>
</attribute>
</widget>
<widget class="ConfigureHotkeys" name="hotkeysTab">
<property name="accessibleName">
<string>Hotkeys</string>
</property>
<attribute name="title">
<string>Hotkeys</string>
</attribute>
</widget>
<widget class="ConfigureCpu" name="cpuTab">
<property name="accessibleName">
<string>CPU</string>
</property>
<attribute name="title">
<string>CPU</string>
</attribute>
</widget>
<widget class="ConfigureCpuDebug" name="cpuDebugTab">
<property name="accessibleName">
<string>Debug</string>
</property>
<attribute name="title">
<string>Debug</string>
</attribute>
</widget>
<widget class="ConfigureGraphics" name="graphicsTab">
<property name="accessibleName">
<string>Graphics</string>
</property>
<attribute name="title">
<string>Graphics</string>
</attribute>
</widget>
<widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab">
<property name="accessibleName">
<string>Advanced</string>
</property>
<attribute name="title">
<string>GraphicsAdvanced</string>
</attribute>
</widget>
<widget class="ConfigureAudio" name="audioTab">
<property name="accessibleName">
<string>Audio</string>
</property>
<attribute name="title">
<string>Audio</string>
</attribute>
</widget>
<widget class="ConfigureDebug" name="debugTab">
<property name="accessibleName">
<string>Debug</string>
</property>
<attribute name="title">
<string>Debug</string>
</attribute>
</widget>
<widget class="ConfigureWeb" name="webTab">
<property name="accessibleName">
<string>Web</string>
</property>
<attribute name="title">
<string>Web</string>
</attribute>
</widget>
<widget class="ConfigureService" name="serviceTab">
<property name="accessibleName">
<string>Services</string>
</property>
<attribute name="title">
<string>Services</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ConfigureGeneral</class>
<extends>QWidget</extends>
<header>configuration/configure_general.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureSystem</class>
<extends>QWidget</extends>
<header>configuration/configure_system.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureProfileManager</class>
<extends>QWidget</extends>
<header>configuration/configure_profile_manager.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureFilesystem</class>
<extends>QWidget</extends>
<header>configuration/configure_filesystem.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureAudio</class>
<extends>QWidget</extends>
<header>configuration/configure_audio.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureDebug</class>
<extends>QWidget</extends>
<header>configuration/configure_debug.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureCpu</class>
<extends>QWidget</extends>
<header>configuration/configure_cpu.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureCpuDebug</class>
<extends>QWidget</extends>
<header>configuration/configure_cpu_debug.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGraphics</class>
<extends>QWidget</extends>
<header>configuration/configure_graphics.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGraphicsAdvanced</class>
<extends>QWidget</extends>
<header>configuration/configure_graphics_advanced.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureWeb</class>
<extends>QWidget</extends>
<header>configuration/configure_web.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureUi</class>
<extends>QWidget</extends>
<header>configuration/configure_ui.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureInput</class>
<extends>QWidget</extends>
<header>configuration/configure_input.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureHotkeys</class>
<extends>QWidget</extends>
<header>configuration/configure_hotkeys.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureService</class>
<extends>QWidget</extends>
<header>configuration/configure_service.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureDialog</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureDialog</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>
+188
View File
@@ -0,0 +1,188 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include <QSignalBlocker>
#include "audio_core/sink.h"
#include "audio_core/sink_details.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_audio.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_audio.h"
ConfigureAudio::ConfigureAudio(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureAudio>()) {
ui->setupUi(this);
InitializeAudioOutputSinkComboBox();
connect(ui->volume_slider, &QSlider::valueChanged, this,
&ConfigureAudio::SetVolumeIndicatorText);
connect(ui->output_sink_combo_box, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureAudio::UpdateAudioDevices);
ui->volume_label->setVisible(Settings::IsConfiguringGlobal());
ui->volume_combo_box->setVisible(!Settings::IsConfiguringGlobal());
SetupPerGameUI();
SetConfiguration();
const bool is_powered_on = Core::System::GetInstance().IsPoweredOn();
ui->output_sink_combo_box->setEnabled(!is_powered_on);
ui->audio_device_combo_box->setEnabled(!is_powered_on);
}
ConfigureAudio::~ConfigureAudio() = default;
void ConfigureAudio::SetConfiguration() {
SetOutputSinkFromSinkID();
// The device list cannot be pre-populated (nor listed) until the output sink is known.
UpdateAudioDevices(ui->output_sink_combo_box->currentIndex());
SetAudioDeviceFromDeviceID();
ui->volume_slider->setValue(Settings::values.volume.GetValue() * ui->volume_slider->maximum());
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching.GetValue());
if (!Settings::IsConfiguringGlobal()) {
if (Settings::values.volume.UsingGlobal()) {
ui->volume_combo_box->setCurrentIndex(0);
ui->volume_slider->setEnabled(false);
} else {
ui->volume_combo_box->setCurrentIndex(1);
ui->volume_slider->setEnabled(true);
}
ConfigurationShared::SetHighlight(ui->volume_layout,
!Settings::values.volume.UsingGlobal());
}
SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
}
void ConfigureAudio::SetOutputSinkFromSinkID() {
[[maybe_unused]] const QSignalBlocker blocker(ui->output_sink_combo_box);
int new_sink_index = 0;
const QString sink_id = QString::fromStdString(Settings::values.sink_id);
for (int index = 0; index < ui->output_sink_combo_box->count(); index++) {
if (ui->output_sink_combo_box->itemText(index) == sink_id) {
new_sink_index = index;
break;
}
}
ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
}
void ConfigureAudio::SetAudioDeviceFromDeviceID() {
int new_device_index = -1;
const QString device_id = QString::fromStdString(Settings::values.audio_device_id);
for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
if (ui->audio_device_combo_box->itemText(index) == device_id) {
new_device_index = index;
break;
}
}
ui->audio_device_combo_box->setCurrentIndex(new_device_index);
}
void ConfigureAudio::SetVolumeIndicatorText(int percentage) {
ui->volume_indicator->setText(tr("%1%", "Volume percentage (e.g. 50%)").arg(percentage));
}
void ConfigureAudio::ApplyConfiguration() {
if (Settings::IsConfiguringGlobal()) {
Settings::values.sink_id =
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
.toStdString();
Settings::values.audio_device_id =
ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
.toStdString();
// Guard if during game and set to game-specific value
if (Settings::values.enable_audio_stretching.UsingGlobal()) {
Settings::values.enable_audio_stretching.SetValue(
ui->toggle_audio_stretching->isChecked());
}
if (Settings::values.volume.UsingGlobal()) {
Settings::values.volume.SetValue(
static_cast<float>(ui->volume_slider->sliderPosition()) /
ui->volume_slider->maximum());
}
} else {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_audio_stretching,
ui->toggle_audio_stretching,
enable_audio_stretching);
if (ui->volume_combo_box->currentIndex() == 0) {
Settings::values.volume.SetGlobal(true);
} else {
Settings::values.volume.SetGlobal(false);
Settings::values.volume.SetValue(
static_cast<float>(ui->volume_slider->sliderPosition()) /
ui->volume_slider->maximum());
}
}
}
void ConfigureAudio::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureAudio::UpdateAudioDevices(int sink_index) {
ui->audio_device_combo_box->clear();
ui->audio_device_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
const std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
for (const auto& device : AudioCore::GetDeviceListForSink(sink_id)) {
ui->audio_device_combo_box->addItem(QString::fromStdString(device));
}
}
void ConfigureAudio::InitializeAudioOutputSinkComboBox() {
ui->output_sink_combo_box->clear();
ui->output_sink_combo_box->addItem(QString::fromUtf8(AudioCore::auto_device_name));
for (const char* id : AudioCore::GetSinkIDs()) {
ui->output_sink_combo_box->addItem(QString::fromUtf8(id));
}
}
void ConfigureAudio::RetranslateUI() {
ui->retranslateUi(this);
SetVolumeIndicatorText(ui->volume_slider->sliderPosition());
}
void ConfigureAudio::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->volume_slider->setEnabled(Settings::values.volume.UsingGlobal());
ui->toggle_audio_stretching->setEnabled(
Settings::values.enable_audio_stretching.UsingGlobal());
return;
}
ConfigurationShared::SetColoredTristate(ui->toggle_audio_stretching,
Settings::values.enable_audio_stretching,
enable_audio_stretching);
connect(ui->volume_combo_box, qOverload<int>(&QComboBox::activated), this, [this](int index) {
ui->volume_slider->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->volume_layout, index == 1);
});
ui->output_sink_combo_box->setVisible(false);
ui->output_sink_label->setVisible(false);
ui->audio_device_combo_box->setVisible(false);
ui->audio_device_label->setVisible(false);
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
namespace ConfigurationShared {
enum class CheckState;
}
namespace Ui {
class ConfigureAudio;
}
class ConfigureAudio : public QWidget {
Q_OBJECT
public:
explicit ConfigureAudio(QWidget* parent = nullptr);
~ConfigureAudio() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void InitializeAudioOutputSinkComboBox();
void RetranslateUI();
void UpdateAudioDevices(int sink_index);
void SetConfiguration();
void SetOutputSinkFromSinkID();
void SetAudioDeviceFromDeviceID();
void SetVolumeIndicatorText(int percentage);
void SetupPerGameUI();
std::unique_ptr<Ui::ConfigureAudio> ui;
ConfigurationShared::CheckState enable_audio_stretching;
};
+165
View File
@@ -0,0 +1,165 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureAudio</class>
<widget class="QWidget" name="ConfigureAudio">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>367</width>
<height>368</height>
</rect>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Audio</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout" name="_3">
<item>
<widget class="QLabel" name="output_sink_label">
<property name="text">
<string>Output Engine:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="output_sink_combo_box"/>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="toggle_audio_stretching">
<property name="toolTip">
<string>This post-processing effect adjusts audio speed to match emulation speed and helps prevent audio stutter. This however increases audio latency.</string>
</property>
<property name="text">
<string>Enable audio stretching</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="_2">
<item>
<widget class="QLabel" name="audio_device_label">
<property name="text">
<string>Audio Device:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="audio_device_combo_box"/>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="volume_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="volume_combo_box">
<item>
<property name="text">
<string>Use global volume</string>
</property>
</item>
<item>
<property name="text">
<string>Set volume:</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="volume_label">
<property name="text">
<string>Volume:</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>30</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QSlider" name="volume_slider">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="pageStep">
<number>10</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="volume_indicator">
<property name="minimumSize">
<size>
<width>32</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>0 %</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>167</width>
<height>55</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QComboBox>
#include <QMessageBox>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_cpu.h"
#include "yuzu/configuration/configure_cpu.h"
ConfigureCpu::ConfigureCpu(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureCpu) {
ui->setupUi(this);
SetConfiguration();
connect(ui->accuracy, qOverload<int>(&QComboBox::activated), this,
&ConfigureCpu::AccuracyUpdated);
connect(ui->accuracy, qOverload<int>(&QComboBox::currentIndexChanged), this,
&ConfigureCpu::UpdateGroup);
}
ConfigureCpu::~ConfigureCpu() = default;
void ConfigureCpu::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
ui->accuracy->setEnabled(runtime_lock);
ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy));
UpdateGroup(static_cast<int>(Settings::values.cpu_accuracy));
ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock);
ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma);
ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock);
ui->cpuopt_unsafe_reduce_fp_error->setChecked(Settings::values.cpuopt_unsafe_reduce_fp_error);
}
void ConfigureCpu::AccuracyUpdated(int index) {
if (static_cast<Settings::CPUAccuracy>(index) == Settings::CPUAccuracy::DebugMode) {
const auto result = QMessageBox::warning(this, tr("Setting CPU to Debug Mode"),
tr("CPU Debug Mode is only intended for developer "
"use. Are you sure you want to enable this?"),
QMessageBox::Yes | QMessageBox::No);
if (result == QMessageBox::No) {
ui->accuracy->setCurrentIndex(static_cast<int>(Settings::CPUAccuracy::Accurate));
UpdateGroup(static_cast<int>(Settings::CPUAccuracy::Accurate));
}
}
}
void ConfigureCpu::UpdateGroup(int index) {
ui->unsafe_group->setVisible(static_cast<Settings::CPUAccuracy>(index) ==
Settings::CPUAccuracy::Unsafe);
}
void ConfigureCpu::ApplyConfiguration() {
Settings::values.cpu_accuracy =
static_cast<Settings::CPUAccuracy>(ui->accuracy->currentIndex());
Settings::values.cpuopt_unsafe_unfuse_fma = ui->cpuopt_unsafe_unfuse_fma->isChecked();
Settings::values.cpuopt_unsafe_reduce_fp_error = ui->cpuopt_unsafe_reduce_fp_error->isChecked();
}
void ConfigureCpu::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureCpu::RetranslateUI() {
ui->retranslateUi(this);
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
#include "core/settings.h"
namespace Ui {
class ConfigureCpu;
}
class ConfigureCpu : public QWidget {
Q_OBJECT
public:
explicit ConfigureCpu(QWidget* parent = nullptr);
~ConfigureCpu() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void AccuracyUpdated(int index);
void UpdateGroup(int index);
void SetConfiguration();
std::unique_ptr<Ui::ConfigureCpu> ui;
};
+144
View File
@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureCpu</class>
<widget class="QWidget" name="ConfigureCpu">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>321</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="title">
<string>General</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel">
<property name="text">
<string>Accuracy:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="accuracy">
<item>
<property name="text">
<string>Accurate</string>
</property>
</item>
<item>
<property name="text">
<string>Unsafe</string>
</property>
</item>
<item>
<property name="text">
<string>Enable Debug Mode</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel">
<property name="wordWrap">
<bool>1</bool>
</property>
<property name="text">
<string>We recommend setting accuracy to "Accurate".</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="unsafe_group">
<property name="title">
<string>Unsafe CPU Optimization Settings</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel">
<property name="wordWrap">
<bool>1</bool>
</property>
<property name="text">
<string>These settings reduce accuracy for speed.</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_unsafe_unfuse_fma">
<property name="text">
<string>Unfuse FMA (improve performance on CPUs without FMA)</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;This option improves speed by reducing accuracy of fused-multiply-add instructions on CPUs without native FMA support.&lt;/div&gt;
</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_unsafe_reduce_fp_error">
<property name="text">
<string>Faster FRSQRTE and FRECPE</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;This option improves the speed of some approximate floating-point functions by using less accurate native approximations.&lt;/div&gt;
</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
<string>CPU settings are available only when game is not running.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+65
View File
@@ -0,0 +1,65 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QComboBox>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_cpu_debug.h"
#include "yuzu/configuration/configure_cpu_debug.h"
ConfigureCpuDebug::ConfigureCpuDebug(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureCpuDebug) {
ui->setupUi(this);
SetConfiguration();
}
ConfigureCpuDebug::~ConfigureCpuDebug() = default;
void ConfigureCpuDebug::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
ui->cpuopt_page_tables->setEnabled(runtime_lock);
ui->cpuopt_page_tables->setChecked(Settings::values.cpuopt_page_tables);
ui->cpuopt_block_linking->setEnabled(runtime_lock);
ui->cpuopt_block_linking->setChecked(Settings::values.cpuopt_block_linking);
ui->cpuopt_return_stack_buffer->setEnabled(runtime_lock);
ui->cpuopt_return_stack_buffer->setChecked(Settings::values.cpuopt_return_stack_buffer);
ui->cpuopt_fast_dispatcher->setEnabled(runtime_lock);
ui->cpuopt_fast_dispatcher->setChecked(Settings::values.cpuopt_fast_dispatcher);
ui->cpuopt_context_elimination->setEnabled(runtime_lock);
ui->cpuopt_context_elimination->setChecked(Settings::values.cpuopt_context_elimination);
ui->cpuopt_const_prop->setEnabled(runtime_lock);
ui->cpuopt_const_prop->setChecked(Settings::values.cpuopt_const_prop);
ui->cpuopt_misc_ir->setEnabled(runtime_lock);
ui->cpuopt_misc_ir->setChecked(Settings::values.cpuopt_misc_ir);
ui->cpuopt_reduce_misalign_checks->setEnabled(runtime_lock);
ui->cpuopt_reduce_misalign_checks->setChecked(Settings::values.cpuopt_reduce_misalign_checks);
}
void ConfigureCpuDebug::ApplyConfiguration() {
Settings::values.cpuopt_page_tables = ui->cpuopt_page_tables->isChecked();
Settings::values.cpuopt_block_linking = ui->cpuopt_block_linking->isChecked();
Settings::values.cpuopt_return_stack_buffer = ui->cpuopt_return_stack_buffer->isChecked();
Settings::values.cpuopt_fast_dispatcher = ui->cpuopt_fast_dispatcher->isChecked();
Settings::values.cpuopt_context_elimination = ui->cpuopt_context_elimination->isChecked();
Settings::values.cpuopt_const_prop = ui->cpuopt_const_prop->isChecked();
Settings::values.cpuopt_misc_ir = ui->cpuopt_misc_ir->isChecked();
Settings::values.cpuopt_reduce_misalign_checks = ui->cpuopt_reduce_misalign_checks->isChecked();
}
void ConfigureCpuDebug::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureCpuDebug::RetranslateUI() {
ui->retranslateUi(this);
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
#include "core/settings.h"
namespace Ui {
class ConfigureCpuDebug;
}
class ConfigureCpuDebug : public QWidget {
Q_OBJECT
public:
explicit ConfigureCpuDebug(QWidget* parent = nullptr);
~ConfigureCpuDebug() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void SetConfiguration();
std::unique_ptr<Ui::ConfigureCpuDebug> ui;
};
+174
View File
@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureCpuDebug</class>
<widget class="QWidget" name="ConfigureCpuDebug">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>321</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox">
<property name="title">
<string>Toggle CPU Optimizations</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel">
<property name="wordWrap">
<bool>1</bool>
</property>
<property name="text">
<string>
&lt;div&gt;
&lt;b&gt;For debugging only.&lt;/b&gt;
&lt;br&gt;
If you're not sure what these do, keep all of these enabled.
&lt;br&gt;
These settings only take effect when CPU Accuracy is "Debug Mode".
&lt;/div&gt;
</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_page_tables">
<property name="text">
<string>Enable inline page tables</string>
</property>
<property name="toolTip">
<string>
&lt;div style="white-space: nowrap"&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
&lt;div style="white-space: nowrap"&gt;Enabling it inlines accesses to PageTable::pointers into emitted code.&lt;/div&gt;
&lt;div style="white-space: nowrap"&gt;Disabling this forces all memory accesses to go through the Memory::Read/Memory::Write functions.&lt;/div&gt;
</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_block_linking">
<property name="text">
<string>Enable block linking</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;This optimization avoids dispatcher lookups by allowing emitted basic blocks to jump directly to other basic blocks if the destination PC is static.&lt;/div&gt;
</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_return_stack_buffer">
<property name="text">
<string>Enable return stack buffer</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;This optimization avoids dispatcher lookups by keeping track potential return addresses of BL instructions. This approximates what happens with a return stack buffer on a real CPU.&lt;/div&gt;
</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_fast_dispatcher">
<property name="text">
<string>Enable fast dispatcher</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;Enable a two-tiered dispatch system. A faster dispatcher written in assembly has a small MRU cache of jump destinations is used first. If that fails, dispatch falls back to the slower C++ dispatcher.&lt;/div&gt;
</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_context_elimination">
<property name="text">
<string>Enable context elimination</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;Enables an IR optimization that reduces unnecessary accesses to the CPU context structure.&lt;/div&gt;
</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_const_prop">
<property name="text">
<string>Enable constant propagation</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;Enables IR optimizations that involve constant propagation.&lt;/div&gt;
</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_misc_ir">
<property name="text">
<string>Enable miscellaneous optimizations</string>
</property>
<property name="toolTip">
<string>
&lt;div&gt;Enables miscellaneous IR optimizations.&lt;/div&gt;
</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="cpuopt_reduce_misalign_checks">
<property name="text">
<string>Enable misalignment check reduction</string>
</property>
<property name="toolTip">
<string>
&lt;div style="white-space: nowrap"&gt;When enabled, a misalignment is only triggered when an access crosses a page boundary.&lt;/div&gt;
&lt;div style="white-space: nowrap"&gt;When disabled, a misalignment is triggered on all misaligned accesses.&lt;/div&gt;
</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
<string>CPU settings are available only when game is not running.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+69
View File
@@ -0,0 +1,69 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDesktopServices>
#include <QUrl>
#include "common/file_util.h"
#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_debug.h"
#include "yuzu/configuration/configure_debug.h"
#include "yuzu/debugger/console.h"
#include "yuzu/uisettings.h"
ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureDebug) {
ui->setupUi(this);
SetConfiguration();
connect(ui->open_log_button, &QPushButton::clicked, []() {
const auto path =
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LogDir));
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
});
}
ConfigureDebug::~ConfigureDebug() = default;
void ConfigureDebug::SetConfiguration() {
ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn());
ui->toggle_console->setChecked(UISettings::values.show_console);
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
ui->reporting_services->setChecked(Settings::values.reporting_services);
ui->quest_flag->setChecked(Settings::values.quest_flag);
ui->enable_graphics_debugging->setEnabled(!Core::System::GetInstance().IsPoweredOn());
ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug);
ui->disable_macro_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit);
ui->extended_logging->setChecked(Settings::values.extended_logging);
}
void ConfigureDebug::ApplyConfiguration() {
UISettings::values.show_console = ui->toggle_console->isChecked();
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
Settings::values.reporting_services = ui->reporting_services->isChecked();
Settings::values.quest_flag = ui->quest_flag->isChecked();
Settings::values.renderer_debug = ui->enable_graphics_debugging->isChecked();
Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked();
Settings::values.extended_logging = ui->extended_logging->isChecked();
Debugger::ToggleConsole();
Log::Filter filter;
filter.ParseFilterString(Settings::values.log_filter);
Log::SetGlobalFilter(filter);
}
void ConfigureDebug::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureDebug::RetranslateUI() {
ui->retranslateUi(this);
}
+30
View File
@@ -0,0 +1,30 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
namespace Ui {
class ConfigureDebug;
}
class ConfigureDebug : public QWidget {
Q_OBJECT
public:
explicit ConfigureDebug(QWidget* parent = nullptr);
~ConfigureDebug() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void SetConfiguration();
std::unique_ptr<Ui::ConfigureDebug> ui;
};
+220
View File
@@ -0,0 +1,220 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureDebug</class>
<widget class="QWidget" name="ConfigureDebug">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>486</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Logging</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Global Log Filter</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="log_filter_edit"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="toggle_console">
<property name="text">
<string>Show Log in Console</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="open_log_button">
<property name="text">
<string>Open Log Location</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="extended_logging">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>When checked, the max size of the log increases from 100 MB to 1 GB</string>
</property>
<property name="text">
<string>Enable Extended Logging</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>This will be reset automatically when yuzu closes.</string>
</property>
<property name="indent">
<number>20</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Homebrew</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>Arguments String</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="homebrew_args_edit"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Graphics</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QCheckBox" name="enable_graphics_debugging">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>When checked, the graphics API enters a slower debugging mode</string>
</property>
<property name="text">
<string>Enable Graphics Debugging</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="disable_macro_jit">
<property name="enabled">
<bool>true</bool>
</property>
<property name="toolTip">
<string>When checked, it disables the macro Just In Time compiler. Enabling this makes games run slower</string>
</property>
<property name="text">
<string>Disable Macro JIT</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Dump</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QCheckBox" name="reporting_services">
<property name="text">
<string>Enable Verbose Reporting Services</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_5">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>This will be reset automatically when yuzu closes.</string>
</property>
<property name="indent">
<number>20</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_6">
<property name="title">
<string>Advanced</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QCheckBox" name="quest_flag">
<property name="text">
<string>Kiosk (Quest) Mode</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<tabstops>
<tabstop>log_filter_edit</tabstop>
<tabstop>toggle_console</tabstop>
<tabstop>open_log_button</tabstop>
<tabstop>homebrew_args_edit</tabstop>
<tabstop>enable_graphics_debugging</tabstop>
<tabstop>reporting_services</tabstop>
<tabstop>quest_flag</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "ui_configure_debug_controller.h"
#include "yuzu/configuration/configure_debug_controller.h"
#include "yuzu/configuration/configure_input_player.h"
ConfigureDebugController::ConfigureDebugController(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem,
InputProfiles* profiles)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureDebugController>()),
debug_controller(
new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, true)) {
ui->setupUi(this);
ui->controllerLayout->addWidget(debug_controller);
connect(ui->clear_all_button, &QPushButton::clicked, this,
[this] { debug_controller->ClearAll(); });
connect(ui->restore_defaults_button, &QPushButton::clicked, this,
[this] { debug_controller->RestoreDefaults(); });
RetranslateUI();
}
ConfigureDebugController::~ConfigureDebugController() = default;
void ConfigureDebugController::ApplyConfiguration() {
debug_controller->ApplyConfiguration();
}
void ConfigureDebugController::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureDebugController::RetranslateUI() {
ui->retranslateUi(this);
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
class QPushButton;
class ConfigureInputPlayer;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
namespace Ui {
class ConfigureDebugController;
}
class ConfigureDebugController : public QDialog {
Q_OBJECT
public:
explicit ConfigureDebugController(QWidget* parent, InputCommon::InputSubsystem* input_subsystem,
InputProfiles* profiles);
~ConfigureDebugController() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
std::unique_ptr<Ui::ConfigureDebugController> ui;
ConfigureInputPlayer* debug_controller;
};
+77
View File
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureDebugController</class>
<widget class="QDialog" name="ConfigureDebugController">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>780</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Debug Controller</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<layout class="QHBoxLayout" name="controllerLayout"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="clear_all_button">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restore_defaults_button">
<property name="text">
<string>Defaults</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureDebugController</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureDebugController</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>
+147
View File
@@ -0,0 +1,147 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QHash>
#include <QListWidgetItem>
#include <QSignalBlocker>
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/hotkeys.h"
ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
InputCommon::InputSubsystem* input_subsystem)
: QDialog(parent), ui(new Ui::ConfigureDialog), registry(registry) {
Settings::SetConfiguringGlobal(true);
ui->setupUi(this);
ui->hotkeysTab->Populate(registry);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
ui->inputTab->Initialize(input_subsystem);
SetConfiguration();
PopulateSelectionList();
connect(ui->uiTab, &ConfigureUi::LanguageChanged, this, &ConfigureDialog::OnLanguageChanged);
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
&ConfigureDialog::UpdateVisibleTabs);
adjustSize();
ui->selectorList->setCurrentRow(0);
}
ConfigureDialog::~ConfigureDialog() = default;
void ConfigureDialog::SetConfiguration() {}
void ConfigureDialog::ApplyConfiguration() {
ui->generalTab->ApplyConfiguration();
ui->uiTab->ApplyConfiguration();
ui->systemTab->ApplyConfiguration();
ui->profileManagerTab->ApplyConfiguration();
ui->filesystemTab->applyConfiguration();
ui->inputTab->ApplyConfiguration();
ui->hotkeysTab->ApplyConfiguration(registry);
ui->cpuTab->ApplyConfiguration();
ui->cpuDebugTab->ApplyConfiguration();
ui->graphicsTab->ApplyConfiguration();
ui->graphicsAdvancedTab->ApplyConfiguration();
ui->audioTab->ApplyConfiguration();
ui->debugTab->ApplyConfiguration();
ui->webTab->ApplyConfiguration();
ui->serviceTab->ApplyConfiguration();
Settings::Apply(Core::System::GetInstance());
Settings::LogSettings();
}
void ConfigureDialog::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureDialog::RetranslateUI() {
const int old_row = ui->selectorList->currentRow();
const int old_index = ui->tabWidget->currentIndex();
ui->retranslateUi(this);
PopulateSelectionList();
ui->selectorList->setCurrentRow(old_row);
UpdateVisibleTabs();
ui->tabWidget->setCurrentIndex(old_index);
}
Q_DECLARE_METATYPE(QList<QWidget*>);
void ConfigureDialog::PopulateSelectionList() {
const std::array<std::pair<QString, QList<QWidget*>>, 6> items{
{{tr("General"), {ui->generalTab, ui->hotkeysTab, ui->uiTab, ui->webTab, ui->debugTab}},
{tr("System"), {ui->systemTab, ui->profileManagerTab, ui->serviceTab, ui->filesystemTab}},
{tr("CPU"), {ui->cpuTab, ui->cpuDebugTab}},
{tr("Graphics"), {ui->graphicsTab, ui->graphicsAdvancedTab}},
{tr("Audio"), {ui->audioTab}},
{tr("Controls"), ui->inputTab->GetSubTabs()}},
};
[[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
ui->selectorList->clear();
for (const auto& entry : items) {
auto* const item = new QListWidgetItem(entry.first);
item->setData(Qt::UserRole, QVariant::fromValue(entry.second));
ui->selectorList->addItem(item);
}
}
void ConfigureDialog::OnLanguageChanged(const QString& locale) {
emit LanguageChanged(locale);
// first apply the configuration, and then restore the display
ApplyConfiguration();
RetranslateUI();
SetConfiguration();
}
void ConfigureDialog::UpdateVisibleTabs() {
const auto items = ui->selectorList->selectedItems();
if (items.isEmpty()) {
return;
}
const std::map<QWidget*, QString> widgets = {
{ui->generalTab, tr("General")},
{ui->systemTab, tr("System")},
{ui->profileManagerTab, tr("Profiles")},
{ui->inputTab, tr("Controls")},
{ui->hotkeysTab, tr("Hotkeys")},
{ui->cpuTab, tr("CPU")},
{ui->cpuDebugTab, tr("Debug")},
{ui->graphicsTab, tr("Graphics")},
{ui->graphicsAdvancedTab, tr("Advanced")},
{ui->audioTab, tr("Audio")},
{ui->debugTab, tr("Debug")},
{ui->webTab, tr("Web")},
{ui->uiTab, tr("UI")},
{ui->filesystemTab, tr("Filesystem")},
{ui->serviceTab, tr("Services")},
};
[[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
ui->tabWidget->clear();
const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));
for (const auto tab : tabs) {
ui->tabWidget->addTab(tab, tab->accessibleName());
}
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
class HotkeyRegistry;
namespace InputCommon {
class InputSubsystem;
}
namespace Ui {
class ConfigureDialog;
}
class ConfigureDialog : public QDialog {
Q_OBJECT
public:
explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry,
InputCommon::InputSubsystem* input_subsystem);
~ConfigureDialog() override;
void ApplyConfiguration();
private slots:
void OnLanguageChanged(const QString& locale);
signals:
void LanguageChanged(const QString& locale);
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void SetConfiguration();
void UpdateVisibleTabs();
void PopulateSelectionList();
std::unique_ptr<Ui::ConfigureDialog> ui;
HotkeyRegistry& registry;
};
+153
View File
@@ -0,0 +1,153 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QFileDialog>
#include <QMessageBox>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "core/settings.h"
#include "ui_configure_filesystem.h"
#include "yuzu/configuration/configure_filesystem.h"
#include "yuzu/uisettings.h"
ConfigureFilesystem::ConfigureFilesystem(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureFilesystem>()) {
ui->setupUi(this);
this->setConfiguration();
connect(ui->nand_directory_button, &QToolButton::pressed, this,
[this] { SetDirectory(DirectoryTarget::NAND, ui->nand_directory_edit); });
connect(ui->sdmc_directory_button, &QToolButton::pressed, this,
[this] { SetDirectory(DirectoryTarget::SD, ui->sdmc_directory_edit); });
connect(ui->gamecard_path_button, &QToolButton::pressed, this,
[this] { SetDirectory(DirectoryTarget::Gamecard, ui->gamecard_path_edit); });
connect(ui->dump_path_button, &QToolButton::pressed, this,
[this] { SetDirectory(DirectoryTarget::Dump, ui->dump_path_edit); });
connect(ui->load_path_button, &QToolButton::pressed, this,
[this] { SetDirectory(DirectoryTarget::Load, ui->load_path_edit); });
connect(ui->cache_directory_button, &QToolButton::pressed, this,
[this] { SetDirectory(DirectoryTarget::Cache, ui->cache_directory_edit); });
connect(ui->reset_game_list_cache, &QPushButton::pressed, this,
&ConfigureFilesystem::ResetMetadata);
connect(ui->gamecard_inserted, &QCheckBox::stateChanged, this,
&ConfigureFilesystem::UpdateEnabledControls);
connect(ui->gamecard_current_game, &QCheckBox::stateChanged, this,
&ConfigureFilesystem::UpdateEnabledControls);
}
ConfigureFilesystem::~ConfigureFilesystem() = default;
void ConfigureFilesystem::setConfiguration() {
ui->nand_directory_edit->setText(
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::NANDDir)));
ui->sdmc_directory_edit->setText(
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir)));
ui->gamecard_path_edit->setText(QString::fromStdString(Settings::values.gamecard_path));
ui->dump_path_edit->setText(
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::DumpDir)));
ui->load_path_edit->setText(
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::LoadDir)));
ui->cache_directory_edit->setText(
QString::fromStdString(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir)));
ui->gamecard_inserted->setChecked(Settings::values.gamecard_inserted);
ui->gamecard_current_game->setChecked(Settings::values.gamecard_current_game);
ui->dump_exefs->setChecked(Settings::values.dump_exefs);
ui->dump_nso->setChecked(Settings::values.dump_nso);
ui->cache_game_list->setChecked(UISettings::values.cache_game_list);
UpdateEnabledControls();
}
void ConfigureFilesystem::applyConfiguration() {
Common::FS::GetUserPath(Common::FS::UserPath::NANDDir,
ui->nand_directory_edit->text().toStdString());
Common::FS::GetUserPath(Common::FS::UserPath::SDMCDir,
ui->sdmc_directory_edit->text().toStdString());
Common::FS::GetUserPath(Common::FS::UserPath::DumpDir,
ui->dump_path_edit->text().toStdString());
Common::FS::GetUserPath(Common::FS::UserPath::LoadDir,
ui->load_path_edit->text().toStdString());
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir,
ui->cache_directory_edit->text().toStdString());
Settings::values.gamecard_path = ui->gamecard_path_edit->text().toStdString();
Settings::values.gamecard_inserted = ui->gamecard_inserted->isChecked();
Settings::values.gamecard_current_game = ui->gamecard_current_game->isChecked();
Settings::values.dump_exefs = ui->dump_exefs->isChecked();
Settings::values.dump_nso = ui->dump_nso->isChecked();
UISettings::values.cache_game_list = ui->cache_game_list->isChecked();
}
void ConfigureFilesystem::SetDirectory(DirectoryTarget target, QLineEdit* edit) {
QString caption;
switch (target) {
case DirectoryTarget::NAND:
caption = tr("Select Emulated NAND Directory...");
break;
case DirectoryTarget::SD:
caption = tr("Select Emulated SD Directory...");
break;
case DirectoryTarget::Gamecard:
caption = tr("Select Gamecard Path...");
break;
case DirectoryTarget::Dump:
caption = tr("Select Dump Directory...");
break;
case DirectoryTarget::Load:
caption = tr("Select Mod Load Directory...");
break;
case DirectoryTarget::Cache:
caption = tr("Select Cache Directory...");
break;
}
QString str;
if (target == DirectoryTarget::Gamecard) {
str = QFileDialog::getOpenFileName(this, caption, QFileInfo(edit->text()).dir().path(),
QStringLiteral("NX Gamecard;*.xci"));
} else {
str = QFileDialog::getExistingDirectory(this, caption, edit->text()) + QDir::separator();
}
if (str.isEmpty())
return;
edit->setText(str);
}
void ConfigureFilesystem::ResetMetadata() {
if (!Common::FS::Exists(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
"game_list")) {
QMessageBox::information(this, tr("Reset Metadata Cache"),
tr("The metadata cache is already empty."));
} else if (Common::FS::DeleteDirRecursively(
Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
"game_list")) {
QMessageBox::information(this, tr("Reset Metadata Cache"),
tr("The operation completed successfully."));
UISettings::values.is_game_list_reload_pending.exchange(true);
} else {
QMessageBox::warning(
this, tr("Reset Metadata Cache"),
tr("The metadata cache couldn't be deleted. It might be in use or non-existent."));
}
}
void ConfigureFilesystem::UpdateEnabledControls() {
ui->gamecard_current_game->setEnabled(ui->gamecard_inserted->isChecked());
ui->gamecard_path_edit->setEnabled(ui->gamecard_inserted->isChecked() &&
!ui->gamecard_current_game->isChecked());
ui->gamecard_path_button->setEnabled(ui->gamecard_inserted->isChecked() &&
!ui->gamecard_current_game->isChecked());
}
void ConfigureFilesystem::retranslateUi() {
ui->retranslateUi(this);
}
+43
View File
@@ -0,0 +1,43 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
class QLineEdit;
namespace Ui {
class ConfigureFilesystem;
}
class ConfigureFilesystem : public QWidget {
Q_OBJECT
public:
explicit ConfigureFilesystem(QWidget* parent = nullptr);
~ConfigureFilesystem() override;
void applyConfiguration();
void retranslateUi();
private:
void setConfiguration();
enum class DirectoryTarget {
NAND,
SD,
Gamecard,
Dump,
Load,
Cache,
};
void SetDirectory(DirectoryTarget target, QLineEdit* edit);
void ResetMetadata();
void UpdateEnabledControls();
std::unique_ptr<Ui::ConfigureFilesystem> ui;
};
+274
View File
@@ -0,0 +1,274 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureFilesystem</class>
<widget class="QWidget" name="ConfigureFilesystem">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>453</width>
<height>561</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Storage Directories</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>NAND</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="nand_directory_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="nand_directory_edit"/>
</item>
<item row="1" column="2">
<widget class="QLineEdit" name="sdmc_directory_edit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>SD Card</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QToolButton" name="sdmc_directory_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Maximum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>60</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Gamecard</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Path</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLineEdit" name="gamecard_path_edit"/>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="gamecard_inserted">
<property name="text">
<string>Inserted</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="gamecard_current_game">
<property name="text">
<string>Current Game</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QToolButton" name="gamecard_path_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>Patch Manager</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="2">
<widget class="QLineEdit" name="load_path_edit"/>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="dump_path_edit"/>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="dump_path_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QToolButton" name="load_path_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="2" column="0" colspan="4">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="dump_nso">
<property name="text">
<string>Dump Decompressed NSOs</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="dump_exefs">
<property name="text">
<string>Dump ExeFS</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Mod Load Root</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Dump Root</string>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_5">
<property name="title">
<string>Caching</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Cache Directory</string>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2">
<widget class="QLineEdit" name="cache_directory_edit"/>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="cache_directory_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0" colspan="4">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="cache_game_list">
<property name="text">
<string>Cache Game List Metadata</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="reset_game_list_cache">
<property name="text">
<string>Reset Metadata Cache</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QCheckBox>
#include <QSpinBox>
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_general.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_general.h"
#include "yuzu/uisettings.h"
ConfigureGeneral::ConfigureGeneral(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGeneral) {
ui->setupUi(this);
SetupPerGameUI();
SetConfiguration();
if (Settings::IsConfiguringGlobal()) {
connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit,
[this]() { ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); });
}
}
ConfigureGeneral::~ConfigureGeneral() = default;
void ConfigureGeneral::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
ui->use_multi_core->setEnabled(runtime_lock);
ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue());
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
ui->toggle_background_pause->setChecked(UISettings::values.pause_when_in_background);
ui->toggle_hide_mouse->setChecked(UISettings::values.hide_mouse);
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue());
ui->frame_limit->setValue(Settings::values.frame_limit.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue());
} else {
ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue() &&
use_frame_limit != ConfigurationShared::CheckState::Global);
}
}
void ConfigureGeneral::ApplyConfiguration() {
if (Settings::IsConfiguringGlobal()) {
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
UISettings::values.pause_when_in_background = ui->toggle_background_pause->isChecked();
UISettings::values.hide_mouse = ui->toggle_hide_mouse->isChecked();
// Guard if during game and set to game-specific value
if (Settings::values.use_frame_limit.UsingGlobal()) {
Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() ==
Qt::Checked);
Settings::values.frame_limit.SetValue(ui->frame_limit->value());
}
if (Settings::values.use_multi_core.UsingGlobal()) {
Settings::values.use_multi_core.SetValue(ui->use_multi_core->isChecked());
}
} else {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core,
ui->use_multi_core, use_multi_core);
bool global_frame_limit = use_frame_limit == ConfigurationShared::CheckState::Global;
Settings::values.use_frame_limit.SetGlobal(global_frame_limit);
Settings::values.frame_limit.SetGlobal(global_frame_limit);
if (!global_frame_limit) {
Settings::values.use_frame_limit.SetValue(ui->toggle_frame_limit->checkState() ==
Qt::Checked);
Settings::values.frame_limit.SetValue(ui->frame_limit->value());
}
}
}
void ConfigureGeneral::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureGeneral::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureGeneral::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->toggle_frame_limit->setEnabled(Settings::values.use_frame_limit.UsingGlobal());
ui->frame_limit->setEnabled(Settings::values.frame_limit.UsingGlobal());
return;
}
ui->toggle_check_exit->setVisible(false);
ui->toggle_user_on_boot->setVisible(false);
ui->toggle_background_pause->setVisible(false);
ui->toggle_hide_mouse->setVisible(false);
ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit,
Settings::values.use_frame_limit, use_frame_limit);
ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,
use_multi_core);
connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, [this]() {
ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked() &&
(use_frame_limit != ConfigurationShared::CheckState::Global));
});
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
namespace ConfigurationShared {
enum class CheckState;
}
class HotkeyRegistry;
namespace Ui {
class ConfigureGeneral;
}
class ConfigureGeneral : public QWidget {
Q_OBJECT
public:
explicit ConfigureGeneral(QWidget* parent = nullptr);
~ConfigureGeneral() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void SetConfiguration();
void SetupPerGameUI();
std::unique_ptr<Ui::ConfigureGeneral> ui;
ConfigurationShared::CheckState use_frame_limit;
ConfigurationShared::CheckState use_multi_core;
};
+113
View File
@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureGeneral</class>
<widget class="QWidget" name="ConfigureGeneral">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>407</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="HorizontalLayout">
<item>
<layout class="QVBoxLayout" name="VerticalLayout">
<item>
<widget class="QGroupBox" name="GeneralGroupBox">
<property name="title">
<string>General</string>
</property>
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
<item>
<layout class="QVBoxLayout" name="GeneralVerticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_frame_limit">
<property name="text">
<string>Limit Speed Percent</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="frame_limit">
<property name="suffix">
<string>%</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999</number>
</property>
<property name="value">
<number>100</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="use_multi_core">
<property name="text">
<string>Multicore CPU Emulation</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_check_exit">
<property name="text">
<string>Confirm exit while emulation is running</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_user_on_boot">
<property name="text">
<string>Prompt for user on game boot</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_background_pause">
<property name="text">
<string>Pause emulation when in background</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_hide_mouse">
<property name="text">
<string>Hide mouse on inactivity</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+267
View File
@@ -0,0 +1,267 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QColorDialog>
#include <QComboBox>
#include <QVulkanInstance>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_graphics.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics.h"
ConfigureGraphics::ConfigureGraphics(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGraphics) {
vulkan_device = Settings::values.vulkan_device.GetValue();
RetrieveVulkanDevices();
ui->setupUi(this);
SetupPerGameUI();
SetConfiguration();
connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] {
UpdateDeviceComboBox();
if (!Settings::IsConfiguringGlobal()) {
ConfigurationShared::SetHighlight(
ui->api_layout, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX);
}
});
connect(ui->device, qOverload<int>(&QComboBox::activated), this,
[this](int device) { UpdateDeviceSelection(device); });
connect(ui->bg_button, &QPushButton::clicked, this, [this] {
const QColor new_bg_color = QColorDialog::getColor(bg_color);
if (!new_bg_color.isValid()) {
return;
}
UpdateBackgroundColorButton(new_bg_color);
});
ui->bg_label->setVisible(Settings::IsConfiguringGlobal());
ui->bg_combobox->setVisible(!Settings::IsConfiguringGlobal());
}
void ConfigureGraphics::UpdateDeviceSelection(int device) {
if (device == -1) {
return;
}
if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) {
vulkan_device = device;
}
}
ConfigureGraphics::~ConfigureGraphics() = default;
void ConfigureGraphics::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
ui->api->setEnabled(runtime_lock);
ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
ui->use_disk_shader_cache->setEnabled(runtime_lock);
ui->use_nvdec_emulation->setEnabled(runtime_lock);
ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
ui->use_asynchronous_gpu_emulation->setChecked(
Settings::values.use_asynchronous_gpu_emulation.GetValue());
ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue()));
ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
} else {
ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend);
ConfigurationShared::SetHighlight(ui->api_layout,
!Settings::values.renderer_backend.UsingGlobal());
ConfigurationShared::SetPerGameSetting(ui->aspect_ratio_combobox,
&Settings::values.aspect_ratio);
ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1);
ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal());
ConfigurationShared::SetHighlight(ui->ar_label,
!Settings::values.aspect_ratio.UsingGlobal());
ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal());
}
UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red.GetValue(),
Settings::values.bg_green.GetValue(),
Settings::values.bg_blue.GetValue()));
UpdateDeviceComboBox();
}
void ConfigureGraphics::ApplyConfiguration() {
if (Settings::IsConfiguringGlobal()) {
// Guard if during game and set to game-specific value
if (Settings::values.renderer_backend.UsingGlobal()) {
Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
}
if (Settings::values.vulkan_device.UsingGlobal()) {
Settings::values.vulkan_device.SetValue(vulkan_device);
}
if (Settings::values.aspect_ratio.UsingGlobal()) {
Settings::values.aspect_ratio.SetValue(ui->aspect_ratio_combobox->currentIndex());
}
if (Settings::values.use_disk_shader_cache.UsingGlobal()) {
Settings::values.use_disk_shader_cache.SetValue(ui->use_disk_shader_cache->isChecked());
}
if (Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()) {
Settings::values.use_asynchronous_gpu_emulation.SetValue(
ui->use_asynchronous_gpu_emulation->isChecked());
}
if (Settings::values.use_nvdec_emulation.UsingGlobal()) {
Settings::values.use_nvdec_emulation.SetValue(ui->use_nvdec_emulation->isChecked());
}
if (Settings::values.bg_red.UsingGlobal()) {
Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF()));
Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF()));
Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF()));
}
} else {
if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.renderer_backend.SetGlobal(true);
Settings::values.vulkan_device.SetGlobal(true);
} else {
Settings::values.renderer_backend.SetGlobal(false);
Settings::values.renderer_backend.SetValue(GetCurrentGraphicsBackend());
if (GetCurrentGraphicsBackend() == Settings::RendererBackend::Vulkan) {
Settings::values.vulkan_device.SetGlobal(false);
Settings::values.vulkan_device.SetValue(vulkan_device);
} else {
Settings::values.vulkan_device.SetGlobal(true);
}
}
ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio,
ui->aspect_ratio_combobox);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_disk_shader_cache,
ui->use_disk_shader_cache, use_disk_shader_cache);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_gpu_emulation,
ui->use_asynchronous_gpu_emulation,
use_asynchronous_gpu_emulation);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation,
ui->use_nvdec_emulation, use_nvdec_emulation);
if (ui->bg_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.bg_red.SetGlobal(true);
Settings::values.bg_green.SetGlobal(true);
Settings::values.bg_blue.SetGlobal(true);
} else {
Settings::values.bg_red.SetGlobal(false);
Settings::values.bg_green.SetGlobal(false);
Settings::values.bg_blue.SetGlobal(false);
Settings::values.bg_red.SetValue(static_cast<float>(bg_color.redF()));
Settings::values.bg_green.SetValue(static_cast<float>(bg_color.greenF()));
Settings::values.bg_blue.SetValue(static_cast<float>(bg_color.blueF()));
}
}
}
void ConfigureGraphics::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureGraphics::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureGraphics::UpdateBackgroundColorButton(QColor color) {
bg_color = color;
QPixmap pixmap(ui->bg_button->size());
pixmap.fill(bg_color);
const QIcon color_icon(pixmap);
ui->bg_button->setIcon(color_icon);
}
void ConfigureGraphics::UpdateDeviceComboBox() {
ui->device->clear();
bool enabled = false;
if (!Settings::IsConfiguringGlobal() &&
ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
vulkan_device = Settings::values.vulkan_device.GetValue();
}
switch (GetCurrentGraphicsBackend()) {
case Settings::RendererBackend::OpenGL:
ui->device->addItem(tr("OpenGL Graphics Device"));
enabled = false;
break;
case Settings::RendererBackend::Vulkan:
for (const auto& device : vulkan_devices) {
ui->device->addItem(device);
}
ui->device->setCurrentIndex(vulkan_device);
enabled = !vulkan_devices.empty();
break;
}
// If in per-game config and use global is selected, don't enable.
enabled &= !(!Settings::IsConfiguringGlobal() &&
ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX);
ui->device->setEnabled(enabled && !Core::System::GetInstance().IsPoweredOn());
}
void ConfigureGraphics::RetrieveVulkanDevices() {
vulkan_devices.clear();
for (const auto& name : Vulkan::RendererVulkan::EnumerateDevices()) {
vulkan_devices.push_back(QString::fromStdString(name));
}
}
Settings::RendererBackend ConfigureGraphics::GetCurrentGraphicsBackend() const {
if (Settings::IsConfiguringGlobal()) {
return static_cast<Settings::RendererBackend>(ui->api->currentIndex());
}
if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.renderer_backend.SetGlobal(true);
return Settings::values.renderer_backend.GetValue();
}
Settings::values.renderer_backend.SetGlobal(false);
return static_cast<Settings::RendererBackend>(ui->api->currentIndex() -
ConfigurationShared::USE_GLOBAL_OFFSET);
}
void ConfigureGraphics::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->api->setEnabled(Settings::values.renderer_backend.UsingGlobal());
ui->device->setEnabled(Settings::values.renderer_backend.UsingGlobal());
ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal());
ui->use_asynchronous_gpu_emulation->setEnabled(
Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal());
ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());
ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal());
return;
}
connect(ui->bg_combobox, qOverload<int>(&QComboBox::activated), this, [this](int index) {
ui->bg_button->setEnabled(index == 1);
ConfigurationShared::SetHighlight(ui->bg_layout, index == 1);
});
ConfigurationShared::SetColoredTristate(
ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
ConfigurationShared::SetColoredTristate(
ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation);
ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,
Settings::values.use_asynchronous_gpu_emulation,
use_asynchronous_gpu_emulation);
ConfigurationShared::SetColoredComboBox(ui->aspect_ratio_combobox, ui->ar_label,
Settings::values.aspect_ratio.GetValue(true));
ConfigurationShared::InsertGlobalItem(
ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include <QString>
#include <QWidget>
#include "core/settings.h"
namespace ConfigurationShared {
enum class CheckState;
}
namespace Ui {
class ConfigureGraphics;
}
class ConfigureGraphics : public QWidget {
Q_OBJECT
public:
explicit ConfigureGraphics(QWidget* parent = nullptr);
~ConfigureGraphics() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void SetConfiguration();
void UpdateBackgroundColorButton(QColor color);
void UpdateDeviceComboBox();
void UpdateDeviceSelection(int device);
void RetrieveVulkanDevices();
void SetupPerGameUI();
Settings::RendererBackend GetCurrentGraphicsBackend() const;
std::unique_ptr<Ui::ConfigureGraphics> ui;
QColor bg_color;
ConfigurationShared::CheckState use_nvdec_emulation;
ConfigurationShared::CheckState use_disk_shader_cache;
ConfigurationShared::CheckState use_asynchronous_gpu_emulation;
std::vector<QString> vulkan_devices;
u32 vulkan_device{};
};
+258
View File
@@ -0,0 +1,258 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureGraphics</class>
<widget class="QWidget" name="ConfigureGraphics">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>437</width>
<height>321</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>API Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="api_layout" native="true">
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="horizontalSpacing">
<number>6</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="api_label">
<property name="text">
<string>API:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="api">
<item>
<property name="text">
<string notr="true">OpenGL</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">Vulkan</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="device_label">
<property name="text">
<string>Device:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="device"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Graphics Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QCheckBox" name="use_disk_shader_cache">
<property name="text">
<string>Use disk shader cache</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="use_asynchronous_gpu_emulation">
<property name="text">
<string>Use asynchronous GPU emulation</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="use_nvdec_emulation">
<property name="text">
<string>Use NVDEC emulation</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="aspect_ratio_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="ar_label">
<property name="text">
<string>Aspect Ratio:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="aspect_ratio_combobox">
<item>
<property name="text">
<string>Default (16:9)</string>
</property>
</item>
<item>
<property name="text">
<string>Force 4:3</string>
</property>
</item>
<item>
<property name="text">
<string>Force 21:9</string>
</property>
</item>
<item>
<property name="text">
<string>Stretch to Window</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="bg_layout" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QComboBox" name="bg_combobox">
<property name="currentText">
<string>Use global background color</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<property name="maxVisibleItems">
<number>10</number>
</property>
<item>
<property name="text">
<string>Use global background color</string>
</property>
</item>
<item>
<property name="text">
<string>Set background color:</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLabel" name="bg_label">
<property name="text">
<string>Background Color:</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="bg_button">
<property name="maximumSize">
<size>
<width>40</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+148
View File
@@ -0,0 +1,148 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_graphics_advanced.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics_advanced.h"
ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGraphicsAdvanced) {
ui->setupUi(this);
SetupPerGameUI();
SetConfiguration();
}
ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default;
void ConfigureGraphicsAdvanced::SetConfiguration() {
const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn();
ui->use_vsync->setEnabled(runtime_lock);
ui->use_assembly_shaders->setEnabled(runtime_lock);
ui->use_asynchronous_shaders->setEnabled(runtime_lock);
ui->anisotropic_filtering_combobox->setEnabled(runtime_lock);
ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue());
ui->use_assembly_shaders->setChecked(Settings::values.use_assembly_shaders.GetValue());
ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setCurrentIndex(
static_cast<int>(Settings::values.gpu_accuracy.GetValue()));
ui->anisotropic_filtering_combobox->setCurrentIndex(
Settings::values.max_anisotropy.GetValue());
} else {
ConfigurationShared::SetPerGameSetting(ui->gpu_accuracy, &Settings::values.gpu_accuracy);
ConfigurationShared::SetPerGameSetting(ui->anisotropic_filtering_combobox,
&Settings::values.max_anisotropy);
ConfigurationShared::SetHighlight(ui->label_gpu_accuracy,
!Settings::values.gpu_accuracy.UsingGlobal());
ConfigurationShared::SetHighlight(ui->af_label,
!Settings::values.max_anisotropy.UsingGlobal());
}
}
void ConfigureGraphicsAdvanced::ApplyConfiguration() {
// Subtract 2 if configuring per-game (separator and "use global configuration" take 2 slots)
const auto gpu_accuracy = static_cast<Settings::GPUAccuracy>(
ui->gpu_accuracy->currentIndex() -
((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
if (Settings::IsConfiguringGlobal()) {
// Must guard in case of a during-game configuration when set to be game-specific.
if (Settings::values.gpu_accuracy.UsingGlobal()) {
Settings::values.gpu_accuracy.SetValue(gpu_accuracy);
}
if (Settings::values.use_vsync.UsingGlobal()) {
Settings::values.use_vsync.SetValue(ui->use_vsync->isChecked());
}
if (Settings::values.use_assembly_shaders.UsingGlobal()) {
Settings::values.use_assembly_shaders.SetValue(ui->use_assembly_shaders->isChecked());
}
if (Settings::values.use_asynchronous_shaders.UsingGlobal()) {
Settings::values.use_asynchronous_shaders.SetValue(
ui->use_asynchronous_shaders->isChecked());
}
if (Settings::values.use_asynchronous_shaders.UsingGlobal()) {
Settings::values.use_asynchronous_shaders.SetValue(
ui->use_asynchronous_shaders->isChecked());
}
if (Settings::values.use_fast_gpu_time.UsingGlobal()) {
Settings::values.use_fast_gpu_time.SetValue(ui->use_fast_gpu_time->isChecked());
}
if (Settings::values.max_anisotropy.UsingGlobal()) {
Settings::values.max_anisotropy.SetValue(
ui->anisotropic_filtering_combobox->currentIndex());
}
} else {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
ui->anisotropic_filtering_combobox);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync,
use_vsync);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_assembly_shaders,
ui->use_assembly_shaders, use_assembly_shaders);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders,
ui->use_asynchronous_shaders,
use_asynchronous_shaders);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,
ui->use_fast_gpu_time, use_fast_gpu_time);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
ui->anisotropic_filtering_combobox);
if (ui->gpu_accuracy->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.gpu_accuracy.SetGlobal(true);
} else {
Settings::values.gpu_accuracy.SetGlobal(false);
Settings::values.gpu_accuracy.SetValue(gpu_accuracy);
}
}
}
void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureGraphicsAdvanced::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureGraphicsAdvanced::SetupPerGameUI() {
// Disable if not global (only happens during game)
if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal());
ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal());
ui->use_assembly_shaders->setEnabled(Settings::values.use_assembly_shaders.UsingGlobal());
ui->use_asynchronous_shaders->setEnabled(
Settings::values.use_asynchronous_shaders.UsingGlobal());
ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal());
ui->anisotropic_filtering_combobox->setEnabled(
Settings::values.max_anisotropy.UsingGlobal());
return;
}
ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync);
ConfigurationShared::SetColoredTristate(
ui->use_assembly_shaders, Settings::values.use_assembly_shaders, use_assembly_shaders);
ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders,
Settings::values.use_asynchronous_shaders,
use_asynchronous_shaders);
ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,
Settings::values.use_fast_gpu_time, use_fast_gpu_time);
ConfigurationShared::SetColoredComboBox(
ui->gpu_accuracy, ui->label_gpu_accuracy,
static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
ConfigurationShared::SetColoredComboBox(
ui->anisotropic_filtering_combobox, ui->af_label,
static_cast<int>(Settings::values.max_anisotropy.GetValue(true)));
}
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
namespace ConfigurationShared {
enum class CheckState;
}
namespace Ui {
class ConfigureGraphicsAdvanced;
}
class ConfigureGraphicsAdvanced : public QWidget {
Q_OBJECT
public:
explicit ConfigureGraphicsAdvanced(QWidget* parent = nullptr);
~ConfigureGraphicsAdvanced() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void SetConfiguration();
void SetupPerGameUI();
std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui;
ConfigurationShared::CheckState use_vsync;
ConfigurationShared::CheckState use_assembly_shaders;
ConfigurationShared::CheckState use_asynchronous_shaders;
ConfigurationShared::CheckState use_fast_gpu_time;
};
+182
View File
@@ -0,0 +1,182 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureGraphicsAdvanced</class>
<widget class="QWidget" name="ConfigureGraphicsAdvanced">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>404</width>
<height>321</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_1">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_1">
<property name="title">
<string>Advanced Graphics Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="gpu_accuracy_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_gpu_accuracy">
<property name="text">
<string>Accuracy Level:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="gpu_accuracy">
<item>
<property name="text">
<string notr="true">Normal</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">High</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">Extreme(very slow)</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="use_vsync">
<property name="toolTip">
<string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string>
</property>
<property name="text">
<string>Use VSync (OpenGL only)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="use_assembly_shaders">
<property name="toolTip">
<string>Enabling this reduces shader stutter. Enables OpenGL assembly shaders on supported Nvidia devices (NV_gpu_program5 is required). This feature is experimental.</string>
</property>
<property name="text">
<string>Use assembly shaders (experimental, Nvidia OpenGL only)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="use_asynchronous_shaders">
<property name="toolTip">
<string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string>
</property>
<property name="text">
<string>Use asynchronous shader building (experimental)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="use_fast_gpu_time">
<property name="text">
<string>Use Fast GPU Time</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="af_layout" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_1">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="af_label">
<property name="text">
<string>Anisotropic Filtering:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="anisotropic_filtering_combobox">
<item>
<property name="text">
<string>Default</string>
</property>
</item>
<item>
<property name="text">
<string>2x</string>
</property>
</item>
<item>
<property name="text">
<string>4x</string>
</property>
</item>
<item>
<property name="text">
<string>8x</string>
</property>
</item>
<item>
<property name="text">
<string>16x</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+192
View File
@@ -0,0 +1,192 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QMenu>
#include <QMessageBox>
#include <QStandardItemModel>
#include "core/settings.h"
#include "ui_configure_hotkeys.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_hotkeys.h"
#include "yuzu/hotkeys.h"
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
model = new QStandardItemModel(this);
model->setColumnCount(3);
connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
connect(ui->hotkey_list, &QTreeView::customContextMenuRequested, this,
&ConfigureHotkeys::PopupContextMenu);
ui->hotkey_list->setContextMenuPolicy(Qt::CustomContextMenu);
ui->hotkey_list->setModel(model);
// TODO(Kloen): Make context configurable as well (hiding the column for now)
ui->hotkey_list->hideColumn(2);
ui->hotkey_list->setColumnWidth(0, 200);
ui->hotkey_list->resizeColumnToContents(1);
connect(ui->button_restore_defaults, &QPushButton::clicked, this,
&ConfigureHotkeys::RestoreDefaults);
connect(ui->button_clear_all, &QPushButton::clicked, this, &ConfigureHotkeys::ClearAll);
RetranslateUI();
}
ConfigureHotkeys::~ConfigureHotkeys() = default;
void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
for (const auto& group : registry.hotkey_groups) {
auto* parent_item = new QStandardItem(group.first);
parent_item->setEditable(false);
for (const auto& hotkey : group.second) {
auto* action = new QStandardItem(hotkey.first);
auto* keyseq =
new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
action->setEditable(false);
keyseq->setEditable(false);
parent_item->appendRow({action, keyseq});
}
model->appendRow(parent_item);
}
ui->hotkey_list->expandAll();
ui->hotkey_list->resizeColumnToContents(0);
}
void ConfigureHotkeys::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureHotkeys::RetranslateUI() {
ui->retranslateUi(this);
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
}
void ConfigureHotkeys::Configure(QModelIndex index) {
if (!index.parent().isValid()) {
return;
}
index = index.sibling(index.row(), 1);
const auto previous_key = model->data(index);
SequenceDialog hotkey_dialog{this};
const int return_code = hotkey_dialog.exec();
const auto key_sequence = hotkey_dialog.GetSequence();
if (return_code == QDialog::Rejected || key_sequence.isEmpty()) {
return;
}
const auto [key_sequence_used, used_action] = IsUsedKey(key_sequence);
if (key_sequence_used && key_sequence != QKeySequence(previous_key.toString())) {
QMessageBox::warning(
this, tr("Conflicting Key Sequence"),
tr("The entered key sequence is already assigned to: %1").arg(used_action));
} else {
model->setData(index, key_sequence.toString(QKeySequence::NativeText));
}
}
std::pair<bool, QString> ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const {
for (int r = 0; r < model->rowCount(); ++r) {
const QStandardItem* const parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
const QStandardItem* const key_seq_item = parent->child(r2, 1);
const auto key_seq_str = key_seq_item->text();
const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText);
if (key_sequence == key_seq) {
return std::make_pair(true, parent->child(r2, 0)->text());
}
}
}
return std::make_pair(false, QString());
}
void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) {
for (int key_id = 0; key_id < model->rowCount(); key_id++) {
const QStandardItem* parent = model->item(key_id, 0);
for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
const QStandardItem* action = parent->child(key_column_id, 0);
const QStandardItem* keyseq = parent->child(key_column_id, 1);
for (auto& [group, sub_actions] : registry.hotkey_groups) {
if (group != parent->text())
continue;
for (auto& [action_name, hotkey] : sub_actions) {
if (action_name != action->text())
continue;
hotkey.keyseq = QKeySequence(keyseq->text());
}
}
}
}
registry.SaveHotkeys();
}
void ConfigureHotkeys::RestoreDefaults() {
for (int r = 0; r < model->rowCount(); ++r) {
const QStandardItem* parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
model->item(r, 0)->child(r2, 1)->setText(Config::default_hotkeys[r2].shortcut.first);
}
}
}
void ConfigureHotkeys::ClearAll() {
for (int r = 0; r < model->rowCount(); ++r) {
const QStandardItem* parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); ++r2) {
model->item(r, 0)->child(r2, 1)->setText(QString{});
}
}
}
void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) {
QModelIndex index = ui->hotkey_list->indexAt(menu_location);
if (!index.parent().isValid()) {
return;
}
const auto selected = index.sibling(index.row(), 1);
QMenu context_menu;
QAction* restore_default = context_menu.addAction(tr("Restore Default"));
QAction* clear = context_menu.addAction(tr("Clear"));
connect(restore_default, &QAction::triggered, [this, selected] {
const QKeySequence& default_key_sequence = QKeySequence::fromString(
Config::default_hotkeys[selected.row()].shortcut.first, QKeySequence::NativeText);
const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence);
if (key_sequence_used &&
default_key_sequence != QKeySequence(model->data(selected).toString())) {
QMessageBox::warning(
this, tr("Conflicting Key Sequence"),
tr("The default key sequence is already assigned to: %1").arg(used_action));
} else {
model->setData(selected, default_key_sequence.toString(QKeySequence::NativeText));
}
});
connect(clear, &QAction::triggered, [this, selected] { model->setData(selected, QString{}); });
context_menu.exec(ui->hotkey_list->viewport()->mapToGlobal(menu_location));
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
namespace Ui {
class ConfigureHotkeys;
}
class HotkeyRegistry;
class QStandardItemModel;
class ConfigureHotkeys : public QWidget {
Q_OBJECT
public:
explicit ConfigureHotkeys(QWidget* parent = nullptr);
~ConfigureHotkeys() override;
void ApplyConfiguration(HotkeyRegistry& registry);
/**
* Populates the hotkey list widget using data from the provided registry.
* Called everytime the Configure dialog is opened.
* @param registry The HotkeyRegistry whose data is used to populate the list.
*/
void Populate(const HotkeyRegistry& registry);
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void Configure(QModelIndex index);
std::pair<bool, QString> IsUsedKey(QKeySequence key_sequence) const;
void RestoreDefaults();
void ClearAll();
void PopupContextMenu(const QPoint& menu_location);
std::unique_ptr<Ui::ConfigureHotkeys> ui;
QStandardItemModel* model;
};
+73
View File
@@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureHotkeys</class>
<widget class="QWidget" name="ConfigureHotkeys">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>439</width>
<height>510</height>
</rect>
</property>
<property name="windowTitle">
<string>Hotkey Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Double-click on a binding to change it.</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_clear_all">
<property name="text">
<string>Clear All</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_restore_defaults">
<property name="text">
<string>Restore Defaults</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTreeView" name="hotkey_list">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+273
View File
@@ -0,0 +1,273 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <QSignalBlocker>
#include <QTimer>
#include "core/core.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/sm/sm.h"
#include "ui_configure_input.h"
#include "ui_configure_input_advanced.h"
#include "ui_configure_input_player.h"
#include "yuzu/configuration/configure_debug_controller.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_input_advanced.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_motion_touch.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"
#include "yuzu/configuration/configure_vibration.h"
#include "yuzu/configuration/input_profiles.h"
namespace {
template <typename Dialog, typename... Args>
void CallConfigureDialog(ConfigureInput& parent, Args&&... args) {
Dialog dialog(&parent, std::forward<Args>(args)...);
const auto res = dialog.exec();
if (res == QDialog::Accepted) {
dialog.ApplyConfiguration();
}
}
} // Anonymous namespace
void OnDockedModeChanged(bool last_state, bool new_state) {
if (last_state == new_state) {
return;
}
Core::System& system{Core::System::GetInstance()};
if (!system.IsPoweredOn()) {
return;
}
Service::SM::ServiceManager& sm = system.ServiceManager();
// Message queue is shared between these services, we just need to signal an operation
// change to one and it will handle both automatically
auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
bool has_signalled = false;
if (applet_oe != nullptr) {
applet_oe->GetMessageQueue()->OperationModeChanged();
has_signalled = true;
}
if (applet_ae != nullptr && !has_signalled) {
applet_ae->GetMessageQueue()->OperationModeChanged();
}
}
ConfigureInput::ConfigureInput(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
profiles(std::make_unique<InputProfiles>()) {
ui->setupUi(this);
}
ConfigureInput::~ConfigureInput() = default;
void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
std::size_t max_players) {
player_controllers = {
new ConfigureInputPlayer(this, 0, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 1, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 2, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 3, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 4, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 5, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 6, ui->consoleInputSettings, input_subsystem,
profiles.get()),
new ConfigureInputPlayer(this, 7, ui->consoleInputSettings, input_subsystem,
profiles.get()),
};
player_tabs = {
ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4,
ui->tabPlayer5, ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8,
};
player_connected = {
ui->checkboxPlayer1Connected, ui->checkboxPlayer2Connected, ui->checkboxPlayer3Connected,
ui->checkboxPlayer4Connected, ui->checkboxPlayer5Connected, ui->checkboxPlayer6Connected,
ui->checkboxPlayer7Connected, ui->checkboxPlayer8Connected,
};
std::array<QLabel*, 8> player_connected_labels = {
ui->label, ui->label_3, ui->label_4, ui->label_5,
ui->label_6, ui->label_7, ui->label_8, ui->label_9,
};
for (std::size_t i = 0; i < player_tabs.size(); ++i) {
player_tabs[i]->setLayout(new QHBoxLayout(player_tabs[i]));
player_tabs[i]->layout()->addWidget(player_controllers[i]);
connect(player_controllers[i], &ConfigureInputPlayer::Connected, [&, i](bool is_connected) {
if (is_connected) {
for (std::size_t index = 0; index <= i; ++index) {
player_connected[index]->setChecked(is_connected);
}
} else {
for (std::size_t index = i; index < player_tabs.size(); ++index) {
player_connected[index]->setChecked(is_connected);
}
}
});
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputDevices, this,
&ConfigureInput::UpdateAllInputDevices);
connect(player_controllers[i], &ConfigureInputPlayer::RefreshInputProfiles, this,
&ConfigureInput::UpdateAllInputProfiles, Qt::QueuedConnection);
connect(player_connected[i], &QCheckBox::stateChanged, [this, i](int state) {
player_controllers[i]->ConnectPlayer(state == Qt::Checked);
});
// Remove/hide all the elements that exceed max_players, if applicable.
if (i >= max_players) {
ui->tabWidget->removeTab(static_cast<int>(max_players));
player_connected[i]->hide();
player_connected_labels[i]->hide();
}
}
// Only the first player can choose handheld mode so connect the signal just to player 1
connect(player_controllers[0], &ConfigureInputPlayer::HandheldStateChanged,
[this](bool is_handheld) { UpdateDockedState(is_handheld); });
advanced = new ConfigureInputAdvanced(this);
ui->tabAdvanced->setLayout(new QHBoxLayout(ui->tabAdvanced));
ui->tabAdvanced->layout()->addWidget(advanced);
connect(advanced, &ConfigureInputAdvanced::CallDebugControllerDialog, [this, input_subsystem] {
CallConfigureDialog<ConfigureDebugController>(*this, input_subsystem, profiles.get());
});
connect(advanced, &ConfigureInputAdvanced::CallMouseConfigDialog, [this, input_subsystem] {
CallConfigureDialog<ConfigureMouseAdvanced>(*this, input_subsystem);
});
connect(advanced, &ConfigureInputAdvanced::CallTouchscreenConfigDialog,
[this] { CallConfigureDialog<ConfigureTouchscreenAdvanced>(*this); });
connect(advanced, &ConfigureInputAdvanced::CallMotionTouchConfigDialog,
[this, input_subsystem] {
CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
});
connect(ui->vibrationButton, &QPushButton::clicked,
[this] { CallConfigureDialog<ConfigureVibration>(*this); });
connect(ui->motionButton, &QPushButton::clicked, [this, input_subsystem] {
CallConfigureDialog<ConfigureMotionTouch>(*this, input_subsystem);
});
connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
RetranslateUI();
LoadConfiguration();
}
QList<QWidget*> ConfigureInput::GetSubTabs() const {
return {
ui->tabPlayer1, ui->tabPlayer2, ui->tabPlayer3, ui->tabPlayer4, ui->tabPlayer5,
ui->tabPlayer6, ui->tabPlayer7, ui->tabPlayer8, ui->tabAdvanced,
};
}
void ConfigureInput::ApplyConfiguration() {
for (auto controller : player_controllers) {
controller->ApplyConfiguration();
}
advanced->ApplyConfiguration();
const bool pre_docked_mode = Settings::values.use_docked_mode.GetValue();
Settings::values.use_docked_mode.SetValue(ui->radioDocked->isChecked());
OnDockedModeChanged(pre_docked_mode, Settings::values.use_docked_mode.GetValue());
Settings::values.vibration_enabled.SetValue(ui->vibrationGroup->isChecked());
Settings::values.motion_enabled.SetValue(ui->motionGroup->isChecked());
}
void ConfigureInput::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureInput::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureInput::LoadConfiguration() {
LoadPlayerControllerIndices();
UpdateDockedState(Settings::values.players.GetValue()[8].connected);
ui->vibrationGroup->setChecked(Settings::values.vibration_enabled.GetValue());
ui->motionGroup->setChecked(Settings::values.motion_enabled.GetValue());
}
void ConfigureInput::LoadPlayerControllerIndices() {
for (std::size_t i = 0; i < player_connected.size(); ++i) {
const auto connected = Settings::values.players.GetValue()[i].connected ||
(i == 0 && Settings::values.players.GetValue()[8].connected);
player_connected[i]->setChecked(connected);
}
}
void ConfigureInput::ClearAll() {
// We don't have a good way to know what tab is active, but we can find out by getting the
// parent of the consoleInputSettings
auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
player_tab->ClearAll();
}
void ConfigureInput::RestoreDefaults() {
// We don't have a good way to know what tab is active, but we can find out by getting the
// parent of the consoleInputSettings
auto* player_tab = static_cast<ConfigureInputPlayer*>(ui->consoleInputSettings->parent());
player_tab->RestoreDefaults();
ui->radioDocked->setChecked(true);
ui->radioUndocked->setChecked(false);
ui->vibrationGroup->setChecked(true);
ui->motionGroup->setChecked(true);
}
void ConfigureInput::UpdateDockedState(bool is_handheld) {
// Disallow changing the console mode if the controller type is handheld.
ui->radioDocked->setEnabled(!is_handheld);
ui->radioUndocked->setEnabled(!is_handheld);
ui->radioDocked->setChecked(Settings::values.use_docked_mode.GetValue());
ui->radioUndocked->setChecked(!Settings::values.use_docked_mode.GetValue());
// Also force into undocked mode if the controller type is handheld.
if (is_handheld) {
ui->radioUndocked->setChecked(true);
}
}
void ConfigureInput::UpdateAllInputDevices() {
for (const auto& player : player_controllers) {
player->UpdateInputDeviceCombobox();
}
}
void ConfigureInput::UpdateAllInputProfiles(std::size_t player_index) {
for (std::size_t i = 0; i < player_controllers.size(); ++i) {
if (i == player_index) {
continue;
}
player_controllers[i]->UpdateInputProfiles();
}
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <QKeyEvent>
#include <QList>
#include <QWidget>
class QCheckBox;
class QString;
class QTimer;
class ConfigureInputAdvanced;
class ConfigureInputPlayer;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
namespace Ui {
class ConfigureInput;
}
void OnDockedModeChanged(bool last_state, bool new_state);
class ConfigureInput : public QWidget {
Q_OBJECT
public:
explicit ConfigureInput(QWidget* parent = nullptr);
~ConfigureInput() override;
/// Initializes the input dialog with the given input subsystem.
void Initialize(InputCommon::InputSubsystem* input_subsystem_, std::size_t max_players = 8);
/// Save all button configurations to settings file.
void ApplyConfiguration();
QList<QWidget*> GetSubTabs() const;
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void ClearAll();
void UpdateDockedState(bool is_handheld);
void UpdateAllInputDevices();
void UpdateAllInputProfiles(std::size_t player_index);
/// Load configuration settings.
void LoadConfiguration();
void LoadPlayerControllerIndices();
/// Restore all buttons to their default values.
void RestoreDefaults();
std::unique_ptr<Ui::ConfigureInput> ui;
std::unique_ptr<InputProfiles> profiles;
std::array<ConfigureInputPlayer*, 8> player_controllers;
std::array<QWidget*, 8> player_tabs;
std::array<QCheckBox*, 8> player_connected;
ConfigureInputAdvanced* advanced;
};
+548
View File
@@ -0,0 +1,548 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInput</class>
<widget class="QWidget" name="ConfigureInput">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>680</width>
<height>540</height>
</rect>
</property>
<property name="windowTitle">
<string>ConfigureInput</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabPlayer1">
<property name="accessibleName">
<string>Player 1</string>
</property>
<attribute name="title">
<string>Player 1</string>
</attribute>
</widget>
<widget class="QWidget" name="tabPlayer2">
<property name="accessibleName">
<string>Player 2</string>
</property>
<attribute name="title">
<string>Player 2</string>
</attribute>
</widget>
<widget class="QWidget" name="tabPlayer3">
<property name="accessibleName">
<string>Player 3</string>
</property>
<attribute name="title">
<string>Player 3</string>
</attribute>
</widget>
<widget class="QWidget" name="tabPlayer4">
<property name="accessibleName">
<string>Player 4</string>
</property>
<attribute name="title">
<string>Player 4</string>
</attribute>
</widget>
<widget class="QWidget" name="tabPlayer5">
<property name="accessibleName">
<string>Player 5</string>
</property>
<attribute name="title">
<string>Player 5</string>
</attribute>
</widget>
<widget class="QWidget" name="tabPlayer6">
<property name="accessibleName">
<string>Player 6</string>
</property>
<attribute name="title">
<string>Player 6</string>
</attribute>
</widget>
<widget class="QWidget" name="tabPlayer7">
<property name="accessibleName">
<string>Player 7</string>
</property>
<attribute name="title">
<string>Player 7</string>
</attribute>
</widget>
<widget class="QWidget" name="tabPlayer8">
<property name="accessibleName">
<string>Player 8</string>
</property>
<attribute name="title">
<string>Player 8</string>
</attribute>
</widget>
<widget class="QWidget" name="tabAdvanced">
<property name="accessibleName">
<string>Advanced</string>
</property>
<attribute name="title">
<string>Advanced</string>
</attribute>
</widget>
</widget>
</item>
<item alignment="Qt::AlignVCenter">
<widget class="QWidget" name="consoleInputSettings" native="true">
<layout class="QHBoxLayout" name="buttonsBottomRightHorizontalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignVCenter">
<widget class="QGroupBox" name="handheldGroup">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>Console Mode</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>8</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QRadioButton" name="radioDocked">
<property name="text">
<string>Docked</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioUndocked">
<property name="text">
<string>Undocked</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="vibrationGroup">
<property name="title">
<string>Vibration</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QPushButton" name="vibrationButton">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="motionGroup">
<property name="title">
<string>Motion</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QPushButton" name="motionButton">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item alignment="Qt::AlignVCenter">
<widget class="QWidget" name="connectedControllers" native="true">
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="spacing">
<number>3</number>
</property>
<item row="1" column="2">
<widget class="QCheckBox" name="checkboxPlayer2Connected">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Controllers</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QCheckBox" name="checkboxPlayer4Connected">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QCheckBox" name="checkboxPlayer3Connected">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QCheckBox" name="checkboxPlayer5Connected">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>1</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="7">
<widget class="QCheckBox" name="checkboxPlayer7Connected">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="6">
<widget class="QCheckBox" name="checkboxPlayer6Connected">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="checkboxPlayer1Connected">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="8">
<widget class="QCheckBox" name="checkboxPlayer8Connected">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>2</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_4">
<property name="text">
<string>3</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="label_5">
<property name="text">
<string>4</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QLabel" name="label_6">
<property name="text">
<string>5</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="6">
<widget class="QLabel" name="label_7">
<property name="text">
<string>6</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="7">
<widget class="QLabel" name="label_8">
<property name="text">
<string>7</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="8">
<widget class="QLabel" name="label_9">
<property name="text">
<string>8</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Connected</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item alignment="Qt::AlignBottom">
<widget class="QPushButton" name="buttonRestoreDefaults">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Defaults</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignBottom">
<widget class="QPushButton" name="buttonClearAll">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="sizeIncrement">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true">min-width: 68px;</string>
</property>
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+173
View File
@@ -0,0 +1,173 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QColorDialog>
#include "core/core.h"
#include "core/settings.h"
#include "ui_configure_input_advanced.h"
#include "yuzu/configuration/configure_input_advanced.h"
ConfigureInputAdvanced::ConfigureInputAdvanced(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureInputAdvanced>()) {
ui->setupUi(this);
controllers_color_buttons = {{
{
ui->player1_left_body_button,
ui->player1_left_buttons_button,
ui->player1_right_body_button,
ui->player1_right_buttons_button,
},
{
ui->player2_left_body_button,
ui->player2_left_buttons_button,
ui->player2_right_body_button,
ui->player2_right_buttons_button,
},
{
ui->player3_left_body_button,
ui->player3_left_buttons_button,
ui->player3_right_body_button,
ui->player3_right_buttons_button,
},
{
ui->player4_left_body_button,
ui->player4_left_buttons_button,
ui->player4_right_body_button,
ui->player4_right_buttons_button,
},
{
ui->player5_left_body_button,
ui->player5_left_buttons_button,
ui->player5_right_body_button,
ui->player5_right_buttons_button,
},
{
ui->player6_left_body_button,
ui->player6_left_buttons_button,
ui->player6_right_body_button,
ui->player6_right_buttons_button,
},
{
ui->player7_left_body_button,
ui->player7_left_buttons_button,
ui->player7_right_body_button,
ui->player7_right_buttons_button,
},
{
ui->player8_left_body_button,
ui->player8_left_buttons_button,
ui->player8_right_body_button,
ui->player8_right_buttons_button,
},
}};
for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
auto& color_buttons = controllers_color_buttons[player_idx];
for (std::size_t button_idx = 0; button_idx < color_buttons.size(); ++button_idx) {
connect(color_buttons[button_idx], &QPushButton::clicked, this,
[this, player_idx, button_idx] {
OnControllerButtonClick(player_idx, button_idx);
});
}
}
connect(ui->mouse_enabled, &QCheckBox::stateChanged, this,
&ConfigureInputAdvanced::UpdateUIEnabled);
connect(ui->debug_enabled, &QCheckBox::stateChanged, this,
&ConfigureInputAdvanced::UpdateUIEnabled);
connect(ui->touchscreen_enabled, &QCheckBox::stateChanged, this,
&ConfigureInputAdvanced::UpdateUIEnabled);
connect(ui->debug_configure, &QPushButton::clicked, this,
[this] { CallDebugControllerDialog(); });
connect(ui->mouse_advanced, &QPushButton::clicked, this, [this] { CallMouseConfigDialog(); });
connect(ui->touchscreen_advanced, &QPushButton::clicked, this,
[this] { CallTouchscreenConfigDialog(); });
connect(ui->buttonMotionTouch, &QPushButton::clicked, this,
&ConfigureInputAdvanced::CallMotionTouchConfigDialog);
LoadConfiguration();
}
ConfigureInputAdvanced::~ConfigureInputAdvanced() = default;
void ConfigureInputAdvanced::OnControllerButtonClick(std::size_t player_idx,
std::size_t button_idx) {
const QColor new_bg_color = QColorDialog::getColor(controllers_colors[player_idx][button_idx]);
if (!new_bg_color.isValid()) {
return;
}
controllers_colors[player_idx][button_idx] = new_bg_color;
controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
QStringLiteral("background-color: %1; min-width: 60px;")
.arg(controllers_colors[player_idx][button_idx].name()));
}
void ConfigureInputAdvanced::ApplyConfiguration() {
for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
auto& player = Settings::values.players.GetValue()[player_idx];
std::array<u32, 4> colors{};
std::transform(controllers_colors[player_idx].begin(), controllers_colors[player_idx].end(),
colors.begin(), [](QColor color) { return color.rgb(); });
player.body_color_left = colors[0];
player.button_color_left = colors[1];
player.body_color_right = colors[2];
player.button_color_right = colors[3];
}
Settings::values.debug_pad_enabled = ui->debug_enabled->isChecked();
Settings::values.mouse_enabled = ui->mouse_enabled->isChecked();
Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked();
Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked();
Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked();
}
void ConfigureInputAdvanced::LoadConfiguration() {
for (std::size_t player_idx = 0; player_idx < controllers_color_buttons.size(); ++player_idx) {
auto& player = Settings::values.players.GetValue()[player_idx];
std::array<u32, 4> colors = {
player.body_color_left,
player.button_color_left,
player.body_color_right,
player.button_color_right,
};
std::transform(colors.begin(), colors.end(), controllers_colors[player_idx].begin(),
[](u32 rgb) { return QColor::fromRgb(rgb); });
for (std::size_t button_idx = 0; button_idx < colors.size(); ++button_idx) {
controllers_color_buttons[player_idx][button_idx]->setStyleSheet(
QStringLiteral("background-color: %1; min-width: 60px;")
.arg(controllers_colors[player_idx][button_idx].name()));
}
}
ui->debug_enabled->setChecked(Settings::values.debug_pad_enabled);
ui->mouse_enabled->setChecked(Settings::values.mouse_enabled);
ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled);
ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard);
ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled);
UpdateUIEnabled();
}
void ConfigureInputAdvanced::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureInputAdvanced::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureInputAdvanced::UpdateUIEnabled() {
ui->mouse_advanced->setEnabled(ui->mouse_enabled->isChecked());
ui->debug_configure->setEnabled(ui->debug_enabled->isChecked());
ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked());
}
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <QWidget>
class QColor;
class QPushButton;
namespace Ui {
class ConfigureInputAdvanced;
}
class ConfigureInputAdvanced : public QWidget {
Q_OBJECT
public:
explicit ConfigureInputAdvanced(QWidget* parent = nullptr);
~ConfigureInputAdvanced() override;
void ApplyConfiguration();
signals:
void CallDebugControllerDialog();
void CallMouseConfigDialog();
void CallTouchscreenConfigDialog();
void CallMotionTouchConfigDialog();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void UpdateUIEnabled();
void OnControllerButtonClick(std::size_t player_idx, std::size_t button_idx);
void LoadConfiguration();
std::unique_ptr<Ui::ConfigureInputAdvanced> ui;
std::array<std::array<QColor, 4>, 8> controllers_colors;
std::array<std::array<QPushButton*, 4>, 8> controllers_color_buttons;
};
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+218
View File
@@ -0,0 +1,218 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <QWidget>
#include "common/param_package.h"
#include "core/settings.h"
#include "ui_configure_input.h"
class QCheckBox;
class QKeyEvent;
class QLabel;
class QPushButton;
class QSlider;
class QSpinBox;
class QString;
class QTimer;
class QWidget;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
namespace InputCommon::Polling {
class DevicePoller;
enum class DeviceType;
} // namespace InputCommon::Polling
namespace Ui {
class ConfigureInputPlayer;
}
class ConfigureInputPlayer : public QWidget {
Q_OBJECT
public:
explicit ConfigureInputPlayer(QWidget* parent, std::size_t player_index, QWidget* bottom_row,
InputCommon::InputSubsystem* input_subsystem_,
InputProfiles* profiles_, bool debug = false);
~ConfigureInputPlayer() override;
/// Save all button configurations to settings file.
void ApplyConfiguration();
/// Set the connection state checkbox (used to sync state).
void ConnectPlayer(bool connected);
/// Update the input devices combobox.
void UpdateInputDeviceCombobox();
/// Updates the list of controller profiles.
void UpdateInputProfiles();
/// Restore all buttons to their default values.
void RestoreDefaults();
/// Clear all input configuration.
void ClearAll();
signals:
/// Emitted when this controller is connected by the user.
void Connected(bool connected);
/// Emitted when the Handheld mode is selected (undocked with dual joycons attached).
void HandheldStateChanged(bool is_handheld);
/// Emitted when the input devices combobox is being refreshed.
void RefreshInputDevices();
/**
* Emitted when the input profiles combobox is being refreshed.
* The player_index represents the current player's index, and the profile combobox
* will not be updated for this index as they are already updated by other mechanisms.
*/
void RefreshInputProfiles(std::size_t player_index);
protected:
void showEvent(QShowEvent* event) override;
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
/// Load configuration settings.
void LoadConfiguration();
/// Called when the button was pressed.
void HandleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type);
/// Finish polling and configure input using the input_setter.
void SetPollingResult(const Common::ParamPackage& params, bool abort);
/// Checks whether a given input can be accepted.
bool IsInputAcceptable(const Common::ParamPackage& params) const;
/// Handle mouse button press events.
void mousePressEvent(QMouseEvent* event) override;
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
/// Update UI to reflect current configuration.
void UpdateUI();
/// Sets the available controllers.
void SetConnectableControllers();
/// Gets the Controller Type for a given controller combobox index.
Settings::ControllerType GetControllerTypeFromIndex(int index) const;
/// Gets the controller combobox index for a given Controller Type.
int GetIndexFromControllerType(Settings::ControllerType type) const;
/// Update the available input devices.
void UpdateInputDevices();
/// Update the current controller icon.
void UpdateControllerIcon();
/// Hides and disables controller settings based on the current controller type.
void UpdateControllerAvailableButtons();
/// Shows or hides motion groupboxes based on the current controller type.
void UpdateMotionButtons();
/// Gets the default controller mapping for this device and auto configures the input to match.
void UpdateMappingWithDefaults();
/// Creates a controller profile.
void CreateProfile();
/// Deletes the selected controller profile.
void DeleteProfile();
/// Loads the selected controller profile.
void LoadProfile();
/// Saves the current controller configuration into a selected controller profile.
void SaveProfile();
std::unique_ptr<Ui::ConfigureInputPlayer> ui;
std::size_t player_index;
bool debug;
InputCommon::InputSubsystem* input_subsystem;
InputProfiles* profiles;
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
/// Stores a pair of "Connected Controllers" combobox index and Controller Type enum.
std::vector<std::pair<int, Settings::ControllerType>> index_controller_type_pairs;
static constexpr int PLAYER_COUNT = 8;
std::array<QCheckBox*, PLAYER_COUNT> player_connected_checkbox;
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
std::array<Common::ParamPackage, Settings::NativeMotion::NumMotions> motions_param;
static constexpr int ANALOG_SUB_BUTTONS_NUM = 4;
/// Each button input is represented by a QPushButton.
std::array<QPushButton*, Settings::NativeButton::NumButtons> button_map;
/// A group of four QPushButtons represent one analog input. The buttons each represent up,
/// down, left, right, respectively.
std::array<std::array<QPushButton*, ANALOG_SUB_BUTTONS_NUM>, Settings::NativeAnalog::NumAnalogs>
analog_map_buttons;
/// Each motion input is represented by a QPushButton.
std::array<QPushButton*, Settings::NativeMotion::NumMotions> motion_map;
std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_label;
std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_deadzone_slider;
std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_groupbox;
std::array<QPushButton*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_button;
std::array<QLabel*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_label;
std::array<QSlider*, Settings::NativeAnalog::NumAnalogs> analog_map_modifier_slider;
std::array<QGroupBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_groupbox;
std::array<QSpinBox*, Settings::NativeAnalog::NumAnalogs> analog_map_range_spinbox;
static const std::array<std::string, ANALOG_SUB_BUTTONS_NUM> analog_sub_buttons;
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
/// A flag to indicate that the "Map Analog Stick" pop-up has been shown and accepted once.
bool map_analog_stick_accepted{};
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
bool want_keyboard_mouse{};
/// List of physical devices users can map with. If a SDL backed device is selected, then you
/// can use this device to get a default mapping.
std::vector<Common::ParamPackage> input_devices;
/// Bottom row is where console wide settings are held, and its "owned" by the parent
/// ConfigureInput widget. On show, add this widget to the main layout. This will change the
/// parent of the widget to this widget (but thats fine).
QWidget* bottom_row;
};
File diff suppressed because it is too large Load Diff
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "ui_configure_input_profile_dialog.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/configuration/configure_input_profile_dialog.h"
ConfigureInputProfileDialog::ConfigureInputProfileDialog(
QWidget* parent, InputCommon::InputSubsystem* input_subsystem, InputProfiles* profiles)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureInputProfileDialog>()),
profile_widget(new ConfigureInputPlayer(this, 9, nullptr, input_subsystem, profiles, false)) {
ui->setupUi(this);
ui->controllerLayout->addWidget(profile_widget);
connect(ui->clear_all_button, &QPushButton::clicked, this,
[this] { profile_widget->ClearAll(); });
connect(ui->restore_defaults_button, &QPushButton::clicked, this,
[this] { profile_widget->RestoreDefaults(); });
RetranslateUI();
}
ConfigureInputProfileDialog::~ConfigureInputProfileDialog() = default;
void ConfigureInputProfileDialog::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureInputProfileDialog::RetranslateUI() {
ui->retranslateUi(this);
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
class QPushButton;
class ConfigureInputPlayer;
class InputProfiles;
namespace InputCommon {
class InputSubsystem;
}
namespace Ui {
class ConfigureInputProfileDialog;
}
class ConfigureInputProfileDialog : public QDialog {
Q_OBJECT
public:
explicit ConfigureInputProfileDialog(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem,
InputProfiles* profiles);
~ConfigureInputProfileDialog() override;
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
std::unique_ptr<Ui::ConfigureInputProfileDialog> ui;
ConfigureInputPlayer* profile_widget;
};
+71
View File
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureInputProfileDialog</class>
<widget class="QDialog" name="ConfigureInputProfileDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>70</width>
<height>540</height>
</rect>
</property>
<property name="windowTitle">
<string>Create Input Profile</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<layout class="QHBoxLayout" name="controllerLayout"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="clear_all_button">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restore_defaults_button">
<property name="text">
<string>Defaults</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureInputProfileDialog</receiver>
<slot>accept()</slot>
</connection>
</connections>
</ui>
+358
View File
@@ -0,0 +1,358 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <sstream>
#include <QCloseEvent>
#include <QLabel>
#include <QMessageBox>
#include <QPushButton>
#include <QStringListModel>
#include <QVBoxLayout>
#include "common/logging/log.h"
#include "core/settings.h"
#include "input_common/main.h"
#include "input_common/udp/client.h"
#include "input_common/udp/udp.h"
#include "ui_configure_motion_touch.h"
#include "yuzu/configuration/configure_motion_touch.h"
#include "yuzu/configuration/configure_touch_from_button.h"
CalibrationConfigurationDialog::CalibrationConfigurationDialog(QWidget* parent,
const std::string& host, u16 port,
u8 pad_index, u16 client_id)
: QDialog(parent) {
layout = new QVBoxLayout;
status_label = new QLabel(tr("Communicating with the server..."));
cancel_button = new QPushButton(tr("Cancel"));
connect(cancel_button, &QPushButton::clicked, this, [this] {
if (!completed) {
job->Stop();
}
accept();
});
layout->addWidget(status_label);
layout->addWidget(cancel_button);
setLayout(layout);
using namespace InputCommon::CemuhookUDP;
job = std::make_unique<CalibrationConfigurationJob>(
host, port, pad_index, client_id,
[this](CalibrationConfigurationJob::Status status) {
QString text;
switch (status) {
case CalibrationConfigurationJob::Status::Ready:
text = tr("Touch the top left corner <br>of your touchpad.");
break;
case CalibrationConfigurationJob::Status::Stage1Completed:
text = tr("Now touch the bottom right corner <br>of your touchpad.");
break;
case CalibrationConfigurationJob::Status::Completed:
text = tr("Configuration completed!");
break;
}
QMetaObject::invokeMethod(this, "UpdateLabelText", Q_ARG(QString, text));
if (status == CalibrationConfigurationJob::Status::Completed) {
QMetaObject::invokeMethod(this, "UpdateButtonText", Q_ARG(QString, tr("OK")));
}
},
[this](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
completed = true;
min_x = min_x_;
min_y = min_y_;
max_x = max_x_;
max_y = max_y_;
});
}
CalibrationConfigurationDialog::~CalibrationConfigurationDialog() = default;
void CalibrationConfigurationDialog::UpdateLabelText(const QString& text) {
status_label->setText(text);
}
void CalibrationConfigurationDialog::UpdateButtonText(const QString& text) {
cancel_button->setText(text);
}
constexpr std::array<std::pair<const char*, const char*>, 2> TouchProviders = {{
{"emu_window", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "Emulator Window")},
{"cemuhookudp", QT_TRANSLATE_NOOP("ConfigureMotionTouch", "CemuhookUDP")},
}};
ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem_)
: QDialog(parent), input_subsystem{input_subsystem_},
ui(std::make_unique<Ui::ConfigureMotionTouch>()) {
ui->setupUi(this);
for (const auto& [provider, name] : TouchProviders) {
ui->touch_provider->addItem(tr(name), QString::fromUtf8(provider));
}
ui->udp_learn_more->setOpenExternalLinks(true);
ui->udp_learn_more->setText(
tr("<a "
"href='https://yuzu-emu.org/wiki/"
"using-a-controller-or-android-phone-for-motion-or-touch-input'><span "
"style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>"));
SetConfiguration();
UpdateUiDisplay();
ConnectEvents();
}
ConfigureMotionTouch::~ConfigureMotionTouch() = default;
void ConfigureMotionTouch::SetConfiguration() {
const Common::ParamPackage motion_param(Settings::values.motion_device);
const Common::ParamPackage touch_param(Settings::values.touch_device);
const std::string motion_engine = motion_param.Get("engine", "motion_emu");
const std::string touch_engine = touch_param.Get("engine", "emu_window");
ui->touch_provider->setCurrentIndex(
ui->touch_provider->findData(QString::fromStdString(touch_engine)));
ui->touch_from_button_checkbox->setChecked(Settings::values.use_touch_from_button);
touch_from_button_maps = Settings::values.touch_from_button_maps;
for (const auto& touch_map : touch_from_button_maps) {
ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
}
ui->touch_from_button_map->setCurrentIndex(Settings::values.touch_from_button_map_index);
ui->motion_sensitivity->setValue(motion_param.Get("sensitivity", 0.01f));
min_x = touch_param.Get("min_x", 100);
min_y = touch_param.Get("min_y", 50);
max_x = touch_param.Get("max_x", 1800);
max_y = touch_param.Get("max_y", 850);
ui->udp_server->setText(QString::fromStdString("127.0.0.1"));
ui->udp_port->setText(QString::number(26760));
udp_server_list_model = new QStringListModel(this);
udp_server_list_model->setStringList({});
ui->udp_server_list->setModel(udp_server_list_model);
std::stringstream ss(Settings::values.udp_input_servers);
std::string token;
while (std::getline(ss, token, ',')) {
const int row = udp_server_list_model->rowCount();
udp_server_list_model->insertRows(row, 1);
const QModelIndex index = udp_server_list_model->index(row);
udp_server_list_model->setData(index, QString::fromStdString(token));
}
}
void ConfigureMotionTouch::UpdateUiDisplay() {
const QString touch_engine = ui->touch_provider->currentData().toString();
const QString cemuhook_udp = QStringLiteral("cemuhookudp");
ui->motion_sensitivity_label->setVisible(true);
ui->motion_sensitivity->setVisible(true);
if (touch_engine == cemuhook_udp) {
ui->touch_calibration->setVisible(true);
ui->touch_calibration_config->setVisible(true);
ui->touch_calibration_label->setVisible(true);
ui->touch_calibration->setText(
QStringLiteral("(%1, %2) - (%3, %4)").arg(min_x).arg(min_y).arg(max_x).arg(max_y));
} else {
ui->touch_calibration->setVisible(false);
ui->touch_calibration_config->setVisible(false);
ui->touch_calibration_label->setVisible(false);
}
ui->udp_config_group_box->setVisible(true);
}
void ConfigureMotionTouch::ConnectEvents() {
connect(ui->touch_provider, qOverload<int>(&QComboBox::currentIndexChanged), this,
[this](int index) { UpdateUiDisplay(); });
connect(ui->udp_test, &QPushButton::clicked, this, &ConfigureMotionTouch::OnCemuhookUDPTest);
connect(ui->udp_add, &QPushButton::clicked, this, &ConfigureMotionTouch::OnUDPAddServer);
connect(ui->udp_remove, &QPushButton::clicked, this, &ConfigureMotionTouch::OnUDPDeleteServer);
connect(ui->touch_calibration_config, &QPushButton::clicked, this,
&ConfigureMotionTouch::OnConfigureTouchCalibration);
connect(ui->touch_from_button_config_btn, &QPushButton::clicked, this,
&ConfigureMotionTouch::OnConfigureTouchFromButton);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [this] {
if (CanCloseDialog()) {
reject();
}
});
}
void ConfigureMotionTouch::OnUDPAddServer() {
QRegExp re(tr(R"re(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4]"
"[0-9]|[01]?[0-9][0-9]?)$)re")); // a valid ip address
bool ok;
QString port_text = ui->udp_port->text();
QString server_text = ui->udp_server->text();
const QString server_string = tr("%1:%2").arg(server_text, port_text);
int port_number = port_text.toInt(&ok, 10);
int row = udp_server_list_model->rowCount();
if (!ok) {
QMessageBox::warning(this, tr("yuzu"), tr("Port number has invalid characters"));
return;
}
if (port_number < 0 || port_number > 65353) {
QMessageBox::warning(this, tr("yuzu"), tr("Port has to be in range 0 and 65353"));
return;
}
if (!re.exactMatch(server_text)) {
QMessageBox::warning(this, tr("yuzu"), tr("IP address is not valid"));
return;
}
// Search for duplicates
for (const auto& item : udp_server_list_model->stringList()) {
if (item == server_string) {
QMessageBox::warning(this, tr("yuzu"), tr("This UDP server already exists"));
return;
}
}
// Limit server count to 8
if (row == 8) {
QMessageBox::warning(this, tr("yuzu"), tr("Unable to add more than 8 servers"));
return;
}
udp_server_list_model->insertRows(row, 1);
QModelIndex index = udp_server_list_model->index(row);
udp_server_list_model->setData(index, server_string);
ui->udp_server_list->setCurrentIndex(index);
}
void ConfigureMotionTouch::OnUDPDeleteServer() {
udp_server_list_model->removeRows(ui->udp_server_list->currentIndex().row(), 1);
}
void ConfigureMotionTouch::OnCemuhookUDPTest() {
ui->udp_test->setEnabled(false);
ui->udp_test->setText(tr("Testing"));
udp_test_in_progress = true;
InputCommon::CemuhookUDP::TestCommunication(
ui->udp_server->text().toStdString(), static_cast<u16>(ui->udp_port->text().toInt()), 0,
24872,
[this] {
LOG_INFO(Frontend, "UDP input test success");
QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, true));
},
[this] {
LOG_ERROR(Frontend, "UDP input test failed");
QMetaObject::invokeMethod(this, "ShowUDPTestResult", Q_ARG(bool, false));
});
}
void ConfigureMotionTouch::OnConfigureTouchCalibration() {
ui->touch_calibration_config->setEnabled(false);
ui->touch_calibration_config->setText(tr("Configuring"));
CalibrationConfigurationDialog dialog(this, ui->udp_server->text().toStdString(),
static_cast<u16>(ui->udp_port->text().toUInt()), 0,
24872);
dialog.exec();
if (dialog.completed) {
min_x = dialog.min_x;
min_y = dialog.min_y;
max_x = dialog.max_x;
max_y = dialog.max_y;
LOG_INFO(Frontend,
"UDP touchpad calibration config success: min_x={}, min_y={}, max_x={}, max_y={}",
min_x, min_y, max_x, max_y);
UpdateUiDisplay();
} else {
LOG_ERROR(Frontend, "UDP touchpad calibration config failed");
}
ui->touch_calibration_config->setEnabled(true);
ui->touch_calibration_config->setText(tr("Configure"));
}
void ConfigureMotionTouch::closeEvent(QCloseEvent* event) {
if (CanCloseDialog()) {
event->accept();
} else {
event->ignore();
}
}
void ConfigureMotionTouch::ShowUDPTestResult(bool result) {
udp_test_in_progress = false;
if (result) {
QMessageBox::information(this, tr("Test Successful"),
tr("Successfully received data from the server."));
} else {
QMessageBox::warning(this, tr("Test Failed"),
tr("Could not receive valid data from the server.<br>Please verify "
"that the server is set up correctly and "
"the address and port are correct."));
}
ui->udp_test->setEnabled(true);
ui->udp_test->setText(tr("Test"));
}
void ConfigureMotionTouch::OnConfigureTouchFromButton() {
ConfigureTouchFromButton dialog{this, touch_from_button_maps, input_subsystem,
ui->touch_from_button_map->currentIndex()};
if (dialog.exec() != QDialog::Accepted) {
return;
}
touch_from_button_maps = dialog.GetMaps();
while (ui->touch_from_button_map->count() > 0) {
ui->touch_from_button_map->removeItem(0);
}
for (const auto& touch_map : touch_from_button_maps) {
ui->touch_from_button_map->addItem(QString::fromStdString(touch_map.name));
}
ui->touch_from_button_map->setCurrentIndex(dialog.GetSelectedIndex());
}
bool ConfigureMotionTouch::CanCloseDialog() {
if (udp_test_in_progress) {
QMessageBox::warning(this, tr("yuzu"),
tr("UDP Test or calibration configuration is in progress.<br>Please "
"wait for them to finish."));
return false;
}
return true;
}
void ConfigureMotionTouch::ApplyConfiguration() {
if (!CanCloseDialog()) {
return;
}
std::string touch_engine = ui->touch_provider->currentData().toString().toStdString();
Common::ParamPackage touch_param{};
touch_param.Set("engine", std::move(touch_engine));
if (touch_engine == "cemuhookudp") {
touch_param.Set("min_x", min_x);
touch_param.Set("min_y", min_y);
touch_param.Set("max_x", max_x);
touch_param.Set("max_y", max_y);
}
Settings::values.touch_device = touch_param.Serialize();
Settings::values.use_touch_from_button = ui->touch_from_button_checkbox->isChecked();
Settings::values.touch_from_button_map_index = ui->touch_from_button_map->currentIndex();
Settings::values.touch_from_button_maps = touch_from_button_maps;
Settings::values.udp_input_servers = GetUDPServerString();
input_subsystem->ReloadInputDevices();
accept();
}
std::string ConfigureMotionTouch::GetUDPServerString() const {
QString input_servers;
for (const auto& item : udp_server_list_model->stringList()) {
input_servers += item;
input_servers += QLatin1Char{','};
}
// Remove last comma
input_servers.chop(1);
return input_servers.toStdString();
}
+95
View File
@@ -0,0 +1,95 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
#include "common/param_package.h"
class QLabel;
class QPushButton;
class QStringListModel;
class QVBoxLayout;
namespace InputCommon {
class InputSubsystem;
}
namespace InputCommon::CemuhookUDP {
class CalibrationConfigurationJob;
}
namespace Ui {
class ConfigureMotionTouch;
}
/// A dialog for touchpad calibration configuration.
class CalibrationConfigurationDialog : public QDialog {
Q_OBJECT
public:
explicit CalibrationConfigurationDialog(QWidget* parent, const std::string& host, u16 port,
u8 pad_index, u16 client_id);
~CalibrationConfigurationDialog() override;
private:
Q_INVOKABLE void UpdateLabelText(const QString& text);
Q_INVOKABLE void UpdateButtonText(const QString& text);
QVBoxLayout* layout;
QLabel* status_label;
QPushButton* cancel_button;
std::unique_ptr<InputCommon::CemuhookUDP::CalibrationConfigurationJob> job;
// Configuration results
bool completed{};
u16 min_x{};
u16 min_y{};
u16 max_x{};
u16 max_y{};
friend class ConfigureMotionTouch;
};
class ConfigureMotionTouch : public QDialog {
Q_OBJECT
public:
explicit ConfigureMotionTouch(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
~ConfigureMotionTouch() override;
public slots:
void ApplyConfiguration();
private slots:
void OnUDPAddServer();
void OnUDPDeleteServer();
void OnCemuhookUDPTest();
void OnConfigureTouchCalibration();
void OnConfigureTouchFromButton();
private:
void closeEvent(QCloseEvent* event) override;
Q_INVOKABLE void ShowUDPTestResult(bool result);
void SetConfiguration();
void UpdateUiDisplay();
void ConnectEvents();
bool CanCloseDialog();
std::string GetUDPServerString() const;
InputCommon::InputSubsystem* input_subsystem;
std::unique_ptr<Ui::ConfigureMotionTouch> ui;
QStringListModel* udp_server_list_model;
// Coordinate system of the CemuhookUDP touch provider
int min_x{};
int min_y{};
int max_x{};
int max_y{};
bool udp_test_in_progress{};
std::vector<Settings::TouchFromButtonMap> touch_from_button_maps;
};
+384
View File
@@ -0,0 +1,384 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureMotionTouch</class>
<widget class="QDialog" name="ConfigureMotionTouch">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>482</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Motion / Touch</string>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QGroupBox" name="motion_group_box">
<property name="title">
<string>Mouse Motion</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="motion_sensitivity_label">
<property name="text">
<string>Sensitivity:</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="motion_sensitivity">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>10.000000000000000</double>
</property>
<property name="singleStep">
<double>0.001000000000000</double>
</property>
<property name="value">
<double>0.010000000000000</double>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="touch_group_box">
<property name="title">
<string>Touch</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="touch_provider_label">
<property name="text">
<string>Touch Provider:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="touch_provider"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="touch_calibration_label">
<property name="text">
<string>Calibration:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="touch_calibration">
<property name="text">
<string>(100, 50) - (1800, 850)</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="touch_calibration_config">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QCheckBox" name="touch_from_button_checkbox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Use button mapping:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="touch_from_button_map"/>
</item>
<item>
<widget class="QPushButton" name="touch_from_button_config_btn">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="udp_config_group_box">
<property name="title">
<string>CemuhookUDP Config</string>
</property>
<layout class="QVBoxLayout">
<item>
<widget class="QLabel" name="udp_help">
<property name="text">
<string>You may use any Cemuhook compatible UDP input source to provide motion and touch input.</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QListView" name="udp_server_list"/>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout">
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="udp_server_label">
<property name="text">
<string>Server:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udp_server">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<property name="leftMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="udp_port_label">
<property name="text">
<string>Port:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="udp_port">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout">
<property name="leftMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="udp_learn_more">
<property name="text">
<string>Learn More</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="udp_test">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Test</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="udp_add">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Add Server</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="udp_remove">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Remove Server</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>167</width>
<height>55</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureMotionTouch</receiver>
<slot>ApplyConfiguration()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
</ui>
+276
View File
@@ -0,0 +1,276 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <QKeyEvent>
#include <QMenu>
#include <QTimer>
#include "common/assert.h"
#include "common/param_package.h"
#include "input_common/main.h"
#include "ui_configure_mouse_advanced.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_mouse_advanced.h"
static QString GetKeyName(int key_code) {
switch (key_code) {
case Qt::LeftButton:
return QObject::tr("Click 0");
case Qt::RightButton:
return QObject::tr("Click 1");
case Qt::MiddleButton:
return QObject::tr("Click 2");
case Qt::BackButton:
return QObject::tr("Click 3");
case Qt::ForwardButton:
return QObject::tr("Click 4");
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
return QObject::tr("Ctrl");
case Qt::Key_Alt:
return QObject::tr("Alt");
case Qt::Key_Meta:
return {};
default:
return QKeySequence(key_code).toString();
}
}
static QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
}
if (param.Get("engine", "") == "keyboard") {
return GetKeyName(param.Get("code", 0));
}
if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
const QString hat_str = QString::fromStdString(param.Get("hat", ""));
const QString direction_str = QString::fromStdString(param.Get("direction", ""));
return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
}
if (param.Has("axis")) {
const QString axis_str = QString::fromStdString(param.Get("axis", ""));
const QString direction_str = QString::fromStdString(param.Get("direction", ""));
return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
}
if (param.Has("button")) {
const QString button_str = QString::fromStdString(param.Get("button", ""));
return QObject::tr("Button %1").arg(button_str);
}
return {};
}
return QObject::tr("[unknown]");
}
ConfigureMouseAdvanced::ConfigureMouseAdvanced(QWidget* parent,
InputCommon::InputSubsystem* input_subsystem_)
: QDialog(parent),
ui(std::make_unique<Ui::ConfigureMouseAdvanced>()), input_subsystem{input_subsystem_},
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
button_map = {
ui->left_button, ui->right_button, ui->middle_button, ui->forward_button, ui->back_button,
};
for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
auto* const button = button_map[button_id];
if (button == nullptr) {
continue;
}
button->setContextMenuPolicy(Qt::CustomContextMenu);
connect(button, &QPushButton::clicked, [=, this] {
HandleClick(
button_map[button_id],
[=, this](const Common::ParamPackage& params) {
buttons_param[button_id] = params;
},
InputCommon::Polling::DeviceType::Button);
});
connect(button, &QPushButton::customContextMenuRequested,
[=, this](const QPoint& menu_location) {
QMenu context_menu;
context_menu.addAction(tr("Clear"), [&] {
buttons_param[button_id].Clear();
button_map[button_id]->setText(tr("[not set]"));
});
context_menu.addAction(tr("Restore Default"), [&] {
buttons_param[button_id] =
Common::ParamPackage{InputCommon::GenerateKeyboardParam(
Config::default_mouse_buttons[button_id])};
button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
});
context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
});
}
connect(ui->buttonClearAll, &QPushButton::clicked, [this] { ClearAll(); });
connect(ui->buttonRestoreDefaults, &QPushButton::clicked, [this] { RestoreDefaults(); });
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this] {
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
if (params.Has("engine")) {
SetPollingResult(params, false);
return;
}
}
});
LoadConfiguration();
resize(0, 0);
}
ConfigureMouseAdvanced::~ConfigureMouseAdvanced() = default;
void ConfigureMouseAdvanced::ApplyConfiguration() {
std::transform(buttons_param.begin(), buttons_param.end(),
Settings::values.mouse_buttons.begin(),
[](const Common::ParamPackage& param) { return param.Serialize(); });
}
void ConfigureMouseAdvanced::LoadConfiguration() {
std::transform(Settings::values.mouse_buttons.begin(), Settings::values.mouse_buttons.end(),
buttons_param.begin(),
[](const std::string& str) { return Common::ParamPackage(str); });
UpdateButtonLabels();
}
void ConfigureMouseAdvanced::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureMouseAdvanced::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureMouseAdvanced::RestoreDefaults() {
for (int button_id = 0; button_id < Settings::NativeMouseButton::NumMouseButtons; button_id++) {
buttons_param[button_id] = Common::ParamPackage{
InputCommon::GenerateKeyboardParam(Config::default_mouse_buttons[button_id])};
}
UpdateButtonLabels();
}
void ConfigureMouseAdvanced::ClearAll() {
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
const auto* const button = button_map[i];
if (button != nullptr && button->isEnabled()) {
buttons_param[i].Clear();
}
}
UpdateButtonLabels();
}
void ConfigureMouseAdvanced::UpdateButtonLabels() {
for (int button = 0; button < Settings::NativeMouseButton::NumMouseButtons; button++) {
button_map[button]->setText(ButtonToText(buttons_param[button]));
}
}
void ConfigureMouseAdvanced::HandleClick(
QPushButton* button, std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type) {
button->setText(tr("[press key]"));
button->setFocus();
// Keyboard keys or mouse buttons can only be used as button devices
want_keyboard_mouse = type == InputCommon::Polling::DeviceType::Button;
if (want_keyboard_mouse) {
const auto iter = std::find(button_map.begin(), button_map.end(), button);
ASSERT(iter != button_map.end());
const auto index = std::distance(button_map.begin(), iter);
ASSERT(index < Settings::NativeButton::NumButtons && index >= 0);
}
input_setter = new_input_setter;
device_pollers = input_subsystem->GetPollers(type);
for (auto& poller : device_pollers) {
poller->Start();
}
QWidget::grabMouse();
QWidget::grabKeyboard();
timeout_timer->start(2500); // Cancel after 2.5 seconds
poll_timer->start(50); // Check for new inputs every 50ms
}
void ConfigureMouseAdvanced::SetPollingResult(const Common::ParamPackage& params, bool abort) {
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
QWidget::releaseMouse();
QWidget::releaseKeyboard();
if (!abort) {
(*input_setter)(params);
}
UpdateButtonLabels();
input_setter = std::nullopt;
}
void ConfigureMouseAdvanced::mousePressEvent(QMouseEvent* event) {
if (!input_setter || !event) {
return;
}
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->button())},
false);
} else {
// We don't want any mouse buttons, so don't stop polling
return;
}
SetPollingResult({}, true);
}
void ConfigureMouseAdvanced::keyPressEvent(QKeyEvent* event) {
if (!input_setter || !event) {
return;
}
if (event->key() != Qt::Key_Escape) {
if (want_keyboard_mouse) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
// Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling
return;
}
}
SetPollingResult({}, true);
}
+78
View File
@@ -0,0 +1,78 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <optional>
#include <QDialog>
class QCheckBox;
class QPushButton;
class QTimer;
namespace InputCommon {
class InputSubsystem;
}
namespace Ui {
class ConfigureMouseAdvanced;
}
class ConfigureMouseAdvanced : public QDialog {
Q_OBJECT
public:
explicit ConfigureMouseAdvanced(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_);
~ConfigureMouseAdvanced() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
/// Load configuration settings.
void LoadConfiguration();
/// Restore all buttons to their default values.
void RestoreDefaults();
/// Clear all input configuration
void ClearAll();
/// Update UI to reflect current configuration.
void UpdateButtonLabels();
/// Called when the button was pressed.
void HandleClick(QPushButton* button,
std::function<void(const Common::ParamPackage&)> new_input_setter,
InputCommon::Polling::DeviceType type);
/// Finish polling and configure input using the input_setter
void SetPollingResult(const Common::ParamPackage& params, bool abort);
/// Handle mouse button press events.
void mousePressEvent(QMouseEvent* event) override;
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
std::unique_ptr<Ui::ConfigureMouseAdvanced> ui;
InputCommon::InputSubsystem* input_subsystem;
/// This will be the the setting function when an input is awaiting configuration.
std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
std::array<QPushButton*, Settings::NativeMouseButton::NumMouseButtons> button_map;
std::array<Common::ParamPackage, Settings::NativeMouseButton::NumMouseButtons> buttons_param;
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
/// A flag to indicate if keyboard keys are okay when configuring an input. If this is false,
/// keyboard events are ignored.
bool want_keyboard_mouse = false;
};
+335
View File
@@ -0,0 +1,335 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureMouseAdvanced</class>
<widget class="QDialog" name="ConfigureMouseAdvanced">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>310</width>
<height>193</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Mouse</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
min-width: 60px;
}</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gridGroupBox">
<property name="title">
<string>Mouse Buttons</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="5">
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QLabel" name="label_5">
<property name="text">
<string>Forward:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="forward_button">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_4">
<property name="minimumSize">
<size>
<width>54</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Back:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="back_button">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Left:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="left_button">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="3">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Middle:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="middle_button">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="6">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="5">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Right:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="right_button">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="4">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QPushButton" name="buttonClearAll">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonRestoreDefaults">
<property name="minimumSize">
<size>
<width>68</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>68</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Defaults</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureMouseAdvanced</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>
+144
View File
@@ -0,0 +1,144 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <utility>
#include <QCheckBox>
#include <QHeaderView>
#include <QMenu>
#include <QStandardItemModel>
#include <QString>
#include <QTimer>
#include <QTreeView>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
#include "ui_configure_per_game.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_per_game.h"
#include "yuzu/uisettings.h"
#include "yuzu/util/util.h"
ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id)
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGame>()), title_id(title_id) {
game_config = std::make_unique<Config>(fmt::format("{:016X}", title_id),
Config::ConfigType::PerGameConfig);
Settings::SetConfiguringGlobal(false);
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
setWindowTitle(tr("Properties"));
ui->addonsTab->SetTitleId(title_id);
scene = new QGraphicsScene;
ui->icon_view->setScene(scene);
LoadConfiguration();
}
ConfigurePerGame::~ConfigurePerGame() = default;
void ConfigurePerGame::ApplyConfiguration() {
ui->addonsTab->ApplyConfiguration();
ui->generalTab->ApplyConfiguration();
ui->systemTab->ApplyConfiguration();
ui->graphicsTab->ApplyConfiguration();
ui->graphicsAdvancedTab->ApplyConfiguration();
ui->audioTab->ApplyConfiguration();
Settings::Apply(Core::System::GetInstance());
Settings::LogSettings();
game_config->Save();
}
void ConfigurePerGame::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigurePerGame::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigurePerGame::LoadFromFile(FileSys::VirtualFile file) {
this->file = std::move(file);
LoadConfiguration();
}
void ConfigurePerGame::LoadConfiguration() {
if (file == nullptr) {
return;
}
ui->addonsTab->LoadFromFile(file);
ui->display_title_id->setText(
QStringLiteral("%1").arg(title_id, 16, 16, QLatin1Char{'0'}).toUpper());
auto& system = Core::System::GetInstance();
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto control = pm.GetControlMetadata();
const auto loader = Loader::GetLoader(system, file);
if (control.first != nullptr) {
ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
} else {
std::string title;
if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
ui->display_name->setText(QString::fromStdString(title));
FileSys::NACP nacp;
if (loader->ReadControlData(nacp) == Loader::ResultStatus::Success)
ui->display_developer->setText(QString::fromStdString(nacp.GetDeveloperName()));
ui->display_version->setText(QStringLiteral("1.0.0"));
}
if (control.second != nullptr) {
scene->clear();
QPixmap map;
const auto bytes = control.second->ReadAllBytes();
map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
} else {
std::vector<u8> bytes;
if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
scene->clear();
QPixmap map;
map.loadFromData(bytes.data(), static_cast<u32>(bytes.size()));
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
}
}
ui->display_filename->setText(QString::fromStdString(file->GetName()));
ui->display_format->setText(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
const auto valueText = ReadableByteSize(file->GetSize());
ui->display_size->setText(valueText);
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2020 yuzu 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 <QList>
#include "core/file_sys/vfs_types.h"
#include "yuzu/configuration/config.h"
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
namespace Ui {
class ConfigurePerGame;
}
class ConfigurePerGame : public QDialog {
Q_OBJECT
public:
explicit ConfigurePerGame(QWidget* parent, u64 title_id);
~ConfigurePerGame() override;
/// Save all button configurations to settings file
void ApplyConfiguration();
void LoadFromFile(FileSys::VirtualFile file);
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void LoadConfiguration();
std::unique_ptr<Ui::ConfigurePerGame> ui;
FileSys::VirtualFile file;
u64 title_id;
QGraphicsScene* scene;
std::unique_ptr<Config> game_config;
};
+330
View File
@@ -0,0 +1,330 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigurePerGame</class>
<widget class="QDialog" name="ConfigurePerGame">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Info</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item alignment="Qt::AlignHCenter">
<widget class="QGraphicsView" name="icon_view">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>256</width>
<height>256</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>256</width>
<height>256</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="6" column="1">
<widget class="QLineEdit" name="display_size">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="display_version">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Title ID</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="display_title_id">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="display_filename">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="display_format">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Filename</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="display_name">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="display_developer">
<property name="enabled">
<bool>true</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Format</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Size</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Developer</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="VerticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="enabled">
<bool>true</bool>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<property name="usesScrollButtons">
<bool>true</bool>
</property>
<property name="documentMode">
<bool>false</bool>
</property>
<property name="tabsClosable">
<bool>false</bool>
</property>
<widget class="ConfigurePerGameAddons" name="addonsTab">
<attribute name="title">
<string>Add-Ons</string>
</attribute>
</widget>
<widget class="ConfigureGeneral" name="generalTab">
<attribute name="title">
<string>General</string>
</attribute>
</widget>
<widget class="ConfigureSystem" name="systemTab">
<attribute name="title">
<string>System</string>
</attribute>
</widget>
<widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title">
<string>Graphics</string>
</attribute>
</widget>
<widget class="ConfigureGraphicsAdvanced" name="graphicsAdvancedTab">
<attribute name="title">
<string>Adv. Graphics</string>
</attribute>
</widget>
<widget class="ConfigureAudio" name="audioTab">
<attribute name="title">
<string>Audio</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ConfigureGeneral</class>
<extends>QWidget</extends>
<header>configuration/configure_general.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureSystem</class>
<extends>QWidget</extends>
<header>configuration/configure_system.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureAudio</class>
<extends>QWidget</extends>
<header>configuration/configure_audio.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGraphics</class>
<extends>QWidget</extends>
<header>configuration/configure_graphics.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGraphicsAdvanced</class>
<extends>QWidget</extends>
<header>configuration/configure_graphics_advanced.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigurePerGameAddons</class>
<extends>QWidget</extends>
<header>configuration/configure_per_game_addons.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>ConfigurePerGame</receiver>
<slot>accept()</slot>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigurePerGame</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>
+144
View File
@@ -0,0 +1,144 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include <utility>
#include <QHeaderView>
#include <QMenu>
#include <QStandardItemModel>
#include <QString>
#include <QTimer>
#include <QTreeView>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "core/core.h"
#include "core/file_sys/patch_manager.h"
#include "core/file_sys/xts_archive.h"
#include "core/loader/loader.h"
#include "ui_configure_per_game_addons.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_input.h"
#include "yuzu/configuration/configure_per_game_addons.h"
#include "yuzu/uisettings.h"
#include "yuzu/util/util.h"
ConfigurePerGameAddons::ConfigurePerGameAddons(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigurePerGameAddons) {
ui->setupUi(this);
layout = new QVBoxLayout;
tree_view = new QTreeView;
item_model = new QStandardItemModel(tree_view);
tree_view->setModel(item_model);
tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::SingleSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
item_model->insertColumns(0, 2);
item_model->setHeaderData(0, Qt::Horizontal, tr("Patch Name"));
item_model->setHeaderData(1, Qt::Horizontal, tr("Version"));
// We must register all custom types with the Qt Automoc system so that we are able to use it
// with signals/slots. In this case, QList falls under the umbrella of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(tree_view);
ui->scrollArea->setLayout(layout);
ui->scrollArea->setEnabled(!Core::System::GetInstance().IsPoweredOn());
connect(item_model, &QStandardItemModel::itemChanged,
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
}
ConfigurePerGameAddons::~ConfigurePerGameAddons() = default;
void ConfigurePerGameAddons::ApplyConfiguration() {
std::vector<std::string> disabled_addons;
for (const auto& item : list_items) {
const auto disabled = item.front()->checkState() == Qt::Unchecked;
if (disabled)
disabled_addons.push_back(item.front()->text().toStdString());
}
auto current = Settings::values.disabled_addons[title_id];
std::sort(disabled_addons.begin(), disabled_addons.end());
std::sort(current.begin(), current.end());
if (disabled_addons != current) {
Common::FS::Delete(Common::FS::GetUserPath(Common::FS::UserPath::CacheDir) + DIR_SEP +
"game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
}
Settings::values.disabled_addons[title_id] = disabled_addons;
}
void ConfigurePerGameAddons::LoadFromFile(FileSys::VirtualFile file) {
this->file = std::move(file);
LoadConfiguration();
}
void ConfigurePerGameAddons::SetTitleId(u64 id) {
this->title_id = id;
}
void ConfigurePerGameAddons::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigurePerGameAddons::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigurePerGameAddons::LoadConfiguration() {
if (file == nullptr) {
return;
}
auto& system = Core::System::GetInstance();
const FileSys::PatchManager pm{title_id, system.GetFileSystemController(),
system.GetContentProvider()};
const auto loader = Loader::GetLoader(system, file);
FileSys::VirtualFile update_raw;
loader->ReadUpdateRaw(update_raw);
const auto& disabled = Settings::values.disabled_addons[title_id];
for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
const auto name =
QString::fromStdString(patch.first).replace(QStringLiteral("[D] "), QString{});
auto* const first_item = new QStandardItem;
first_item->setText(name);
first_item->setCheckable(true);
const auto patch_disabled =
std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
list_items.push_back(QList<QStandardItem*>{
first_item, new QStandardItem{QString::fromStdString(patch.second)}});
item_model->appendRow(list_items.back());
}
tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <vector>
#include <QList>
#include "core/file_sys/vfs_types.h"
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
namespace Ui {
class ConfigurePerGameAddons;
}
class ConfigurePerGameAddons : public QWidget {
Q_OBJECT
public:
explicit ConfigurePerGameAddons(QWidget* parent = nullptr);
~ConfigurePerGameAddons() override;
/// Save all button configurations to settings file
void ApplyConfiguration();
void LoadFromFile(FileSys::VirtualFile file);
void SetTitleId(u64 id);
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void LoadConfiguration();
std::unique_ptr<Ui::ConfigurePerGameAddons> ui;
FileSys::VirtualFile file;
u64 title_id;
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
std::vector<QList<QStandardItem*>> list_items;
};
+38
View File
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigurePerGameAddons</class>
<widget class="QWidget" name="ConfigurePerGameAddons">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QScrollArea" name="scrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="scrollAreaWidgetContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>380</width>
<height>280</height>
</rect>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+311
View File
@@ -0,0 +1,311 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <QFileDialog>
#include <QGraphicsItem>
#include <QGraphicsScene>
#include <QHeaderView>
#include <QMessageBox>
#include <QStandardItemModel>
#include <QTreeView>
#include <QVBoxLayout>
#include "common/assert.h"
#include "common/file_util.h"
#include "common/string_util.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
#include "ui_configure_profile_manager.h"
#include "yuzu/configuration/configure_profile_manager.h"
#include "yuzu/util/limitable_input_dialog.h"
namespace {
// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
constexpr std::array<u8, 107> backup_jpeg{
0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
};
QString GetImagePath(Common::UUID uuid) {
const auto path = Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) +
"/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
return QString::fromStdString(path);
}
QString GetAccountUsername(const Service::Account::ProfileManager& manager, Common::UUID uuid) {
Service::Account::ProfileBase profile;
if (!manager.GetProfileBase(uuid, profile)) {
return {};
}
const auto text = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
return QString::fromStdString(text);
}
QString FormatUserEntryText(const QString& username, Common::UUID uuid) {
return ConfigureProfileManager::tr("%1\n%2",
"%1 is the profile username, %2 is the formatted UUID (e.g. "
"00112233-4455-6677-8899-AABBCCDDEEFF))")
.arg(username, QString::fromStdString(uuid.FormatSwitch()));
}
QPixmap GetIcon(Common::UUID uuid) {
QPixmap icon{GetImagePath(uuid)};
if (!icon) {
icon.fill(Qt::black);
icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
}
return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
}
QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) {
return LimitableInputDialog::GetText(parent, ConfigureProfileManager::tr("Enter Username"),
description_text, 1,
static_cast<int>(Service::Account::profile_username_size));
}
} // Anonymous namespace
ConfigureProfileManager ::ConfigureProfileManager(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureProfileManager),
profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
ui->setupUi(this);
tree_view = new QTreeView;
item_model = new QStandardItemModel(tree_view);
item_model->insertColumns(0, 1);
tree_view->setModel(item_model);
tree_view->setAlternatingRowColors(true);
tree_view->setSelectionMode(QHeaderView::SingleSelection);
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
tree_view->setSortingEnabled(true);
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
tree_view->setUniformRowHeights(true);
tree_view->setIconSize({64, 64});
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
// We must register all custom types with the Qt Automoc system so that we are able to use it
// with signals/slots. In this case, QList falls under the umbrells of custom types.
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
layout = new QVBoxLayout;
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(tree_view);
ui->scrollArea->setLayout(layout);
connect(tree_view, &QTreeView::clicked, this, &ConfigureProfileManager::SelectUser);
connect(ui->pm_add, &QPushButton::clicked, this, &ConfigureProfileManager::AddUser);
connect(ui->pm_rename, &QPushButton::clicked, this, &ConfigureProfileManager::RenameUser);
connect(ui->pm_remove, &QPushButton::clicked, this, &ConfigureProfileManager::DeleteUser);
connect(ui->pm_set_image, &QPushButton::clicked, this, &ConfigureProfileManager::SetUserImage);
scene = new QGraphicsScene;
ui->current_user_icon->setScene(scene);
SetConfiguration();
RetranslateUI();
}
ConfigureProfileManager::~ConfigureProfileManager() = default;
void ConfigureProfileManager::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureProfileManager::RetranslateUI() {
ui->retranslateUi(this);
item_model->setHeaderData(0, Qt::Horizontal, tr("Users"));
}
void ConfigureProfileManager::SetConfiguration() {
enabled = !Core::System::GetInstance().IsPoweredOn();
item_model->removeRows(0, item_model->rowCount());
list_items.clear();
PopulateUserList();
UpdateCurrentUser();
}
void ConfigureProfileManager::PopulateUserList() {
const auto& profiles = profile_manager->GetAllUsers();
for (const auto& user : profiles) {
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(user, profile))
continue;
const auto username = Common::StringFromFixedZeroTerminatedBuffer(
reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
list_items.push_back(QList<QStandardItem*>{new QStandardItem{
GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
}
for (const auto& item : list_items)
item_model->appendRow(item);
}
void ConfigureProfileManager::UpdateCurrentUser() {
ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
ASSERT(current_user);
const auto username = GetAccountUsername(*profile_manager, *current_user);
scene->clear();
scene->addPixmap(
GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
ui->current_user_username->setText(username);
}
void ConfigureProfileManager::ApplyConfiguration() {
if (!enabled) {
return;
}
Settings::Apply(Core::System::GetInstance());
}
void ConfigureProfileManager::SelectUser(const QModelIndex& index) {
Settings::values.current_user =
std::clamp<s32>(index.row(), 0, static_cast<s32>(profile_manager->GetUserCount() - 1));
UpdateCurrentUser();
ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
ui->pm_rename->setEnabled(true);
ui->pm_set_image->setEnabled(true);
}
void ConfigureProfileManager::AddUser() {
const auto username =
GetProfileUsernameFromUser(this, tr("Enter a username for the new user:"));
if (username.isEmpty()) {
return;
}
const auto uuid = Common::UUID::Generate();
profile_manager->CreateNewUser(uuid, username.toStdString());
item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
}
void ConfigureProfileManager::RenameUser() {
const auto user = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(user);
ASSERT(uuid);
Service::Account::ProfileBase profile;
if (!profile_manager->GetProfileBase(*uuid, profile))
return;
const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:"));
if (new_username.isEmpty()) {
return;
}
const auto username_std = new_username.toStdString();
std::fill(profile.username.begin(), profile.username.end(), '\0');
std::copy(username_std.begin(), username_std.end(), profile.username.begin());
profile_manager->SetProfileBase(*uuid, profile);
item_model->setItem(
user, 0,
new QStandardItem{GetIcon(*uuid),
FormatUserEntryText(QString::fromStdString(username_std), *uuid)});
UpdateCurrentUser();
}
void ConfigureProfileManager::DeleteUser() {
const auto index = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(index);
ASSERT(uuid);
const auto username = GetAccountUsername(*profile_manager, *uuid);
const auto confirm = QMessageBox::question(
this, tr("Confirm Delete"),
tr("You are about to delete user with name \"%1\". Are you sure?").arg(username));
if (confirm == QMessageBox::No)
return;
if (Settings::values.current_user == tree_view->currentIndex().row())
Settings::values.current_user = 0;
UpdateCurrentUser();
if (!profile_manager->RemoveUser(*uuid))
return;
item_model->removeRows(tree_view->currentIndex().row(), 1);
tree_view->clearSelection();
ui->pm_remove->setEnabled(false);
ui->pm_rename->setEnabled(false);
}
void ConfigureProfileManager::SetUserImage() {
const auto index = tree_view->currentIndex().row();
const auto uuid = profile_manager->GetUser(index);
ASSERT(uuid);
const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
tr("JPEG Images (*.jpg *.jpeg)"));
if (file.isEmpty()) {
return;
}
const auto image_path = GetImagePath(*uuid);
if (QFile::exists(image_path) && !QFile::remove(image_path)) {
QMessageBox::warning(
this, tr("Error deleting image"),
tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path));
return;
}
const auto raw_path = QString::fromStdString(
Common::FS::GetUserPath(Common::FS::UserPath::NANDDir) + "/system/save/8000000000000010");
const QFileInfo raw_info{raw_path};
if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
QMessageBox::warning(this, tr("Error deleting file"),
tr("Unable to delete existing file: %1.").arg(raw_path));
return;
}
const QString absolute_dst_path = QFileInfo{image_path}.absolutePath();
if (!QDir{raw_path}.mkpath(absolute_dst_path)) {
QMessageBox::warning(
this, tr("Error creating user image directory"),
tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path));
return;
}
if (!QFile::copy(file, image_path)) {
QMessageBox::warning(this, tr("Error copying user image"),
tr("Unable to copy image from %1 to %2").arg(file, image_path));
return;
}
const auto username = GetAccountUsername(*profile_manager, *uuid);
item_model->setItem(index, 0,
new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
UpdateCurrentUser();
}
+61
View File
@@ -0,0 +1,61 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QList>
#include <QWidget>
class QGraphicsScene;
class QStandardItem;
class QStandardItemModel;
class QTreeView;
class QVBoxLayout;
namespace Service::Account {
class ProfileManager;
}
namespace Ui {
class ConfigureProfileManager;
}
class ConfigureProfileManager : public QWidget {
Q_OBJECT
public:
explicit ConfigureProfileManager(QWidget* parent = nullptr);
~ConfigureProfileManager() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void SetConfiguration();
void PopulateUserList();
void UpdateCurrentUser();
void SelectUser(const QModelIndex& index);
void AddUser();
void RenameUser();
void DeleteUser();
void SetUserImage();
QVBoxLayout* layout;
QTreeView* tree_view;
QStandardItemModel* item_model;
QGraphicsScene* scene;
std::vector<QList<QStandardItem*>> list_items;
std::unique_ptr<Ui::ConfigureProfileManager> ui;
bool enabled = false;
std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};
+172
View File
@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureProfileManager</class>
<widget class="QWidget" name="ConfigureProfileManager">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>366</width>
<height>483</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="gridGroupBox">
<property name="title">
<string>Profile Manager</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<property name="sizeConstraint">
<enum>QLayout::SetNoConstraint</enum>
</property>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Current User</string>
</property>
</widget>
</item>
<item>
<widget class="QGraphicsView" name="current_user_icon">
<property name="minimumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>48</width>
<height>48</height>
</size>
</property>
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="interactive">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="current_user_username">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Username</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QScrollArea" name="scrollArea">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="widgetResizable">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="pm_set_image">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Set Image</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pm_add">
<property name="text">
<string>Add</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pm_rename">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Rename</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pm_remove">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
<string>Profile management is available only when game is not running.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+142
View File
@@ -0,0 +1,142 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QGraphicsItem>
#include <QtConcurrent/QtConcurrent>
#include "core/hle/service/bcat/backend/boxcat.h"
#include "core/settings.h"
#include "ui_configure_service.h"
#include "yuzu/configuration/configure_service.h"
namespace {
QString FormatEventStatusString(const Service::BCAT::EventStatus& status) {
QString out;
if (status.header.has_value()) {
out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.header));
}
if (status.events.size() == 1) {
out += QStringLiteral("%1<br>").arg(QString::fromStdString(status.events.front()));
} else {
for (const auto& event : status.events) {
out += QStringLiteral("- %1<br>").arg(QString::fromStdString(event));
}
}
if (status.footer.has_value()) {
out += QStringLiteral("<i>%1</i><br>").arg(QString::fromStdString(*status.footer));
}
return out;
}
} // Anonymous namespace
ConfigureService::ConfigureService(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureService>()) {
ui->setupUi(this);
ui->bcat_source->addItem(QStringLiteral("None"));
ui->bcat_empty_label->setHidden(true);
ui->bcat_empty_header->setHidden(true);
#ifdef YUZU_ENABLE_BOXCAT
ui->bcat_source->addItem(QStringLiteral("Boxcat"), QStringLiteral("boxcat"));
#endif
connect(ui->bcat_source, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&ConfigureService::OnBCATImplChanged);
this->SetConfiguration();
}
ConfigureService::~ConfigureService() = default;
void ConfigureService::ApplyConfiguration() {
Settings::values.bcat_backend = ui->bcat_source->currentText().toLower().toStdString();
}
void ConfigureService::RetranslateUi() {
ui->retranslateUi(this);
}
void ConfigureService::SetConfiguration() {
const int index =
ui->bcat_source->findData(QString::fromStdString(Settings::values.bcat_backend));
ui->bcat_source->setCurrentIndex(index == -1 ? 0 : index);
}
std::pair<QString, QString> ConfigureService::BCATDownloadEvents() {
#ifdef YUZU_ENABLE_BOXCAT
std::optional<std::string> global;
std::map<std::string, Service::BCAT::EventStatus> map;
const auto res = Service::BCAT::Boxcat::GetStatus(global, map);
switch (res) {
case Service::BCAT::Boxcat::StatusResult::Success:
break;
case Service::BCAT::Boxcat::StatusResult::Offline:
return {QString{},
tr("The boxcat service is offline or you are not connected to the internet.")};
case Service::BCAT::Boxcat::StatusResult::ParseError:
return {QString{},
tr("There was an error while processing the boxcat event data. Contact the yuzu "
"developers.")};
case Service::BCAT::Boxcat::StatusResult::BadClientVersion:
return {QString{},
tr("The version of yuzu you are using is either too new or too old for the server. "
"Try updating to the latest official release of yuzu.")};
}
if (map.empty()) {
return {QStringLiteral("Current Boxcat Events"),
tr("There are currently no events on boxcat.")};
}
QString out;
if (global.has_value()) {
out += QStringLiteral("%1<br>").arg(QString::fromStdString(*global));
}
for (const auto& [key, value] : map) {
out += QStringLiteral("%1<b>%2</b><br>%3")
.arg(out.isEmpty() ? QString{} : QStringLiteral("<br>"))
.arg(QString::fromStdString(key))
.arg(FormatEventStatusString(value));
}
return {tr("Current Boxcat Events"), std::move(out)};
#else
return {tr("Current Boxcat Events"), tr("There are currently no events on boxcat.")};
#endif
}
void ConfigureService::OnBCATImplChanged() {
#ifdef YUZU_ENABLE_BOXCAT
const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
ui->bcat_empty_header->setHidden(!boxcat);
ui->bcat_empty_label->setHidden(!boxcat);
ui->bcat_empty_header->setText(QString{});
ui->bcat_empty_label->setText(tr("Yuzu is retrieving the latest boxcat status..."));
if (!boxcat)
return;
const auto future = QtConcurrent::run([this] { return BCATDownloadEvents(); });
watcher.setFuture(future);
connect(&watcher, &QFutureWatcher<std::pair<QString, QString>>::finished, this,
[this] { OnUpdateBCATEmptyLabel(watcher.result()); });
#endif
}
void ConfigureService::OnUpdateBCATEmptyLabel(std::pair<QString, QString> string) {
#ifdef YUZU_ENABLE_BOXCAT
const auto boxcat = ui->bcat_source->currentText() == QStringLiteral("Boxcat");
if (boxcat) {
ui->bcat_empty_header->setText(string.first);
ui->bcat_empty_label->setText(string.second);
}
#endif
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QFutureWatcher>
#include <QWidget>
namespace Ui {
class ConfigureService;
}
class ConfigureService : public QWidget {
Q_OBJECT
public:
explicit ConfigureService(QWidget* parent = nullptr);
~ConfigureService() override;
void ApplyConfiguration();
void RetranslateUi();
private:
void SetConfiguration();
std::pair<QString, QString> BCATDownloadEvents();
void OnBCATImplChanged();
void OnUpdateBCATEmptyLabel(std::pair<QString, QString> string);
std::unique_ptr<Ui::ConfigureService> ui;
QFutureWatcher<std::pair<QString, QString>> watcher{this};
};
+124
View File
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureService</class>
<widget class="QWidget" name="ConfigureService">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>433</width>
<height>561</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>BCAT</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1" colspan="2">
<widget class="QLabel" name="label_2">
<property name="maximumSize">
<size>
<width>260</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>BCAT is Nintendo's way of sending data to games to engage its community and unlock additional content.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>BCAT Backend</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QLabel" name="bcat_empty_label">
<property name="enabled">
<bool>true</bool>
</property>
<property name="maximumSize">
<size>
<width>260</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLabel" name="label_3">
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://yuzu-emu.org/help/feature/boxcat&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Learn more about BCAT, Boxcat, and Current Events&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QComboBox" name="bcat_source"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="bcat_empty_header">
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+253
View File
@@ -0,0 +1,253 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <array>
#include <chrono>
#include <optional>
#include <QFileDialog>
#include <QGraphicsItem>
#include <QMessageBox>
#include "common/assert.h"
#include "common/file_util.h"
#include "core/core.h"
#include "core/hle/service/time/time.h"
#include "core/settings.h"
#include "ui_configure_system.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_system.h"
ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
ui->setupUi(this);
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
&ConfigureSystem::RefreshConsoleID);
connect(ui->rng_seed_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
ui->rng_seed_edit->setEnabled(state == Qt::Checked);
if (state != Qt::Checked) {
ui->rng_seed_edit->setText(QStringLiteral("00000000"));
}
});
connect(ui->custom_rtc_checkbox, &QCheckBox::stateChanged, this, [this](int state) {
ui->custom_rtc_edit->setEnabled(state == Qt::Checked);
if (state != Qt::Checked) {
ui->custom_rtc_edit->setDateTime(QDateTime::currentDateTime());
}
});
ui->label_console_id->setVisible(Settings::IsConfiguringGlobal());
ui->button_regenerate_console_id->setVisible(Settings::IsConfiguringGlobal());
SetupPerGameUI();
SetConfiguration();
}
ConfigureSystem::~ConfigureSystem() = default;
void ConfigureSystem::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QWidget::changeEvent(event);
}
void ConfigureSystem::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureSystem::SetConfiguration() {
enabled = !Core::System::GetInstance().IsPoweredOn();
const auto rng_seed =
QStringLiteral("%1")
.arg(Settings::values.rng_seed.GetValue().value_or(0), 8, 16, QLatin1Char{'0'})
.toUpper();
const auto rtc_time = Settings::values.custom_rtc.GetValue().value_or(
std::chrono::seconds(QDateTime::currentSecsSinceEpoch()));
ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.GetValue().has_value());
ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.GetValue().has_value() &&
Settings::values.rng_seed.UsingGlobal());
ui->rng_seed_edit->setText(rng_seed);
ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.GetValue().has_value());
ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.GetValue().has_value() &&
Settings::values.rng_seed.UsingGlobal());
ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time.count()));
if (Settings::IsConfiguringGlobal()) {
ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue());
ui->combo_region->setCurrentIndex(Settings::values.region_index.GetValue());
ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index.GetValue());
ui->combo_sound->setCurrentIndex(Settings::values.sound_index.GetValue());
} else {
ConfigurationShared::SetPerGameSetting(ui->combo_language,
&Settings::values.language_index);
ConfigurationShared::SetPerGameSetting(ui->combo_region, &Settings::values.region_index);
ConfigurationShared::SetPerGameSetting(ui->combo_time_zone,
&Settings::values.time_zone_index);
ConfigurationShared::SetPerGameSetting(ui->combo_sound, &Settings::values.sound_index);
ConfigurationShared::SetHighlight(ui->label_language,
!Settings::values.language_index.UsingGlobal());
ConfigurationShared::SetHighlight(ui->label_region,
!Settings::values.region_index.UsingGlobal());
ConfigurationShared::SetHighlight(ui->label_timezone,
!Settings::values.time_zone_index.UsingGlobal());
ConfigurationShared::SetHighlight(ui->label_sound,
!Settings::values.sound_index.UsingGlobal());
}
}
void ConfigureSystem::ReadSystemSettings() {}
void ConfigureSystem::ApplyConfiguration() {
auto& system = Core::System::GetInstance();
// Allow setting custom RTC even if system is powered on,
// to allow in-game time to be fast forwarded
if (Settings::values.custom_rtc.UsingGlobal()) {
if (ui->custom_rtc_checkbox->isChecked()) {
Settings::values.custom_rtc.SetValue(
std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()));
if (system.IsPoweredOn()) {
const s64 posix_time{Settings::values.custom_rtc.GetValue()->count() +
Service::Time::TimeManager::GetExternalTimeZoneOffset()};
system.GetTimeManager().UpdateLocalSystemClockTime(posix_time);
}
} else {
Settings::values.custom_rtc.SetValue(std::nullopt);
}
}
if (!enabled) {
return;
}
if (Settings::IsConfiguringGlobal()) {
// Guard if during game and set to game-specific value
if (Settings::values.language_index.UsingGlobal()) {
Settings::values.language_index.SetValue(ui->combo_language->currentIndex());
}
if (Settings::values.region_index.UsingGlobal()) {
Settings::values.region_index.SetValue(ui->combo_region->currentIndex());
}
if (Settings::values.time_zone_index.UsingGlobal()) {
Settings::values.time_zone_index.SetValue(ui->combo_time_zone->currentIndex());
}
if (Settings::values.sound_index.UsingGlobal()) {
Settings::values.sound_index.SetValue(ui->combo_sound->currentIndex());
}
if (Settings::values.rng_seed.UsingGlobal()) {
if (ui->rng_seed_checkbox->isChecked()) {
Settings::values.rng_seed.SetValue(
ui->rng_seed_edit->text().toULongLong(nullptr, 16));
} else {
Settings::values.rng_seed.SetValue(std::nullopt);
}
}
} else {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.language_index,
ui->combo_language);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index,
ui->combo_time_zone);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.sound_index, ui->combo_sound);
switch (use_rng_seed) {
case ConfigurationShared::CheckState::On:
case ConfigurationShared::CheckState::Off:
Settings::values.rng_seed.SetGlobal(false);
if (ui->rng_seed_checkbox->isChecked()) {
Settings::values.rng_seed.SetValue(
ui->rng_seed_edit->text().toULongLong(nullptr, 16));
} else {
Settings::values.rng_seed.SetValue(std::nullopt);
}
break;
case ConfigurationShared::CheckState::Global:
Settings::values.rng_seed.SetGlobal(false);
Settings::values.rng_seed.SetValue(std::nullopt);
Settings::values.rng_seed.SetGlobal(true);
break;
case ConfigurationShared::CheckState::Count:
break;
}
switch (use_custom_rtc) {
case ConfigurationShared::CheckState::On:
case ConfigurationShared::CheckState::Off:
Settings::values.custom_rtc.SetGlobal(false);
if (ui->custom_rtc_checkbox->isChecked()) {
Settings::values.custom_rtc.SetValue(
std::chrono::seconds(ui->custom_rtc_edit->dateTime().toSecsSinceEpoch()));
} else {
Settings::values.custom_rtc.SetValue(std::nullopt);
}
break;
case ConfigurationShared::CheckState::Global:
Settings::values.custom_rtc.SetGlobal(false);
Settings::values.custom_rtc.SetValue(std::nullopt);
Settings::values.custom_rtc.SetGlobal(true);
break;
case ConfigurationShared::CheckState::Count:
break;
}
}
Settings::Apply(system);
}
void ConfigureSystem::RefreshConsoleID() {
QMessageBox::StandardButton reply;
QString warning_text = tr("This will replace your current virtual Switch with a new one. "
"Your current virtual Switch will not be recoverable. "
"This might have unexpected effects in games. This might fail, "
"if you use an outdated config savegame. Continue?");
reply = QMessageBox::critical(this, tr("Warning"), warning_text,
QMessageBox::No | QMessageBox::Yes);
if (reply == QMessageBox::No) {
return;
}
u64 console_id{};
ui->label_console_id->setText(
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}
void ConfigureSystem::SetupPerGameUI() {
if (Settings::IsConfiguringGlobal()) {
ui->combo_language->setEnabled(Settings::values.language_index.UsingGlobal());
ui->combo_region->setEnabled(Settings::values.region_index.UsingGlobal());
ui->combo_time_zone->setEnabled(Settings::values.time_zone_index.UsingGlobal());
ui->combo_sound->setEnabled(Settings::values.sound_index.UsingGlobal());
ui->rng_seed_checkbox->setEnabled(Settings::values.rng_seed.UsingGlobal());
ui->rng_seed_edit->setEnabled(Settings::values.rng_seed.UsingGlobal());
ui->custom_rtc_checkbox->setEnabled(Settings::values.custom_rtc.UsingGlobal());
ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.UsingGlobal());
return;
}
ConfigurationShared::SetColoredComboBox(ui->combo_language, ui->label_language,
Settings::values.language_index.GetValue(true));
ConfigurationShared::SetColoredComboBox(ui->combo_region, ui->label_region,
Settings::values.region_index.GetValue(true));
ConfigurationShared::SetColoredComboBox(ui->combo_time_zone, ui->label_timezone,
Settings::values.time_zone_index.GetValue(true));
ConfigurationShared::SetColoredComboBox(ui->combo_sound, ui->label_sound,
Settings::values.sound_index.GetValue(true));
ConfigurationShared::SetColoredTristate(
ui->rng_seed_checkbox, Settings::values.rng_seed.UsingGlobal(),
Settings::values.rng_seed.GetValue().has_value(),
Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed);
ConfigurationShared::SetColoredTristate(
ui->custom_rtc_checkbox, Settings::values.custom_rtc.UsingGlobal(),
Settings::values.custom_rtc.GetValue().has_value(),
Settings::values.custom_rtc.GetValue(true).has_value(), use_custom_rtc);
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QList>
#include <QWidget>
namespace ConfigurationShared {
enum class CheckState;
}
namespace Ui {
class ConfigureSystem;
}
class ConfigureSystem : public QWidget {
Q_OBJECT
public:
explicit ConfigureSystem(QWidget* parent = nullptr);
~ConfigureSystem() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
void SetConfiguration();
void ReadSystemSettings();
void RefreshConsoleID();
void SetupPerGameUI();
std::unique_ptr<Ui::ConfigureSystem> ui;
bool enabled = false;
int language_index = 0;
int region_index = 0;
int time_zone_index = 0;
int sound_index = 0;
ConfigurationShared::CheckState use_rng_seed;
ConfigurationShared::CheckState use_custom_rtc;
};
+545
View File
@@ -0,0 +1,545 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureSystem</class>
<widget class="QWidget" name="ConfigureSystem">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>366</width>
<height>483</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="group_system_settings">
<property name="title">
<string>System Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="0">
<widget class="QLabel" name="label_region">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="combo_time_zone">
<item>
<property name="text">
<string>Auto</string>
</property>
</item>
<item>
<property name="text">
<string>Default</string>
</property>
</item>
<item>
<property name="text">
<string>CET</string>
</property>
</item>
<item>
<property name="text">
<string>CST6CDT</string>
</property>
</item>
<item>
<property name="text">
<string>Cuba</string>
</property>
</item>
<item>
<property name="text">
<string>EET</string>
</property>
</item>
<item>
<property name="text">
<string>Egypt</string>
</property>
</item>
<item>
<property name="text">
<string>Eire</string>
</property>
</item>
<item>
<property name="text">
<string>EST</string>
</property>
</item>
<item>
<property name="text">
<string>EST5EDT</string>
</property>
</item>
<item>
<property name="text">
<string>GB</string>
</property>
</item>
<item>
<property name="text">
<string>GB-Eire</string>
</property>
</item>
<item>
<property name="text">
<string>GMT</string>
</property>
</item>
<item>
<property name="text">
<string>GMT+0</string>
</property>
</item>
<item>
<property name="text">
<string>GMT-0</string>
</property>
</item>
<item>
<property name="text">
<string>GMT0</string>
</property>
</item>
<item>
<property name="text">
<string>Greenwich</string>
</property>
</item>
<item>
<property name="text">
<string>Hongkong</string>
</property>
</item>
<item>
<property name="text">
<string>HST</string>
</property>
</item>
<item>
<property name="text">
<string>Iceland</string>
</property>
</item>
<item>
<property name="text">
<string>Iran</string>
</property>
</item>
<item>
<property name="text">
<string>Israel</string>
</property>
</item>
<item>
<property name="text">
<string>Jamaica</string>
</property>
</item>
<item>
<property name="text">
<string>Japan</string>
</property>
</item>
<item>
<property name="text">
<string>Kwajalein</string>
</property>
</item>
<item>
<property name="text">
<string>Libya</string>
</property>
</item>
<item>
<property name="text">
<string>MET</string>
</property>
</item>
<item>
<property name="text">
<string>MST</string>
</property>
</item>
<item>
<property name="text">
<string>MST7MDT</string>
</property>
</item>
<item>
<property name="text">
<string>Navajo</string>
</property>
</item>
<item>
<property name="text">
<string>NZ</string>
</property>
</item>
<item>
<property name="text">
<string>NZ-CHAT</string>
</property>
</item>
<item>
<property name="text">
<string>Poland</string>
</property>
</item>
<item>
<property name="text">
<string>Portugal</string>
</property>
</item>
<item>
<property name="text">
<string>PRC</string>
</property>
</item>
<item>
<property name="text">
<string>PST8PDT</string>
</property>
</item>
<item>
<property name="text">
<string>ROC</string>
</property>
</item>
<item>
<property name="text">
<string>ROK</string>
</property>
</item>
<item>
<property name="text">
<string>Singapore</string>
</property>
</item>
<item>
<property name="text">
<string>Turkey</string>
</property>
</item>
<item>
<property name="text">
<string>UCT</string>
</property>
</item>
<item>
<property name="text">
<string>Universal</string>
</property>
</item>
<item>
<property name="text">
<string>UTC</string>
</property>
</item>
<item>
<property name="text">
<string>W-SU</string>
</property>
</item>
<item>
<property name="text">
<string>WET</string>
</property>
</item>
<item>
<property name="text">
<string>Zulu</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="combo_region">
<item>
<property name="text">
<string>Japan</string>
</property>
</item>
<item>
<property name="text">
<string>USA</string>
</property>
</item>
<item>
<property name="text">
<string>Europe</string>
</property>
</item>
<item>
<property name="text">
<string>Australia</string>
</property>
</item>
<item>
<property name="text">
<string>China</string>
</property>
</item>
<item>
<property name="text">
<string>Korea</string>
</property>
</item>
<item>
<property name="text">
<string>Taiwan</string>
</property>
</item>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_timezone">
<property name="text">
<string>Time Zone:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="combo_language">
<property name="toolTip">
<string>Note: this can be overridden when region setting is auto-select</string>
</property>
<item>
<property name="text">
<string>Japanese (日本語)</string>
</property>
</item>
<item>
<property name="text">
<string>English</string>
</property>
</item>
<item>
<property name="text">
<string>French (français)</string>
</property>
</item>
<item>
<property name="text">
<string>German (Deutsch)</string>
</property>
</item>
<item>
<property name="text">
<string>Italian (italiano)</string>
</property>
</item>
<item>
<property name="text">
<string>Spanish (español)</string>
</property>
</item>
<item>
<property name="text">
<string>Chinese</string>
</property>
</item>
<item>
<property name="text">
<string>Korean (한국어)</string>
</property>
</item>
<item>
<property name="text">
<string>Dutch (Nederlands)</string>
</property>
</item>
<item>
<property name="text">
<string>Portuguese (português)</string>
</property>
</item>
<item>
<property name="text">
<string>Russian (Русский)</string>
</property>
</item>
<item>
<property name="text">
<string>Taiwanese</string>
</property>
</item>
<item>
<property name="text">
<string>British English</string>
</property>
</item>
<item>
<property name="text">
<string>Canadian French</string>
</property>
</item>
<item>
<property name="text">
<string>Latin American Spanish</string>
</property>
</item>
<item>
<property name="text">
<string>Simplified Chinese</string>
</property>
</item>
<item>
<property name="text">
<string>Traditional Chinese (正體中文)</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="custom_rtc_checkbox">
<property name="text">
<string>Custom RTC</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_language">
<property name="text">
<string>Language</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="rng_seed_checkbox">
<property name="text">
<string>RNG Seed</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="combo_sound">
<item>
<property name="text">
<string>Mono</string>
</property>
</item>
<item>
<property name="text">
<string>Stereo</string>
</property>
</item>
<item>
<property name="text">
<string>Surround</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_console_id">
<property name="text">
<string>Console ID:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_sound">
<property name="text">
<string>Sound output mode</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QDateTimeEdit" name="custom_rtc_edit">
<property name="minimumDate">
<date>
<year>1970</year>
<month>1</month>
<day>1</day>
</date>
</property>
<property name="displayFormat">
<string>d MMM yyyy h:mm:ss AP</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="rng_seed_edit">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<family>Lucida Console</family>
</font>
</property>
<property name="inputMask">
<string notr="true">HHHHHHHH</string>
</property>
<property name="maxLength">
<number>8</number>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="button_regenerate_console_id">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string>Regenerate</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
<string>System settings are available only when game is not running.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
+623
View File
@@ -0,0 +1,623 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QInputDialog>
#include <QKeyEvent>
#include <QMessageBox>
#include <QMouseEvent>
#include <QResizeEvent>
#include <QStandardItemModel>
#include <QTimer>
#include "common/param_package.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/settings.h"
#include "input_common/main.h"
#include "ui_configure_touch_from_button.h"
#include "yuzu/configuration/configure_touch_from_button.h"
#include "yuzu/configuration/configure_touch_widget.h"
static QString GetKeyName(int key_code) {
switch (key_code) {
case Qt::Key_Shift:
return QObject::tr("Shift");
case Qt::Key_Control:
return QObject::tr("Ctrl");
case Qt::Key_Alt:
return QObject::tr("Alt");
case Qt::Key_Meta:
return QString{};
default:
return QKeySequence(key_code).toString();
}
}
static QString ButtonToText(const Common::ParamPackage& param) {
if (!param.Has("engine")) {
return QObject::tr("[not set]");
}
if (param.Get("engine", "") == "keyboard") {
return GetKeyName(param.Get("code", 0));
}
if (param.Get("engine", "") == "sdl") {
if (param.Has("hat")) {
const QString hat_str = QString::fromStdString(param.Get("hat", ""));
const QString direction_str = QString::fromStdString(param.Get("direction", ""));
return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
}
if (param.Has("axis")) {
const QString axis_str = QString::fromStdString(param.Get("axis", ""));
const QString direction_str = QString::fromStdString(param.Get("direction", ""));
return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
}
if (param.Has("button")) {
const QString button_str = QString::fromStdString(param.Get("button", ""));
return QObject::tr("Button %1").arg(button_str);
}
return {};
}
return QObject::tr("[unknown]");
}
ConfigureTouchFromButton::ConfigureTouchFromButton(
QWidget* parent, const std::vector<Settings::TouchFromButtonMap>& touch_maps,
InputCommon::InputSubsystem* input_subsystem_, const int default_index)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchFromButton>()),
touch_maps(touch_maps), input_subsystem{input_subsystem_}, selected_index(default_index),
timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
ui->setupUi(this);
binding_list_model = new QStandardItemModel(0, 3, this);
binding_list_model->setHorizontalHeaderLabels(
{tr("Button"), tr("X", "X axis"), tr("Y", "Y axis")});
ui->binding_list->setModel(binding_list_model);
ui->bottom_screen->SetCoordLabel(ui->coord_label);
SetConfiguration();
UpdateUiDisplay();
ConnectEvents();
}
ConfigureTouchFromButton::~ConfigureTouchFromButton() = default;
void ConfigureTouchFromButton::showEvent(QShowEvent* ev) {
QWidget::showEvent(ev);
// width values are not valid in the constructor
const int w =
ui->binding_list->viewport()->contentsRect().width() / binding_list_model->columnCount();
if (w <= 0) {
return;
}
ui->binding_list->setColumnWidth(0, w);
ui->binding_list->setColumnWidth(1, w);
ui->binding_list->setColumnWidth(2, w);
}
void ConfigureTouchFromButton::SetConfiguration() {
for (const auto& touch_map : touch_maps) {
ui->mapping->addItem(QString::fromStdString(touch_map.name));
}
ui->mapping->setCurrentIndex(selected_index);
}
void ConfigureTouchFromButton::UpdateUiDisplay() {
ui->button_delete->setEnabled(touch_maps.size() > 1);
ui->button_delete_bind->setEnabled(false);
binding_list_model->removeRows(0, binding_list_model->rowCount());
for (const auto& button_str : touch_maps[selected_index].buttons) {
Common::ParamPackage package{button_str};
QStandardItem* button = new QStandardItem(ButtonToText(package));
button->setData(QString::fromStdString(button_str));
button->setEditable(false);
QStandardItem* xcoord = new QStandardItem(QString::number(package.Get("x", 0)));
QStandardItem* ycoord = new QStandardItem(QString::number(package.Get("y", 0)));
binding_list_model->appendRow({button, xcoord, ycoord});
const int dot = ui->bottom_screen->AddDot(package.Get("x", 0), package.Get("y", 0));
button->setData(dot, DataRoleDot);
}
}
void ConfigureTouchFromButton::ConnectEvents() {
connect(ui->mapping, qOverload<int>(&QComboBox::currentIndexChanged), this, [this](int index) {
SaveCurrentMapping();
selected_index = index;
UpdateUiDisplay();
});
connect(ui->button_new, &QPushButton::clicked, this, &ConfigureTouchFromButton::NewMapping);
connect(ui->button_delete, &QPushButton::clicked, this,
&ConfigureTouchFromButton::DeleteMapping);
connect(ui->button_rename, &QPushButton::clicked, this,
&ConfigureTouchFromButton::RenameMapping);
connect(ui->button_delete_bind, &QPushButton::clicked, this,
&ConfigureTouchFromButton::DeleteBinding);
connect(ui->binding_list, &QTreeView::doubleClicked, this,
&ConfigureTouchFromButton::EditBinding);
connect(ui->binding_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
&ConfigureTouchFromButton::OnBindingSelection);
connect(binding_list_model, &QStandardItemModel::itemChanged, this,
&ConfigureTouchFromButton::OnBindingChanged);
connect(ui->binding_list->model(), &QStandardItemModel::rowsAboutToBeRemoved, this,
&ConfigureTouchFromButton::OnBindingDeleted);
connect(ui->bottom_screen, &TouchScreenPreview::DotAdded, this,
&ConfigureTouchFromButton::NewBinding);
connect(ui->bottom_screen, &TouchScreenPreview::DotSelected, this,
&ConfigureTouchFromButton::SetActiveBinding);
connect(ui->bottom_screen, &TouchScreenPreview::DotMoved, this,
&ConfigureTouchFromButton::SetCoordinates);
connect(ui->buttonBox, &QDialogButtonBox::accepted, this,
&ConfigureTouchFromButton::ApplyConfiguration);
connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
connect(poll_timer.get(), &QTimer::timeout, [this]() {
Common::ParamPackage params;
for (auto& poller : device_pollers) {
params = poller->GetNextInput();
if (params.Has("engine")) {
SetPollingResult(params, false);
return;
}
}
});
}
void ConfigureTouchFromButton::SaveCurrentMapping() {
auto& map = touch_maps[selected_index];
map.buttons.clear();
for (int i = 0, rc = binding_list_model->rowCount(); i < rc; ++i) {
const auto bind_str = binding_list_model->index(i, 0)
.data(Qt::ItemDataRole::UserRole + 1)
.toString()
.toStdString();
if (bind_str.empty()) {
continue;
}
Common::ParamPackage params{bind_str};
if (!params.Has("engine")) {
continue;
}
params.Set("x", binding_list_model->index(i, 1).data().toInt());
params.Set("y", binding_list_model->index(i, 2).data().toInt());
map.buttons.emplace_back(params.Serialize());
}
}
void ConfigureTouchFromButton::NewMapping() {
const QString name =
QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile."));
if (name.isEmpty()) {
return;
}
touch_maps.emplace_back(Settings::TouchFromButtonMap{name.toStdString(), {}});
ui->mapping->addItem(name);
ui->mapping->setCurrentIndex(ui->mapping->count() - 1);
}
void ConfigureTouchFromButton::DeleteMapping() {
const auto answer = QMessageBox::question(
this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->mapping->currentText()));
if (answer != QMessageBox::Yes) {
return;
}
const bool blocked = ui->mapping->blockSignals(true);
ui->mapping->removeItem(selected_index);
ui->mapping->blockSignals(blocked);
touch_maps.erase(touch_maps.begin() + selected_index);
selected_index = ui->mapping->currentIndex();
UpdateUiDisplay();
}
void ConfigureTouchFromButton::RenameMapping() {
const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:"));
if (new_name.isEmpty()) {
return;
}
ui->mapping->setItemText(selected_index, new_name);
touch_maps[selected_index].name = new_name.toStdString();
}
void ConfigureTouchFromButton::GetButtonInput(const int row_index, const bool is_new) {
binding_list_model->item(row_index, 0)->setText(tr("[press key]"));
input_setter = [this, row_index, is_new](const Common::ParamPackage& params,
const bool cancel) {
auto* cell = binding_list_model->item(row_index, 0);
if (cancel) {
if (is_new) {
binding_list_model->removeRow(row_index);
} else {
cell->setText(
ButtonToText(Common::ParamPackage{cell->data().toString().toStdString()}));
}
} else {
cell->setText(ButtonToText(params));
cell->setData(QString::fromStdString(params.Serialize()));
}
};
device_pollers = input_subsystem->GetPollers(InputCommon::Polling::DeviceType::Button);
for (auto& poller : device_pollers) {
poller->Start();
}
grabKeyboard();
grabMouse();
qApp->setOverrideCursor(QCursor(Qt::CursorShape::ArrowCursor));
timeout_timer->start(5000); // Cancel after 5 seconds
poll_timer->start(200); // Check for new inputs every 200ms
}
void ConfigureTouchFromButton::NewBinding(const QPoint& pos) {
auto* button = new QStandardItem();
button->setEditable(false);
auto* x_coord = new QStandardItem(QString::number(pos.x()));
auto* y_coord = new QStandardItem(QString::number(pos.y()));
const int dot_id = ui->bottom_screen->AddDot(pos.x(), pos.y());
button->setData(dot_id, DataRoleDot);
binding_list_model->appendRow({button, x_coord, y_coord});
ui->binding_list->setFocus();
ui->binding_list->setCurrentIndex(button->index());
GetButtonInput(binding_list_model->rowCount() - 1, true);
}
void ConfigureTouchFromButton::EditBinding(const QModelIndex& qi) {
if (qi.row() >= 0 && qi.column() == 0) {
GetButtonInput(qi.row(), false);
}
}
void ConfigureTouchFromButton::DeleteBinding() {
const int row_index = ui->binding_list->currentIndex().row();
if (row_index < 0) {
return;
}
ui->bottom_screen->RemoveDot(binding_list_model->index(row_index, 0).data(DataRoleDot).toInt());
binding_list_model->removeRow(row_index);
}
void ConfigureTouchFromButton::OnBindingSelection(const QItemSelection& selected,
const QItemSelection& deselected) {
ui->button_delete_bind->setEnabled(!selected.isEmpty());
if (!selected.isEmpty()) {
const auto dot_data = selected.indexes().first().data(DataRoleDot);
if (dot_data.isValid()) {
ui->bottom_screen->HighlightDot(dot_data.toInt());
}
}
if (!deselected.isEmpty()) {
const auto dot_data = deselected.indexes().first().data(DataRoleDot);
if (dot_data.isValid()) {
ui->bottom_screen->HighlightDot(dot_data.toInt(), false);
}
}
}
void ConfigureTouchFromButton::OnBindingChanged(QStandardItem* item) {
if (item->column() == 0) {
return;
}
const bool blocked = binding_list_model->blockSignals(true);
item->setText(QString::number(
std::clamp(item->text().toInt(), 0,
static_cast<int>((item->column() == 1 ? Layout::ScreenUndocked::Width
: Layout::ScreenUndocked::Height) -
1))));
binding_list_model->blockSignals(blocked);
const auto dot_data = binding_list_model->index(item->row(), 0).data(DataRoleDot);
if (dot_data.isValid()) {
ui->bottom_screen->MoveDot(dot_data.toInt(),
binding_list_model->item(item->row(), 1)->text().toInt(),
binding_list_model->item(item->row(), 2)->text().toInt());
}
}
void ConfigureTouchFromButton::OnBindingDeleted(const QModelIndex& parent, int first, int last) {
for (int i = first; i <= last; ++i) {
const auto ix = binding_list_model->index(i, 0);
if (!ix.isValid()) {
return;
}
const auto dot_data = ix.data(DataRoleDot);
if (dot_data.isValid()) {
ui->bottom_screen->RemoveDot(dot_data.toInt());
}
}
}
void ConfigureTouchFromButton::SetActiveBinding(const int dot_id) {
for (int i = 0; i < binding_list_model->rowCount(); ++i) {
if (binding_list_model->index(i, 0).data(DataRoleDot) == dot_id) {
ui->binding_list->setCurrentIndex(binding_list_model->index(i, 0));
ui->binding_list->setFocus();
return;
}
}
}
void ConfigureTouchFromButton::SetCoordinates(const int dot_id, const QPoint& pos) {
for (int i = 0; i < binding_list_model->rowCount(); ++i) {
if (binding_list_model->item(i, 0)->data(DataRoleDot) == dot_id) {
binding_list_model->item(i, 1)->setText(QString::number(pos.x()));
binding_list_model->item(i, 2)->setText(QString::number(pos.y()));
return;
}
}
}
void ConfigureTouchFromButton::SetPollingResult(const Common::ParamPackage& params,
const bool cancel) {
releaseKeyboard();
releaseMouse();
qApp->restoreOverrideCursor();
timeout_timer->stop();
poll_timer->stop();
for (auto& poller : device_pollers) {
poller->Stop();
}
if (input_setter) {
(*input_setter)(params, cancel);
input_setter.reset();
}
}
void ConfigureTouchFromButton::keyPressEvent(QKeyEvent* event) {
if (!input_setter && event->key() == Qt::Key_Delete) {
DeleteBinding();
return;
}
if (!input_setter) {
return QDialog::keyPressEvent(event);
}
if (event->key() != Qt::Key_Escape) {
SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
false);
} else {
SetPollingResult({}, true);
}
}
void ConfigureTouchFromButton::ApplyConfiguration() {
SaveCurrentMapping();
accept();
}
int ConfigureTouchFromButton::GetSelectedIndex() const {
return selected_index;
}
std::vector<Settings::TouchFromButtonMap> ConfigureTouchFromButton::GetMaps() const {
return touch_maps;
}
TouchScreenPreview::TouchScreenPreview(QWidget* parent) : QFrame(parent) {
setBackgroundRole(QPalette::ColorRole::Base);
}
TouchScreenPreview::~TouchScreenPreview() = default;
void TouchScreenPreview::SetCoordLabel(QLabel* const label) {
coord_label = label;
}
int TouchScreenPreview::AddDot(const int device_x, const int device_y) {
QFont dot_font{QStringLiteral("monospace")};
dot_font.setStyleHint(QFont::Monospace);
dot_font.setPointSize(20);
auto* dot = new QLabel(this);
dot->setAttribute(Qt::WA_TranslucentBackground);
dot->setFont(dot_font);
dot->setText(QChar(0xD7)); // U+00D7 Multiplication Sign
dot->setAlignment(Qt::AlignmentFlag::AlignCenter);
dot->setProperty(PropId, ++max_dot_id);
dot->setProperty(PropX, device_x);
dot->setProperty(PropY, device_y);
dot->setCursor(Qt::CursorShape::PointingHandCursor);
dot->setMouseTracking(true);
dot->installEventFilter(this);
dot->show();
PositionDot(dot, device_x, device_y);
dots.emplace_back(max_dot_id, dot);
return max_dot_id;
}
void TouchScreenPreview::RemoveDot(const int id) {
const auto iter = std::find_if(dots.begin(), dots.end(),
[id](const auto& entry) { return entry.first == id; });
if (iter == dots.cend()) {
return;
}
iter->second->deleteLater();
dots.erase(iter);
}
void TouchScreenPreview::HighlightDot(const int id, const bool active) const {
for (const auto& dot : dots) {
if (dot.first == id) {
// use color property from the stylesheet, or fall back to the default palette
if (dot_highlight_color.isValid()) {
dot.second->setStyleSheet(
active ? QStringLiteral("color: %1").arg(dot_highlight_color.name())
: QString{});
} else {
dot.second->setForegroundRole(active ? QPalette::ColorRole::LinkVisited
: QPalette::ColorRole::NoRole);
}
if (active) {
dot.second->raise();
}
return;
}
}
}
void TouchScreenPreview::MoveDot(const int id, const int device_x, const int device_y) const {
const auto iter = std::find_if(dots.begin(), dots.end(),
[id](const auto& entry) { return entry.first == id; });
if (iter == dots.cend()) {
return;
}
iter->second->setProperty(PropX, device_x);
iter->second->setProperty(PropY, device_y);
PositionDot(iter->second, device_x, device_y);
}
void TouchScreenPreview::resizeEvent(QResizeEvent* event) {
if (ignore_resize) {
return;
}
const int target_width = std::min(width(), height() * 4 / 3);
const int target_height = std::min(height(), width() * 3 / 4);
if (target_width == width() && target_height == height()) {
return;
}
ignore_resize = true;
setGeometry((parentWidget()->contentsRect().width() - target_width) / 2, y(), target_width,
target_height);
ignore_resize = false;
if (event->oldSize().width() != target_width || event->oldSize().height() != target_height) {
for (const auto& dot : dots) {
PositionDot(dot.second);
}
}
}
void TouchScreenPreview::mouseMoveEvent(QMouseEvent* event) {
if (!coord_label) {
return;
}
const auto pos = MapToDeviceCoords(event->x(), event->y());
if (pos) {
coord_label->setText(QStringLiteral("X: %1, Y: %2").arg(pos->x()).arg(pos->y()));
} else {
coord_label->clear();
}
}
void TouchScreenPreview::leaveEvent(QEvent* event) {
if (coord_label) {
coord_label->clear();
}
}
void TouchScreenPreview::mousePressEvent(QMouseEvent* event) {
if (event->button() != Qt::MouseButton::LeftButton) {
return;
}
const auto pos = MapToDeviceCoords(event->x(), event->y());
if (pos) {
emit DotAdded(*pos);
}
}
bool TouchScreenPreview::eventFilter(QObject* obj, QEvent* event) {
switch (event->type()) {
case QEvent::Type::MouseButtonPress: {
const auto mouse_event = static_cast<QMouseEvent*>(event);
if (mouse_event->button() != Qt::MouseButton::LeftButton) {
break;
}
emit DotSelected(obj->property(PropId).toInt());
drag_state.dot = qobject_cast<QLabel*>(obj);
drag_state.start_pos = mouse_event->globalPos();
return true;
}
case QEvent::Type::MouseMove: {
if (!drag_state.dot) {
break;
}
const auto mouse_event = static_cast<QMouseEvent*>(event);
if (!drag_state.active) {
drag_state.active =
(mouse_event->globalPos() - drag_state.start_pos).manhattanLength() >=
QApplication::startDragDistance();
if (!drag_state.active) {
break;
}
}
auto current_pos = mapFromGlobal(mouse_event->globalPos());
current_pos.setX(std::clamp(current_pos.x(), contentsMargins().left(),
contentsMargins().left() + contentsRect().width() - 1));
current_pos.setY(std::clamp(current_pos.y(), contentsMargins().top(),
contentsMargins().top() + contentsRect().height() - 1));
const auto device_coord = MapToDeviceCoords(current_pos.x(), current_pos.y());
if (device_coord) {
drag_state.dot->setProperty(PropX, device_coord->x());
drag_state.dot->setProperty(PropY, device_coord->y());
PositionDot(drag_state.dot, device_coord->x(), device_coord->y());
emit DotMoved(drag_state.dot->property(PropId).toInt(), *device_coord);
if (coord_label) {
coord_label->setText(
QStringLiteral("X: %1, Y: %2").arg(device_coord->x()).arg(device_coord->y()));
}
}
return true;
}
case QEvent::Type::MouseButtonRelease: {
drag_state.dot.clear();
drag_state.active = false;
return true;
}
default:
break;
}
return obj->eventFilter(obj, event);
}
std::optional<QPoint> TouchScreenPreview::MapToDeviceCoords(const int screen_x,
const int screen_y) const {
const float t_x = 0.5f + static_cast<float>(screen_x - contentsMargins().left()) *
(Layout::ScreenUndocked::Width - 1) / (contentsRect().width() - 1);
const float t_y = 0.5f + static_cast<float>(screen_y - contentsMargins().top()) *
(Layout::ScreenUndocked::Height - 1) /
(contentsRect().height() - 1);
if (t_x >= 0.5f && t_x < Layout::ScreenUndocked::Width && t_y >= 0.5f &&
t_y < Layout::ScreenUndocked::Height) {
return QPoint{static_cast<int>(t_x), static_cast<int>(t_y)};
}
return std::nullopt;
}
void TouchScreenPreview::PositionDot(QLabel* const dot, const int device_x,
const int device_y) const {
const float device_coord_x =
static_cast<float>(device_x >= 0 ? device_x : dot->property(PropX).toInt());
int x_coord = static_cast<int>(
device_coord_x * (contentsRect().width() - 1) / (Layout::ScreenUndocked::Width - 1) +
contentsMargins().left() - static_cast<float>(dot->width()) / 2 + 0.5f);
const float device_coord_y =
static_cast<float>(device_y >= 0 ? device_y : dot->property(PropY).toInt());
const int y_coord = static_cast<int>(
device_coord_y * (contentsRect().height() - 1) / (Layout::ScreenUndocked::Height - 1) +
contentsMargins().top() - static_cast<float>(dot->height()) / 2 + 0.5f);
dot->move(x_coord, y_coord);
}
+92
View File
@@ -0,0 +1,92 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include <optional>
#include <vector>
#include <QDialog>
class QItemSelection;
class QModelIndex;
class QStandardItemModel;
class QStandardItem;
class QTimer;
namespace Common {
class ParamPackage;
}
namespace InputCommon {
class InputSubsystem;
}
namespace InputCommon::Polling {
class DevicePoller;
}
namespace Settings {
struct TouchFromButtonMap;
}
namespace Ui {
class ConfigureTouchFromButton;
}
class ConfigureTouchFromButton : public QDialog {
Q_OBJECT
public:
explicit ConfigureTouchFromButton(QWidget* parent,
const std::vector<Settings::TouchFromButtonMap>& touch_maps,
InputCommon::InputSubsystem* input_subsystem_,
int default_index = 0);
~ConfigureTouchFromButton() override;
int GetSelectedIndex() const;
std::vector<Settings::TouchFromButtonMap> GetMaps() const;
public slots:
void ApplyConfiguration();
void NewBinding(const QPoint& pos);
void SetActiveBinding(int dot_id);
void SetCoordinates(int dot_id, const QPoint& pos);
protected:
void showEvent(QShowEvent* ev) override;
void keyPressEvent(QKeyEvent* event) override;
private slots:
void NewMapping();
void DeleteMapping();
void RenameMapping();
void EditBinding(const QModelIndex& qi);
void DeleteBinding();
void OnBindingSelection(const QItemSelection& selected, const QItemSelection& deselected);
void OnBindingChanged(QStandardItem* item);
void OnBindingDeleted(const QModelIndex& parent, int first, int last);
private:
void SetConfiguration();
void UpdateUiDisplay();
void ConnectEvents();
void GetButtonInput(int row_index, bool is_new);
void SetPollingResult(const Common::ParamPackage& params, bool cancel);
void SaveCurrentMapping();
std::unique_ptr<Ui::ConfigureTouchFromButton> ui;
std::vector<Settings::TouchFromButtonMap> touch_maps;
QStandardItemModel* binding_list_model;
InputCommon::InputSubsystem* input_subsystem;
int selected_index;
std::unique_ptr<QTimer> timeout_timer;
std::unique_ptr<QTimer> poll_timer;
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
std::optional<std::function<void(const Common::ParamPackage&, bool)>> input_setter;
static constexpr int DataRoleDot = Qt::ItemDataRole::UserRole + 2;
};
+221
View File
@@ -0,0 +1,221 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureTouchFromButton</class>
<widget class="QDialog" name="ConfigureTouchFromButton">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>500</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>Configure Touchscreen Mappings</string>
</property>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Mapping:</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="mapping">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_new">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>New</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_delete">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="button_rename">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Rename</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Click the bottom area to add a point, then press a button to bind.
Drag points to change position, or double-click table cells to edit values.</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="button_delete_bind">
<property name="text">
<string>Delete Point</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="binding_list">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<property name="uniformRowHeights">
<bool>true</bool>
</property>
<property name="itemsExpandable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="TouchScreenPreview" name="bottom_screen">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>160</width>
<height>120</height>
</size>
</property>
<property name="baseSize">
<size>
<width>320</width>
<height>240</height>
</size>
</property>
<property name="cursor">
<cursorShape>CrossCursor</cursorShape>
</property>
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="coord_label">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TouchScreenPreview</class>
<extends>QFrame</extends>
<header>yuzu/configuration/configure_touch_widget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>ConfigureTouchFromButton</receiver>
<slot>reject()</slot>
</connection>
</connections>
</ui>
+62
View File
@@ -0,0 +1,62 @@
// Copyright 2020 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <optional>
#include <utility>
#include <vector>
#include <QFrame>
#include <QPointer>
class QLabel;
// Widget for representing touchscreen coordinates
class TouchScreenPreview : public QFrame {
Q_OBJECT
Q_PROPERTY(QColor dotHighlightColor MEMBER dot_highlight_color)
public:
explicit TouchScreenPreview(QWidget* parent);
~TouchScreenPreview() override;
void SetCoordLabel(QLabel*);
int AddDot(int device_x, int device_y);
void RemoveDot(int id);
void HighlightDot(int id, bool active = true) const;
void MoveDot(int id, int device_x, int device_y) const;
signals:
void DotAdded(const QPoint& pos);
void DotSelected(int dot_id);
void DotMoved(int dot_id, const QPoint& pos);
protected:
void resizeEvent(QResizeEvent*) override;
void mouseMoveEvent(QMouseEvent*) override;
void leaveEvent(QEvent*) override;
void mousePressEvent(QMouseEvent*) override;
bool eventFilter(QObject*, QEvent*) override;
private:
std::optional<QPoint> MapToDeviceCoords(int screen_x, int screen_y) const;
void PositionDot(QLabel* dot, int device_x = -1, int device_y = -1) const;
bool ignore_resize = false;
QPointer<QLabel> coord_label;
std::vector<std::pair<int, QLabel*>> dots;
int max_dot_id = 0;
QColor dot_highlight_color;
static constexpr char PropId[] = "dot_id";
static constexpr char PropX[] = "device_x";
static constexpr char PropY[] = "device_y";
struct DragState {
bool active = false;
QPointer<QLabel> dot;
QPoint start_pos;
};
DragState drag_state;
};
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include "ui_configure_touchscreen_advanced.h"
#include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_touchscreen_advanced.h"
ConfigureTouchscreenAdvanced::ConfigureTouchscreenAdvanced(QWidget* parent)
: QDialog(parent), ui(std::make_unique<Ui::ConfigureTouchscreenAdvanced>()) {
ui->setupUi(this);
connect(ui->restore_defaults_button, &QPushButton::clicked, this,
&ConfigureTouchscreenAdvanced::RestoreDefaults);
LoadConfiguration();
resize(0, 0);
}
ConfigureTouchscreenAdvanced::~ConfigureTouchscreenAdvanced() = default;
void ConfigureTouchscreenAdvanced::changeEvent(QEvent* event) {
if (event->type() == QEvent::LanguageChange) {
RetranslateUI();
}
QDialog::changeEvent(event);
}
void ConfigureTouchscreenAdvanced::RetranslateUI() {
ui->retranslateUi(this);
}
void ConfigureTouchscreenAdvanced::ApplyConfiguration() {
Settings::values.touchscreen.finger = ui->finger_box->value();
Settings::values.touchscreen.diameter_x = ui->diameter_x_box->value();
Settings::values.touchscreen.diameter_y = ui->diameter_y_box->value();
Settings::values.touchscreen.rotation_angle = ui->angle_box->value();
}
void ConfigureTouchscreenAdvanced::LoadConfiguration() {
ui->finger_box->setValue(Settings::values.touchscreen.finger);
ui->diameter_x_box->setValue(Settings::values.touchscreen.diameter_x);
ui->diameter_y_box->setValue(Settings::values.touchscreen.diameter_y);
ui->angle_box->setValue(Settings::values.touchscreen.rotation_angle);
}
void ConfigureTouchscreenAdvanced::RestoreDefaults() {
ui->finger_box->setValue(0);
ui->diameter_x_box->setValue(15);
ui->diameter_y_box->setValue(15);
ui->angle_box->setValue(0);
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
namespace Ui {
class ConfigureTouchscreenAdvanced;
}
class ConfigureTouchscreenAdvanced : public QDialog {
Q_OBJECT
public:
explicit ConfigureTouchscreenAdvanced(QWidget* parent);
~ConfigureTouchscreenAdvanced() override;
void ApplyConfiguration();
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
/// Load configuration settings.
void LoadConfiguration();
/// Restore all buttons to their default values.
void RestoreDefaults();
std::unique_ptr<Ui::ConfigureTouchscreenAdvanced> ui;
};

Some files were not shown because too many files have changed in this diff Show More