Temporarily added my GUI

This commit is contained in:
kalaposfos13 2024-11-16 10:06:41 +01:00
parent 01100091ff
commit 9062be0fd7
5 changed files with 486 additions and 0 deletions

View File

@ -0,0 +1,187 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "kbm_config_dialog.h"
#include "kbm_help_dialog.h"
#include <fstream>
#include <iostream>
#include "common/config.h"
#include "common/path_util.h"
#include "game_info.h"
#include "src/sdl_window.h"
#include <QCloseEvent>
#include <QComboBox>
#include <QFile>
#include <QHBoxLayout>
#include <QMessageBox>
#include <QPushButton>
#include <QTextStream>
#include <QVBoxLayout>
EditorDialog::EditorDialog(QWidget* parent) : QDialog(parent) {
setWindowTitle("Edit Config File");
resize(600, 400);
// Create the editor widget
editor = new QPlainTextEdit(this);
editorFont.setPointSize(10); // Set default text size
editor->setFont(editorFont); // Apply font to the editor
// Create the game selection combo box
gameComboBox = new QComboBox(this);
gameComboBox->addItem("default"); // Add default option
/*
gameComboBox = new QComboBox(this);
layout->addWidget(gameComboBox); // Add the combobox for selecting game configurations
// Populate the combo box with game configurations
QStringList gameConfigs = GameInfoClass::GetGameInfo(this);
gameComboBox->addItems(gameConfigs);
gameComboBox->setCurrentText("default.ini"); // Set the default selection
*/
// Load all installed games
loadInstalledGames();
// Create Save, Cancel, and Help buttons
QPushButton* saveButton = new QPushButton("Save", this);
QPushButton* cancelButton = new QPushButton("Cancel", this);
QPushButton* helpButton = new QPushButton("Help", this);
// Layout for the game selection and buttons
QHBoxLayout* topLayout = new QHBoxLayout();
topLayout->addWidget(gameComboBox);
topLayout->addStretch();
topLayout->addWidget(saveButton);
topLayout->addWidget(cancelButton);
topLayout->addWidget(helpButton);
// Main layout with editor and buttons
QVBoxLayout* layout = new QVBoxLayout(this);
layout->addLayout(topLayout);
layout->addWidget(editor);
// Load the default config file content into the editor
loadFile(gameComboBox->currentText());
// Connect button and combo box signals
connect(saveButton, &QPushButton::clicked, this, &EditorDialog::onSaveClicked);
connect(cancelButton, &QPushButton::clicked, this, &EditorDialog::onCancelClicked);
connect(helpButton, &QPushButton::clicked, this, &EditorDialog::onHelpClicked);
connect(gameComboBox, &QComboBox::currentTextChanged, this,
&EditorDialog::onGameSelectionChanged);
}
void EditorDialog::loadFile(QString game) {
// to make sure the files and the directory do exist
const auto config_file = Config::GetFoolproofKbmConfigFile(game.toStdString());
QFile file(config_file);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream in(&file);
editor->setPlainText(in.readAll());
originalConfig = editor->toPlainText();
file.close();
} else {
QMessageBox::warning(this, "Error", "Could not open the file for reading");
}
}
void EditorDialog::saveFile(QString game) {
// to make sure the files and the directory do exist
const auto config_file = Config::GetFoolproofKbmConfigFile(game.toStdString());
QFile file(config_file);
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
out << editor->toPlainText();
file.close();
} else {
QMessageBox::warning(this, "Error", "Could not open the file for writing");
}
}
// Override the close event to show the save confirmation dialog only if changes were made
void EditorDialog::closeEvent(QCloseEvent* event) {
if (hasUnsavedChanges()) {
QMessageBox::StandardButton reply;
reply = QMessageBox::question(this, "Save Changes", "Do you want to save changes?",
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if (reply == QMessageBox::Yes) {
saveFile(gameComboBox->currentText());
event->accept(); // Close the dialog
} else if (reply == QMessageBox::No) {
event->accept(); // Close the dialog without saving
} else {
event->ignore(); // Cancel the close event
}
} else {
event->accept(); // No changes, close the dialog without prompting
}
}
void EditorDialog::keyPressEvent(QKeyEvent* event) {
if (event->key() == Qt::Key_Escape) {
close(); // Trigger the close action, same as pressing the close button
} else {
QDialog::keyPressEvent(event); // Call the base class implementation for other keys
}
}
void EditorDialog::onSaveClicked() {
saveFile(gameComboBox->currentText());
reject(); // Close the dialog
}
void EditorDialog::onCancelClicked() {
reject(); // Close the dialog
}
bool isHelpOpen = false;
HelpDialog* helpDialog;
void EditorDialog::onHelpClicked() {
if (!isHelpOpen) {
helpDialog = new HelpDialog(this);
helpDialog->setWindowTitle("Help");
helpDialog->setAttribute(Qt::WA_DeleteOnClose); // Clean up on close
// Get the position and size of the Config window
QRect configGeometry = this->geometry();
int helpX = configGeometry.x() + configGeometry.width() + 10; // 10 pixels offset
int helpY = configGeometry.y();
// Move the Help dialog to the right side of the Config window
helpDialog->move(helpX, helpY);
helpDialog->show();
isHelpOpen = true;
} else {
helpDialog->close();
isHelpOpen = false;
}
}
bool EditorDialog::hasUnsavedChanges() {
// Compare the current content with the original content to check if there are unsaved changes
return editor->toPlainText() != originalConfig;
}
void EditorDialog::loadInstalledGames() {
QStringList filePaths;
for (const auto& installLoc : Config::getGameInstallDirs()) {
QString installDir;
Common::FS::PathToQString(installDir, installLoc);
QDir parentFolder(installDir);
QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const auto& fileInfo : fileList) {
if (fileInfo.isDir() && !fileInfo.filePath().endsWith("-UPDATE")) {
gameComboBox->addItem(fileInfo.fileName()); // Add game name to combo box
}
}
}
}
QString previousGame = "default";
void EditorDialog::onGameSelectionChanged(const QString& game) {
saveFile(previousGame);
loadFile(gameComboBox->currentText()); // Reload file based on the selected game
previousGame = gameComboBox->currentText();
}

View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QComboBox>
#include <QDialog>
#include <QPlainTextEdit>
#include "string"
class EditorDialog : public QDialog {
Q_OBJECT // Necessary for using Qt's meta-object system (signals/slots)
public : explicit EditorDialog(QWidget* parent = nullptr); // Constructor
protected:
void closeEvent(QCloseEvent* event) override; // Override close event
void keyPressEvent(QKeyEvent* event) override;
private:
QPlainTextEdit* editor; // Editor widget for the config file
QFont editorFont; // To handle the text size
QString originalConfig; // Starting config string
std::string gameId;
QComboBox* gameComboBox; // Combo box for selecting game configurations
void loadFile(QString game); // Function to load the config file
void saveFile(QString game); // Function to save the config file
void loadInstalledGames(); // Helper to populate gameComboBox
bool hasUnsavedChanges(); // Checks for unsaved changes
private slots:
void onSaveClicked(); // Save button slot
void onCancelClicked(); // Slot for handling cancel button
void onHelpClicked(); // Slot for handling help button
void onGameSelectionChanged(const QString& game); // Slot for game selection changes
};

View File

@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "kbm_help_dialog.h"
#include <QApplication>
#include <QDialog>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QPropertyAnimation>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QWidget>
ExpandableSection::ExpandableSection(const QString& title, const QString& content,
QWidget* parent = nullptr)
: QWidget(parent) {
QVBoxLayout* layout = new QVBoxLayout(this);
// Button to toggle visibility of content
toggleButton = new QPushButton(title);
layout->addWidget(toggleButton);
// QTextBrowser for content (initially hidden)
contentBrowser = new QTextBrowser();
contentBrowser->setPlainText(content);
contentBrowser->setVisible(false);
// Remove scrollbars from QTextBrowser
contentBrowser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
contentBrowser->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
// Set size policy to allow vertical stretching only
contentBrowser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
// Calculate and set initial height based on content
updateContentHeight();
layout->addWidget(contentBrowser);
// Connect button click to toggle visibility
connect(toggleButton, &QPushButton::clicked, [this]() {
contentBrowser->setVisible(!contentBrowser->isVisible());
if (contentBrowser->isVisible()) {
updateContentHeight(); // Update height when expanding
}
emit expandedChanged(); // Notify for layout adjustments
});
// Connect to update height if content changes
connect(contentBrowser->document(), &QTextDocument::contentsChanged, this,
&ExpandableSection::updateContentHeight);
// Minimal layout settings for spacing
layout->setSpacing(2);
layout->setContentsMargins(0, 0, 0, 0);
}
HelpDialog::HelpDialog(QWidget* parent) : QDialog(parent) {
// Main layout for the help dialog
QVBoxLayout* mainLayout = new QVBoxLayout(this);
// Container widget for the scroll area
QWidget* containerWidget = new QWidget;
QVBoxLayout* containerLayout = new QVBoxLayout(containerWidget);
// Add expandable sections to container layout
auto* quickstartSection = new ExpandableSection("Quickstart", quickstart());
auto* faqSection = new ExpandableSection("FAQ", faq());
auto* syntaxSection = new ExpandableSection("Syntax", syntax());
auto* specialSection = new ExpandableSection("Special Bindings", special());
auto* bindingsSection = new ExpandableSection("Keybindings", bindings());
containerLayout->addWidget(quickstartSection);
containerLayout->addWidget(faqSection);
containerLayout->addWidget(syntaxSection);
containerLayout->addWidget(specialSection);
containerLayout->addWidget(bindingsSection);
containerLayout->addStretch(1);
// Scroll area wrapping the container
QScrollArea* scrollArea = new QScrollArea;
scrollArea->setWidgetResizable(true);
scrollArea->setWidget(containerWidget);
// Add the scroll area to the main dialog layout
mainLayout->addWidget(scrollArea);
setLayout(mainLayout);
// Minimum size for the dialog
setMinimumSize(500, 400);
// Re-adjust dialog layout when any section expands/collapses
connect(quickstartSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize);
connect(faqSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize);
connect(syntaxSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize);
connect(specialSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize);
connect(bindingsSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize);
}

View File

@ -0,0 +1,151 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QApplication>
#include <QDialog>
#include <QGroupBox>
#include <QLabel>
#include <QPropertyAnimation>
#include <QTextBrowser>
#include <QVBoxLayout>
#include <QWidget>
class ExpandableSection : public QWidget {
Q_OBJECT
public:
explicit ExpandableSection(const QString& title, const QString& content, QWidget* parent);
signals:
void expandedChanged(); // Signal to indicate layout size change
private:
QPushButton* toggleButton;
QTextBrowser* contentBrowser; // Changed from QLabel to QTextBrowser
QPropertyAnimation* animation;
int contentHeight;
void updateContentHeight() {
int contentHeight = contentBrowser->document()->size().height();
contentBrowser->setMinimumHeight(contentHeight + 5);
contentBrowser->setMaximumHeight(contentHeight + 5);
}
};
class HelpDialog : public QDialog {
Q_OBJECT
public:
explicit HelpDialog(QWidget* parent = nullptr);
private:
QString quickstart() {
return
R"(The keyboard remapping backend, GUI and documentation have been written by kalaposfos
In this section, you will find information about the project, its features and help on setting up your ideal setup.
To view the config file's syntax, check out the Syntax tab, for keybind names, visit Normal Keybinds and Special Bindings, and if you are here to view emulator-wide keybinds, you can find it in the FAQ section.
This project started out because I didn't like the original unchangeable keybinds, but rather than waiting for someone else to do it, I implemented this myself. From the default keybinds, you can clearly tell this was a project built for Bloodborne, but ovbiously you can make adjustments however you like.
)";
}
QString faq() {
return
R"(Q: What are the emulator-wide keybinds?
A: -F12: Triggers Rdoc capture
-F11: Toggles fullscreen
-F10: Toggles FPS counter
-Ctrl F10: Open the debug menu
-F9: Pauses emultor, if the debug menu is open
-F8: Reparses the config file while in-game
-F7: Toggles mouse capture and mouse input
Q: How do I change between mouse and controller joystick input, and why is it even required?
A: You can switch between them with F9, and it is required, because mouse input is done with polling, which means mouse movement is checked every frame, and if it didn't move, the code manually sets the emulator's virtual controller to 0 (back to the center), even if other input devices would update it.
Q: What happens if I accidentally make a typo in the config?
A: The code recognises the line as wrong, and skip it, so the rest of the file will get parsed, but that line in question will be treated like a comment line.
Q: I want to bind <key> to <input>, but your code doesn't support <key>!
A: Some keys are intentionally omitted, but if you read the bindings through, and you're sure it is not there and isn't one of the intentionally disabled ones, reach out to me by opening an issue on https://github.com/kalaposfos13/shadPS4 or on Discord (@kalaposfos).
)";
}
QString syntax() {
return
R"(This is the full list of currently supported mouse and keyboard inputs, and how to use them.
Emulator-reserved keys: F1 through F12, Insert, PrintScreen, Delete, Home, End, PgUp, PgDown
Syntax (aka how a line can look like):
#Comment line
<controller_button> = <key>, <key>, <key>;
<controller_button> = <key>, <key>;
<controller_button> = <key>;
Examples:
#Interact
cross = e;
#Heavy attack (in BB)
r2 = leftbutton, lshift;
#Move forward
axis_left_y_minus = w;
You can make a comment line by putting # as the first character.
Whitespace doesn't matter, <button>=<key>; is just as valid as <button> = <key>;
';' at the ends of lines is also optional.d
)";
}
QString bindings() {
return
R"(The following names should be interpreted without the '' around them, and for inputs that have left and right versions, only the left one is shown, but the right can be inferred from that.
Example: 'lshift', 'rshift'
Keyboard:
Alphabet: 'a', 'b', ..., 'z'
Numbers: '0', '1', ..., '9'
Keypad: 'kp0', kp1', ..., 'kp9', 'kpperiod', 'kpcomma',
'kpdivide', 'kpmultiply', 'kpdivide', 'kpplus', 'kpminus', 'kpenter'
Punctuation and misc:
'space', 'comma', 'period', 'question', 'semicolon', 'minus', 'plus', 'lparenthesis', 'lbracket', 'lbrace', 'backslash', 'dash',
'enter', 'tab', backspace', 'escape'
Arrow keys: 'up', 'down', 'left', 'right'
Modifier keys (these can be used both as normal and modifier keys):
'lctrl', 'lshift', 'lalt', 'lwin' = 'lmeta', 'none' (the same as not adding it at all)
Mouse:
'leftbutton', 'rightbutton', 'middlebutton', 'sidebuttonforward', 'sidebuttonback'
The following wheel inputs cannot be bound to axis input, only button:
'mousewheelup', 'mousewheeldown', 'mousewheelleft', 'mousewheelright'
Controller (this is not for controller remappings (yet), but rather the buttons and axes the above keys will be bound to):
The same left-right rule still applies here.
Buttons:
'triangle', 'circle', 'cross', 'square', 'l1', 'l2', 'l3',
'options', touchpad', 'up', 'down', 'left', 'right'
Axes:
'axis_left_x_plus', 'axis_left_x_minus', 'axis_left_y_plus', 'axis_left_y_minus'
'axis_right_x_plus', ..., 'axis_right_y_minus'
)";
}
QString special() {
return
R"(There are some extra bindings you can put into the config file, that don't correspond to a controller input, but rather something else.
You can find these here, with detailed comments, examples and suggestions for most of them.
'leftjoystick_halfmode' and 'rightjoystick_halfmode' = <key>;
These are a pair of input modifiers, that change the way keyboard button bound axes work. By default, those push the joystick to the max in their respective direction, but if their respective joystick_halfmode modifier value is true, they only push it (wait for it)... halfway. With this, you can change from run to walk in games like Bloodborne.
'mouse_to_joystick' = 'none', 'left' or 'right';
This binds the mouse movement to either joystick. If it recieves a value that is not 'left' or 'right', it defaults to 'none'.
'mouse_movement_params' = float, float, float;
(If you don't know what a float is, it is a data type that stores non-whole numbers.)
This controls mouse-to-joystick input smoothness. Let's break each parameter down:
1st: mouse_deadzone_offset: this value should have a value between 0 and 1 (It gets clamped to that range anyway), with 0 being no offset and 1 being pushing the joystick to the max in the direction the mouse moved.
This controls the minimum distance the joystick gets moved, when moving the mouse. If set to 0, it will emulate raw mouse input, which doesn't work very well due to deadzones preventing input if the movement is not large enough.
It defaults to 0.5.
2nd: mouse_speed: It's just a multiplier to the mouse input speed, which varies between people, so rather than adjusting the mouse speed globally, they can just do it here.
It defults to 1.0, which is just about fine for my mouse.
If you input a negative number, the axis directions get reversed.
3rd: mouse_speed_offset: This also should be in the 0 to 1 range, with 0 being no offset and 1 being offsetting to the max possible value.
This is best explained through an example: Let's set mouse_deadzone to 0.5, and this to 0: This means that if we move the mousevery slowly, it still inputs a half-strength joystick input, and if we increase the speed, it would stay that way until we move faster than half the max speed. If we instead set this to 0.25, we now only need to move the mouse faster than the 0.5-0.25=0.25=quarter of the max speed, to get an increase in joystick speed. If we set it to 0.5, then even moving the mouse at 1 pixel per frame will result in a faster-than-minimum speed.
'key_toggle' = <key>, <key_to_toggle>;
This assigns a key to another key, and if pressed, toggles that key's virtual value. If it's on, then it doesn't matter if the key is pressed or not, the input handler will treat it as if it's pressed.
)";
}
};

View File

@ -4,6 +4,7 @@
#include <QDockWidget>
#include <QKeyEvent>
#include <QProgressDialog>
#include <QPlainTextEdit>
#include "about_dialog.h"
#include "cheats_patches.h"
@ -21,6 +22,9 @@
#include "install_dir_select.h"
#include "main_window.h"
#include "settings_dialog.h"
#include "kbm_config_dialog.h"
#include "video_core/renderer_vulkan/vk_instance.h"
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
@ -260,6 +264,12 @@ void MainWindow::CreateConnects() {
settingsDialog->exec();
});
// this is the editor for kbm keybinds
connect(ui->controllerButton, &QPushButton::clicked, this, [this]() {
EditorDialog *editorWindow = new EditorDialog(this);
editorWindow->exec(); // Show the editor window modally
});
#ifdef ENABLE_UPDATER
connect(ui->updaterAct, &QAction::triggered, this, [this]() {
auto checkUpdate = new CheckUpdate(true);