[App/Qt] Implement Settings subsystem

This subsystem reads an embedded XML file containing abstract settings data, converts to settings objects which are then read by the Qt UI to generate UI Widgets
This commit is contained in:
Satori 2021-08-24 16:25:18 +01:00
parent b76c398abe
commit b2aa9c83b0
31 changed files with 1498 additions and 480 deletions

3
.gitmodules vendored
View File

@ -67,3 +67,6 @@
[submodule "third_party/premake-qt"]
path = third_party/premake-qt
url = https://github.com/dcourtois/premake-qt.git
[submodule "third_party/pugixml"]
path = third_party/pugixml
url = https://github.com/zeux/pugixml

161
assets/settings.xml Normal file
View File

@ -0,0 +1,161 @@
<?xml version="1.0" encoding="UTF-8"?>
<Sets>
<Set name="General" icon="">
<Group name="General Settings">
<MultiChoiceSetting>
<title>Game Language</title>
<description>Choose the language for launched games (does not apply to the UI)</description>
<type>int</type>
<cvar>user_language</cvar>
<options>
<option>
<title>English</title>
<value>1</value>
</option>
<option>
<title>Japanese</title>
<value>2</value>
</option>
<option>
<title>German</title>
<value>3</value>
</option>
<option>
<title>French</title>
<value>4</value>
</option>
<option>
<title>Spanish</title>
<value>5</value>
</option>
<option>
<title>Italian</title>
<value>6</value>
</option>
<option>
<title>Korean</title>
<value>7</value>
</option>
<option>
<title>Chinese (Traditional)</title>
<value>8</value>
</option>
<option>
<title>Portuguese</title>
<value>9</value>
</option>
<option>
<title>Polish</title>
<value>11</value>
</option>
<option>
<title>Russian</title>
<value>12</value>
</option>
<option>
<title>Slovenian</title>
<value>13</value>
</option>
<option>
<title>Turkish</title>
<value>14</value>
</option>
<option>
<title>Norwegian</title>
<value>15</value>
</option>
<option>
<title>Dutch</title>
<value>16</value>
</option>
<option>
<title>Chinese (Simplified)</title>
<value>17</value>
</option>
</options>
</MultiChoiceSetting>
<BooleanSetting>
<title>Discord Rich Presence</title>
<description>Enable Discord Rich Presence in Xenia</description>
<cvar>discord</cvar>
</BooleanSetting>
<BooleanSetting>
<title>Show game Icon in Taskbar</title>
<description>Show the currently loaded game's icon in the Taskbar</description>
<cvar>taskbar_show_game_icon</cvar>
</BooleanSetting>
<BooleanSetting>
<title>Launch Games in New Window</title>
<description>Launch games in their own separate window</description>
<cvar>launch_external_window</cvar>
</BooleanSetting>
<BooleanSetting>
<title>Launch Games Fullscreen</title>
<description>Launched games start in fullscreen mode</description>
<cvar>fullscreen</cvar>
</BooleanSetting>
</Group>
<Group name="Logging">
<BooleanSetting>
<title>Enable Logging</title>
<description>Disable or enable logging</description>
<cvar>logging</cvar>
</BooleanSetting>
<BooleanSetting>
<title>Show Log Window</title>
<description>Enable to show a new log window</description>
<cvar>log_window</cvar>
</BooleanSetting>
<MultiChoiceSetting>
<title>Log Level</title>
<description>Choose the level of logging to record to the log file</description>
<cvar>log_level</cvar>
<type>int</type>
<options>
<option>
<title>Error</title>
<value>0</value>
</option>
<option>
<title>Warning</title>
<value>1</value>
</option>
<option>
<title>Info</title>
<value>2</value>
</option>
<option>
<title>Debug</title>
<value>3</value>
</option>
</options>
</MultiChoiceSetting>
<PathInputSetting>
<title>Log Location</title>
<description>Enter the directory to write log files to</description>
<cvar>log_file</cvar>
</PathInputSetting>
</Group>
</Set>
<Set name="CPU" icon="">
</Set>
<Set name="Graphics" icon="">
</Set>
<Set name="Audio" icon="">
</Set>
<Set name="Library" icon="">
</Set>
<Set name="Interface" icon="">
</Set>
<Set name="Controls" icon="">
</Set>
<Set name="Advanced" icon="">
</Set>
</Sets>

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
<xs:element name="Sets">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="Set"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Set">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="Group"/>
</xs:sequence>
<xs:attribute name="icon" use="required"/>
<xs:attribute name="name" use="required" type="xs:NCName"/>
</xs:complexType>
</xs:element>
<xs:element name="Group">
<xs:complexType>
<xs:sequence>
<xs:choice maxOccurs="unbounded">
<xs:element ref="BooleanSetting"/>
<xs:element ref="MultiChoiceSetting"/>
</xs:choice>
<xs:element minOccurs="0" ref="PathInputSetting"/>
</xs:sequence>
<xs:attribute name="name" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="BooleanSetting">
<xs:complexType>
<xs:sequence>
<xs:element ref="title"/>
<xs:element ref="description"/>
<xs:element ref="cvar"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="MultiChoiceSetting">
<xs:complexType>
<xs:sequence>
<xs:element ref="title"/>
<xs:element ref="description"/>
<xs:element ref="type"/>
<xs:element minOccurs="0" ref="cvar"/>
<xs:element ref="options"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="type" type="xs:NCName"/>
<xs:element name="options">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="option"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="option">
<xs:complexType>
<xs:sequence>
<xs:element ref="title"/>
<xs:element ref="value"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="value" type="xs:integer"/>
<xs:element name="TextInputSetting">
<xs:complexType>
<xs:sequence>
<xs:element ref="title"/>
<xs:element ref="description"/>
<xs:element ref="cvar"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="PathInputSetting">
<xs:complexType>
<xs:sequence>
<xs:element ref="title"/>
<xs:element ref="description"/>
<xs:element ref="cvar"/>
<xs:element name="require_valid_path" type="xs:boolean" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="title" type="xs:string"/>
<xs:element name="description" type="xs:string"/>
<xs:element name="cvar" type="xs:NCName"/>
</xs:schema>

View File

@ -229,10 +229,13 @@ solution("xenia")
include("third_party/spirv-tools.lua")
include("third_party/volk.lua")
include("third_party/xxhash.lua")
include("third_party/pugixml.lua")
include("src/xenia")
include("src/xenia/app")
include("src/xenia/app/discord")
include("src/xenia/app/library")
include("src/xenia/app/settings")
include("src/xenia/apu")
include("src/xenia/apu/nop")
include("src/xenia/apu/sdl")

View File

@ -0,0 +1,11 @@
project_root = "../../../.."
include(project_root.."/tools/build")
group("src")
project("xenia-app-library")
uuid("bb78039c-0150-4be5-a1d8-e7e00b574b63")
kind("StaticLib")
language("C++")
defines({
})
local_platform_files()

View File

@ -23,6 +23,8 @@ project("xenia-app")
"spirv-tools",
"volk",
"xenia-app-discord",
"xenia-app-library",
"xenia-app-settings",
"xenia-apu",
"xenia-apu-nop",
"xenia-apu-sdl",
@ -130,7 +132,7 @@ project("xenia-app")
"XBYAK_NO_OP_NAMES",
"XBYAK_ENABLE_OMITTED_OPERAND",
})
recursive_platform_files()
local_platform_files()
files({
"xenia_main.cc",
"../base/main_"..platform_suffix..".cc",

View File

@ -0,0 +1,14 @@
project_root = "../../../.."
include(project_root.."/tools/build")
group("src")
project("xenia-app-settings")
uuid("d2031dc9-e643-4d46-b252-65c825895a3d")
kind("StaticLib")
language("C++")
defines({
})
links({
"pugixml",
})
local_platform_files()

View File

@ -0,0 +1,87 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "settings.h"
#include "settings_loader.h"
#include "xenia/base/cvar.h"
namespace xe {
namespace cvars {
void LoadSettings(xe::app::settings::SettingsLoader& loader) {
#define ADD_BOOLEAN_SETTING(title, description, cvar_name, group, set) \
extern xe::cvar::ConfigVar<bool>* cv_##cvar_name; \
loader.AddBooleanInputSetting(title, description, cv_##cvar_name, group, set)
#define ADD_TEXT_INPUT_SETTING(title, description, cvar_name, group, set) \
extern xe::cvar::ConfigVar<std::string>* cv_##cvar_name; \
loader.AddTextInputSetting(title, description, cv_##cvar_name, group, set)
#define ADD_PATH_INPUT_SETTING(title, description, cvar_name, group, set) \
extern xe::cvar::ConfigVar<std::filesystem::path>* cv_##cvar_name; \
loader.AddFilePathInputSetting(title, description, cv_##cvar_name, group, set)
loader.AddBooleanInputSetting("Discord",
"Enable support for Discord rich presence",
"discord", "General Settings", "General");
// #include "settings.inc"
}
} // namespace cvars
namespace app {
namespace settings {
void ActionSettingsItem::Trigger() { on_triggered(); }
void SettingsGroup::AddItem(std::unique_ptr<ISettingsItem>&& item) {
items.emplace_back(std::move(item));
}
SettingsGroup& SettingsSet::FindOrCreateSettingsGroup(
const std::string& title) {
const auto& it = std::find_if(
groups.begin(), groups.end(),
[&title](const SettingsGroup& group) { return group.title == title; });
if (it == groups.end()) {
groups.push_back(SettingsGroup{title});
return groups.back();
}
return *it;
}
SettingsSet& Settings::FindOrCreateSettingsSet(const std::string& title) {
const auto& it = std::find_if(
settings_.begin(), settings_.end(),
[&title](const SettingsSet& set) { return set.title == title; });
if (it == settings_.end()) {
settings_.push_back(SettingsSet{title});
return settings_.back();
}
return *it;
}
Settings& Settings::Instance() {
static Settings settings;
return settings;
}
void Settings::LoadSettingsItems() {
SettingsLoader loader(*this);
// xe::cvars::LoadSettings(loader);
loader.LoadSettingsFromEmbeddedXml();
}
} // namespace settings
} // namespace app
} // namespace xe

View File

@ -0,0 +1,291 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APP_SETTINGS_SETTINGS_H_
#define XENIA_APP_SETTINGS_SETTINGS_H_
#include <filesystem>
#include <string>
#include <variant>
#include <vector>
#include "xenia/base/cvar.h"
#include "xenia/base/delegate.h"
#include "xenia/base/logging.h"
namespace xe {
namespace app {
namespace settings {
using cvar::ConfigVar;
using cvar::IConfigVar;
enum class SettingsType {
Boolean,
TextInput,
PathInput,
NumberInput,
MultiChoice,
Range,
Action,
Custom
};
class ISettingsItem {
public:
virtual ~ISettingsItem() = default;
ISettingsItem(SettingsType type, const std::string& title,
const std::string& description)
: type_(type), title_(title), description_(description) {}
const SettingsType type() const { return type_; }
const std::string& title() const { return title_; }
const std::string& description() const { return description_; }
private:
SettingsType type_;
std::string title_;
std::string description_;
};
template <typename T, SettingsType Type>
class BasicSettingsItem : public ISettingsItem {
public:
BasicSettingsItem(const std::string& title, const std::string& description,
ConfigVar<T>* cvar = nullptr)
: ISettingsItem(Type, title, description), cvar_(cvar) {}
ConfigVar<T>* cvar() const { return cvar_; }
virtual bool UpdateValue(T value) {
if (auto cvar = cvar_->as<T>(); cvar != nullptr) {
cvar->set_config_value(value);
Config::Instance().SaveConfig();
return true;
}
return false;
}
private:
ConfigVar<T>* cvar_;
};
using BooleanSettingsItem = BasicSettingsItem<bool, SettingsType::Boolean>;
using TextInputSettingsItem =
BasicSettingsItem<std::string, SettingsType::TextInput>;
using FilePathInputSettingsItem =
BasicSettingsItem<std::filesystem::path, SettingsType::PathInput>;
enum class ValueType {
Int8,
Int16,
Int32,
Int64,
UInt8,
UInt16,
UInt32,
UInt64,
Double
};
using NumberValue = std::variant<int8_t, int16_t, int32_t, int64_t, uint8_t,
uint16_t, uint32_t, uint64_t, double>;
inline int number_value_to_int(NumberValue v) {
struct Visitor {
int operator()(int8_t value) { return value; }
int operator()(int16_t value) { return value; }
int operator()(int32_t value) { return value; }
int operator()(int64_t value) { return static_cast<int>(value); }
int operator()(uint8_t value) { return value; }
int operator()(uint16_t value) { return value; }
int operator()(uint32_t value) { return value; }
int operator()(uint64_t value) { return static_cast<int>(value); }
int operator()(double value) { return static_cast<int>(value); }
};
return std::visit(Visitor{}, v);
}
template <SettingsType settings_type>
class NumberSettingsItem : public ISettingsItem {
public:
struct ValueVisitor {
ValueVisitor(NumberSettingsItem* item) : item(item) {}
bool operator()(int8_t value) { return update(ValueType::Int8, value); }
bool operator()(int16_t value) { return update(ValueType::Int16, value); }
bool operator()(int32_t value) { return update(ValueType::Int32, value); }
bool operator()(int64_t value) { return update(ValueType::Int64, value); }
bool operator()(uint8_t value) { return update(ValueType::UInt8, value); }
bool operator()(uint16_t value) { return update(ValueType::UInt16, value); }
bool operator()(uint32_t value) { return update(ValueType::UInt32, value); }
bool operator()(uint64_t value) { return update(ValueType::UInt64, value); }
bool operator()(double value) { return update(ValueType::Double, value); }
template <typename T>
bool update(ValueType type, T value) {
if (type == item->value_type()) {
if (auto cvar = item->cvar()->as<T>(); cvar != nullptr) {
cvar->set_config_value(value);
Config::Instance().SaveConfig();
return true;
}
}
return false;
}
NumberSettingsItem* item;
};
NumberSettingsItem(ValueType value_type, const std::string& title,
const std::string& description, IConfigVar* cvar = nullptr)
: ISettingsItem(settings_type, title, description),
value_type_(value_type),
cvar_(cvar) {}
ValueType value_type() const { return value_type_; }
IConfigVar* cvar() const { return cvar_; }
virtual bool UpdateValue(NumberValue value) {
auto res = std::visit(ValueVisitor(this), value);
if (!res) {
XELOGE("Could not update value for {0}. Incorrect value type provided",
cvar_->name());
}
return res;
}
private:
ValueType value_type_;
IConfigVar* cvar_;
};
class RangeInputSettingsItem : public NumberSettingsItem<SettingsType::Range> {
public:
RangeInputSettingsItem(ValueType value_type, std::string title,
NumberValue min, NumberValue max,
std::string description = "",
IConfigVar* cvar = nullptr)
: NumberSettingsItem(value_type, title, description, cvar),
min_(min),
max_(max) {}
bool UpdateValue(NumberValue value) override {
if (value < min_ || value > max_) {
return false;
}
return NumberSettingsItem::UpdateValue(value);
}
NumberValue min() const { return min_; }
NumberValue max() const { return max_; }
private:
NumberValue min_;
NumberValue max_;
};
using NumberInputSettingsItem = NumberSettingsItem<SettingsType::NumberInput>;
class IMultiChoiceSettingsItem : public ISettingsItem {
public:
IMultiChoiceSettingsItem(std::string title, std::string description)
: ISettingsItem(SettingsType::MultiChoice, title, description) {}
virtual bool UpdateIndex(int index) = 0;
virtual const std::vector<std::string>& option_names() const = 0;
};
template <typename T>
class MultiChoiceSettingsItem : public IMultiChoiceSettingsItem {
public:
struct Option {
std::string title;
T value;
};
MultiChoiceSettingsItem(std::string title, std::string description,
std::vector<Option> options,
ConfigVar<T>* cvar = nullptr)
: IMultiChoiceSettingsItem(title, description),
cvar_(cvar),
options_(options) {}
MultiChoiceSettingsItem(std::string title, std::string description,
std::initializer_list<Option> args,
ConfigVar<T>* cvar = nullptr)
: IMultiChoiceSettingsItem(title, description),
cvar_(cvar),
options_(args) {}
bool UpdateIndex(int index) override {
if (cvar_) {
if (index < options_.size()) {
cvar_->set_config_value(options_.at(index).value);
Config::Instance().SaveConfig();
return true;
} else {
XELOGE("Out of range index when updating multi choice settings item");
}
}
return false;
}
private:
ConfigVar<T>* cvar_;
std::vector<Option> options_;
};
class ActionSettingsItem : public ISettingsItem {
public:
ActionSettingsItem(std::string title, std::string description = "")
: ISettingsItem(SettingsType::Action, title, description) {}
Delegate<> on_triggered;
virtual void Trigger();
};
struct SettingsGroup {
std::string title;
std::vector<std::unique_ptr<ISettingsItem>> items;
void AddItem(std::unique_ptr<ISettingsItem>&& item);
};
struct SettingsSet {
std::string title;
std::vector<SettingsGroup> groups;
SettingsGroup& FindOrCreateSettingsGroup(const std::string& title);
};
class Settings {
public:
static Settings& Instance();
void LoadSettingsItems();
const std::vector<SettingsSet>& settings() const { return settings_; }
SettingsSet& FindOrCreateSettingsSet(const std::string& title);
private:
std::vector<SettingsSet> settings_;
};
} // namespace settings
} // namespace app
} // namespace xe
#endif

View File

@ -0,0 +1,315 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "settings_loader.h"
#include <array>
#include "build/settings.h"
#include "third_party/pugixml/src/pugixml.hpp"
#include "xenia/base/logging.h"
#include "xenia/config.h"
namespace xe {
namespace app {
namespace settings {
// All possible settings node types
static std::string_view valid_node_types[] = {
"BooleanSetting", "TextInputSetting", "PathInputSetting",
"NumberInputSetting", "RangeInputSetting", "MultiChoiceSetting",
"ActionSetting", "CustomSetting"};
static std::string_view valid_multi_choice_types[] = {
"int", "int32", "int64", "uint", "uint32", "uint64", "double", "string"};
template <>
SettingsLoader::Option<int32_t> SettingsLoader::LoadMultiChoiceOption<int32_t>(
const pugi::xml_node& node) {
std::string title = node.child("title").text().as_string();
int32_t value = node.child("value").text().as_int();
return Option<int32_t>{title, value};
}
template <>
SettingsLoader::Option<int64_t> SettingsLoader::LoadMultiChoiceOption<int64_t>(
const pugi::xml_node& node) {
std::string title = node.child("title").text().as_string();
int64_t value = node.child("value").text().as_llong();
return Option<int64_t>{title, value};
}
template <>
SettingsLoader::Option<uint32_t>
SettingsLoader::LoadMultiChoiceOption<uint32_t>(const pugi::xml_node& node) {
std::string title = node.child("title").text().as_string();
uint32_t value = node.child("value").text().as_uint();
return Option<uint32_t>{title, value};
}
template <>
SettingsLoader::Option<uint64_t>
SettingsLoader::LoadMultiChoiceOption<uint64_t>(const pugi::xml_node& node) {
std::string title = node.child("title").text().as_string();
uint64_t value = node.child("value").text().as_ullong();
return Option<uint64_t>{title, value};
}
template <>
SettingsLoader::Option<double> SettingsLoader::LoadMultiChoiceOption<double>(
const pugi::xml_node& node) {
std::string title = node.child("title").text().as_string();
double value = node.child("value").text().as_double();
return Option<double>{title, value};
}
template <>
SettingsLoader::Option<std::string>
SettingsLoader::LoadMultiChoiceOption<std::string>(const pugi::xml_node& node) {
std::string title = node.child("title").text().as_string();
std::string value = node.child("value").text().as_string();
return Option<std::string>{title, value};
}
void SettingsLoader::AddSetting(std::unique_ptr<ISettingsItem>&& item,
const std::string& group,
const std::string& set) {
auto& settings_set = settings_.FindOrCreateSettingsSet(set);
auto& settings_group = settings_set.FindOrCreateSettingsGroup(group);
settings_group.AddItem(std::move(item));
}
bool SettingsLoader::LoadSettingsFromEmbeddedXml() {
constexpr size_t size = sizeof(xe::embedded::settings_data);
std::array<uint8_t, size> settings_buf;
// copy embedded data to buffer
std::copy(std::begin(xe::embedded::settings_data),
std::end(xe::embedded::settings_data), std::begin(settings_buf));
pugi::xml_document doc;
doc.load_buffer_inplace(settings_buf.data(), settings_buf.size());
const auto& sets_node = doc.first_child();
if (std::string_view(sets_node.name()) != "Sets") {
XELOGE("Settings key 'Sets' not found");
return false;
}
for (const auto& set_node : sets_node.children()) {
XELOGD("Loading settings set with name {}", set_node.name());
for (const auto& group_node : set_node.children()) {
// load group and set names
std::string group = group_node.attribute("name").value();
std::string set = set_node.attribute("name").value();
for (const auto& settings_item_node : group_node.children()) {
auto settings_item = LoadSettingsItemFromXmlNode(settings_item_node);
if (settings_item) {
AddSetting(std::move(settings_item), group, set);
}
}
}
}
return true;
}
std::unique_ptr<ISettingsItem> SettingsLoader::LoadSettingsItemFromXmlNode(
const pugi::xml_node& node) {
std::string_view node_name = node.name();
auto node_found = std::find(std::begin(valid_node_types),
std::end(valid_node_types), node_name);
// if node is a valid settings node
if (node_found != std::end(valid_node_types)) {
std::string_view node_type = *node_found;
// TODO: handle exceptions for child not found
std::string title = node.child_value("title");
std::string description = node.child_value("description");
// if cvar specified, load it
IConfigVar* cvar = nullptr;
if (node.child("cvar")) {
std::string cvar_name = node.child_value("cvar");
cvar = Config::Instance().FindConfigVarByName(cvar_name);
if (!cvar) {
XELOGE("Failed to find cvar with name '{}'", cvar_name);
return nullptr;
}
}
if (node_type == "BooleanSetting") {
return std::make_unique<BooleanSettingsItem>(
title, description, dynamic_cast<ConfigVar<bool>*>(cvar));
} else if (node_type == "TextInputSetting") {
return std::make_unique<TextInputSettingsItem>(
title, description, dynamic_cast<ConfigVar<std::string>*>(cvar));
} else if (node_type == "PathInputSetting") {
return std::make_unique<FilePathInputSettingsItem>(
title, description,
dynamic_cast<ConfigVar<std::filesystem::path>*>(cvar));
} else if (node_type == "MultiChoiceSetting") {
// TODO: load type, load options and add options to array of type `type`
std::string_view type_name = node.child_value("type");
if (type_name.empty()) {
XELOGE("'type' node not found for MultiChoiceSetting");
return nullptr;
}
auto node_found =
std::find(std::begin(valid_multi_choice_types),
std::end(valid_multi_choice_types), type_name);
if (node_found == std::end(valid_multi_choice_types)) {
XELOGE("Invalid type '{}' provided for MultiChoiceSetting type",
type_name);
return nullptr;
}
std::string_view multi_choice_type = *node_found;
if (multi_choice_type == "int" || multi_choice_type == "int32") {
std::vector<Option<int32_t>> values;
for (const auto& option_node : node.child("options").children()) {
values.push_back(LoadMultiChoiceOption<int32_t>(option_node));
}
}
if (multi_choice_type == "int64") {
std::vector<Option<int64_t>> values;
for (const auto& option_node : node.child("options").children()) {
values.push_back(LoadMultiChoiceOption<int64_t>(option_node));
}
}
}
} else {
XELOGE("Unknown settings node type {}", node.name());
}
return nullptr;
}
void SettingsLoader::AddBooleanInputSetting(std::string title,
std::string description,
cvar::ConfigVar<bool>* cvar,
std::string group,
std::string set) {
if (cvar) {
auto setting_item =
std::make_unique<BooleanSettingsItem>(title, description, cvar);
AddSetting(std::move(setting_item), group, set);
} else {
XELOGE("Could not find cvar for setting with title {}", title);
}
}
void SettingsLoader::AddBooleanInputSetting(std::string title,
std::string description,
std::string cvar_name,
std::string group,
std::string set) {
auto cvar = Config::Instance().FindConfigVarByName(cvar_name);
return AddBooleanInputSetting(
title, description, dynamic_cast<ConfigVar<bool>*>(cvar), group, set);
}
void SettingsLoader::AddTextInputSetting(std::string title,
std::string description,
cvar::ConfigVar<std::string>* cvar,
std::string group, std::string set) {
if (cvar) {
auto setting_item =
std::make_unique<TextInputSettingsItem>(title, description, cvar);
AddSetting(std::move(setting_item), group, set);
} else {
XELOGE("Could not find cvar for setting with title {}", title);
}
}
void SettingsLoader::AddTextInputSetting(std::string title,
std::string description,
std::string cvar_name,
std::string group, std::string set) {
auto cvar = Config::Instance().FindConfigVarByName(cvar_name);
return AddTextInputSetting(title, description,
dynamic_cast<ConfigVar<std::string>*>(cvar), group,
set);
}
void SettingsLoader::AddFilePathInputSetting(
std::string title, std::string description,
cvar::ConfigVar<std::filesystem::path>* cvar, std::string group,
std::string set) {
if (cvar) {
auto setting_item =
std::make_unique<FilePathInputSettingsItem>(title, description, cvar);
AddSetting(std::move(setting_item), group, set);
} else {
XELOGE("Could not find cvar for setting with title {}", title);
}
}
void SettingsLoader::AddFilePathInputSetting(std::string title,
std::string description,
std::string cvar_name,
std::string group,
std::string set) {
auto cvar = Config::Instance().FindConfigVarByName(cvar_name);
return AddFilePathInputSetting(
title, description, dynamic_cast<ConfigVar<std::filesystem::path>*>(cvar),
group, set);
}
void SettingsLoader::AddNumberInputInputSetting(
std::string title, std::string description, cvar::IConfigVar* cvar,
std::string group, std::string set, ValueType value_type) {
if (cvar) {
auto setting_item = std::make_unique<NumberInputSettingsItem>(
value_type, title, description, cvar);
AddSetting(std::move(setting_item), group, set);
} else {
XELOGE("Could not find cvar for setting with title {}", title);
}
}
void SettingsLoader::AddNumberInputInputSetting(
std::string title, std::string description, std::string cvar_name,
std::string group, std::string set, ValueType value_type) {
auto cvar = Config::Instance().FindConfigVarByName(cvar_name);
return AddNumberInputInputSetting(title, description, cvar, group, set,
value_type);
}
void SettingsLoader::AddRangeInputSetting(std::string title,
std::string description,
cvar::IConfigVar* cvar,
std::string group, std::string set,
ValueType value_type, NumberValue min,
NumberValue max) {
if (cvar) {
auto setting_item = std::make_unique<RangeInputSettingsItem>(
value_type, title, min, max, description, cvar);
AddSetting(std::move(setting_item), group, set);
} else {
XELOGE("Could not find cvar for setting with title {}", title);
}
}
void SettingsLoader::AddRangeInputSetting(std::string title,
std::string description,
std::string cvar_name,
std::string group, std::string set,
ValueType value_type, NumberValue min,
NumberValue max) {
auto cvar = Config::Instance().FindConfigVarByName(cvar_name);
return AddRangeInputSetting(title, description, cvar, group, set, value_type,
min, max);
}
} // namespace settings
} // namespace app
} // namespace xe

View File

@ -0,0 +1,125 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_APP_SETTINGS_SETTINGS_LOADER_H_
#define XENIA_APP_SETTINGS_SETTINGS_LOADER_H_
#include <memory>
#include <string>
#include "settings.h"
#include "third_party/pugixml/src/pugixml.hpp"
#include "xenia/base/cvar.h"
namespace xe {
namespace app {
namespace settings {
class SettingsLoader {
public:
SettingsLoader(Settings& settings) : settings_(settings) {}
void AddSetting(std::unique_ptr<ISettingsItem>&& item,
const std::string& group, const std::string& set);
bool LoadSettingsFromEmbeddedXml();
void AddBooleanInputSetting(std::string title, std::string description,
std::string cvar_name, std::string group,
std::string set);
void AddBooleanInputSetting(std::string title, std::string description,
cvar::ConfigVar<bool>* cvar, std::string group,
std::string set);
void AddTextInputSetting(std::string title, std::string description,
std::string cvar_name, std::string group,
std::string set);
void AddTextInputSetting(std::string title, std::string description,
cvar::ConfigVar<std::string>* cvar,
std::string group, std::string set);
void AddFilePathInputSetting(std::string title, std::string description,
std::string cvar_name, std::string group,
std::string set);
void AddFilePathInputSetting(std::string title, std::string description,
cvar::ConfigVar<std::filesystem::path>* cvar,
std::string group, std::string set);
void AddNumberInputInputSetting(std::string title, std::string description,
std::string cvar_name, std::string group,
std::string set, ValueType value_type);
void AddNumberInputInputSetting(std::string title, std::string description,
cvar::IConfigVar* cvar, std::string group,
std::string set, ValueType value_type);
void AddRangeInputSetting(std::string title, std::string description,
std::string cvar_name, std::string group,
std::string set, ValueType value_type,
NumberValue min, NumberValue max);
void AddRangeInputSetting(std::string title, std::string description,
cvar::IConfigVar* cvar, std::string group,
std::string set, ValueType value_type,
NumberValue min, NumberValue max);
template <typename T>
void AddMultiChoiceInputSetting(
std::string title, std::string description, std::string cvar_name,
std::string group, std::string set,
std::initializer_list<typename MultiChoiceSettingsItem<T>::Option>
options);
template <typename T>
void AddMultiChoiceInputSetting(
std::string title, std::string description, cvar::ConfigVar<T>* cvar,
std::string group, std::string set,
std::initializer_list<typename MultiChoiceSettingsItem<T>::Option>
options);
private:
template <typename T>
using Option = typename MultiChoiceSettingsItem<T>::Option;
std::unique_ptr<ISettingsItem> LoadSettingsItemFromXmlNode(
const pugi::xml_node& node);
template <typename T>
Option<T> LoadMultiChoiceOption(const pugi::xml_node& option_node);
Settings& settings_;
};
template <typename T>
void SettingsLoader::AddMultiChoiceInputSetting(
std::string title, std::string description, std::string cvar_name,
std::string group, std::string set,
std::initializer_list<typename MultiChoiceSettingsItem<T>::Option>
options) {
auto cvar = Config::Instance().FindConfigVarByName(cvar_name);
return AddMultiChoiceInputSetting(title, description, cvar, group, set,
options);
}
template <typename T>
void SettingsLoader::AddMultiChoiceInputSetting(
std::string title, std::string description, cvar::ConfigVar<T>* cvar,
std::string group, std::string set,
std::initializer_list<typename MultiChoiceSettingsItem<T>::Option>
options) {
if (cvar) {
auto setting_item = std::make_unique<MultiChoiceSettingsItem<T>>(
title, description, options, cvar);
AddSetting(std::move(setting_item), group, set);
} else {
XELOGE("Could not find cvar named {}", cvar_name);
}
}
} // namespace settings
} // namespace app
} // namespace xe
#endif

View File

@ -28,6 +28,7 @@
#include "xenia/base/logging.h"
#include "xenia/config.h"
#include "xenia/ui/qt/loop_qt.h"
#include "xenia/app/settings/settings.h"
#if XE_PLATFORM_WIN32 && QT_STATIC
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
@ -106,6 +107,17 @@ int xenia_main(const std::vector<std::string>& args) {
Config::Instance().SetupConfig(storage_root);
auto& settings = settings::Settings::Instance();
settings.LoadSettingsItems(); // required to be called in main() as requires cvars to be loaded already
for (const auto& set : settings.settings()) {
XELOGI("Found settings set {}", set.title);
for (const auto& group : set.groups) {
XELOGI("Found settings group {0} in set {1}", group.title, set.title);
}
}
if (cvars::discord) {
discord::DiscordPresence::Initialize();
discord::DiscordPresence::NotPlaying();

View File

@ -218,17 +218,18 @@ CommandVar<T>* register_commandvar(std::string_view name, T* default_value,
requires_restart, type) \
namespace xe::cvars { \
type name = default_value; \
static auto cv_##name = xe::cvar::register_configvar<type>( \
#name, &cvars::name, description, category, is_transient, \
requires_restart); \
extern "C" xe::cvar::ConfigVar<type>* cv_##name = \
xe::cvar::register_configvar<type>(#name, &cvars::name, description, \
category, is_transient, \
requires_restart); \
} // namespace cvars
// CmdVars can only be strings for now, we don't need any others
#define CmdVar(name, default_value, description) \
namespace xe::cvars { \
std::string name = default_value; \
static auto cmdvar_##name = xe::cvar::register_commandvar<std::string>( \
#name, &cvars::name, description); \
#define CmdVar(name, default_value, description) \
namespace xe::cvars { \
std::string name = default_value; \
auto cmdvar_##name = xe::cvar::register_commandvar<std::string>( \
#name, &cvars::name, description); \
} // namespace cvars
#define DECLARE_bool(name) DECLARE_CVar(name, bool)
@ -243,9 +244,10 @@ CommandVar<T>* register_commandvar(std::string_view name, T* default_value,
#define DECLARE_path(name) DECLARE_CVar(name, std::filesystem::path)
#define DECLARE_CVar(name, type) \
namespace xe::cvars { \
extern type name; \
#define DECLARE_CVar(name, type) \
namespace xe::cvars { \
extern type name; \
extern "C" xe::cvar::ConfigVar<type>* cv_##name; \
}
// load template implementations

View File

@ -83,7 +83,7 @@ void CommandVar<T>::UpdateValue() {
template <class T>
void ConfigVar<T>::UpdateValue() {
if (this->command_line_value()) {
return this->set_config_value(*this->command_line_value());
return this->set_current_value(*this->command_line_value());
}
if (game_config_value_) return this->set_current_value(*game_config_value_);
if (config_value_) return this->set_current_value(*config_value_);

View File

@ -3,8 +3,6 @@
#include <QLabel>
#include <QVBoxLayout>
#include "xenia/ui/qt/settings/widgets/settings_checkbox.h"
#include "xenia/ui/qt/settings/widgets/settings_groupbox.h"
#include "xenia/ui/qt/widgets/combobox.h"
#include "xenia/ui/qt/widgets/groupbox.h"
#include "xenia/ui/qt/widgets/scroll_area.h"
@ -39,44 +37,16 @@ void GeneralPane::Build() {
layout->setSpacing(16);
layout->setContentsMargins(32, 16, 32, 16);
// Add settings groupboxes to layout
layout->addWidget(CreateGeneralGroupBox());
layout->addWidget(CreateUpdateGroupBox());
layout->addWidget(CreateWindowGroupBox());
layout->addWidget(CreateLogGroupBox());
auto settings_factory = SettingsWidgetFactory();
auto settings_widget = settings_factory.BuildSettingsWidget("General");
layout->addWidget(settings_widget);
layout->addStretch();
set_widget(scroll_area);
}
XGroupBox* GeneralPane::CreateGeneralGroupBox() {
auto groupbox = new SettingsGroupBox("General Settings");
auto& config = Config::Instance();
auto discord_cvar = config.FindConfigVar(cvars::discord);
auto discord_checkbox = groupbox->CreateCheckBox("Discord Rich Presence", discord_cvar);
discord_checkbox->set_update_config_fn(
[discord_checkbox](bool value, cvar::ConfigVar<bool>& cvar) {
cvar.set_config_value(value);
discord_checkbox->UpdateLabel(
tr("Please restart xenia for this change to take effect."));
});
return groupbox;
}
XGroupBox* GeneralPane::CreateUpdateGroupBox() {
return new XGroupBox("Update Settings");
}
XGroupBox* GeneralPane::CreateWindowGroupBox() {
return new XGroupBox("Window Settings");
}
XGroupBox* GeneralPane::CreateLogGroupBox() {
return new XGroupBox("Log Settings");
}
} // namespace qt
} // namespace ui

View File

@ -15,12 +15,6 @@ class GeneralPane : public SettingsPane {
explicit GeneralPane() : SettingsPane(0xE713, "General") {}
void Build() override;
private:
XGroupBox* CreateGeneralGroupBox();
XGroupBox* CreateUpdateGroupBox();
XGroupBox* CreateWindowGroupBox();
XGroupBox* CreateLogGroupBox();
};
} // namespace qt

View File

@ -0,0 +1,10 @@
#include "settings_pane.h"
namespace xe {
namespace ui {
namespace qt {
}
}
}

View File

@ -5,13 +5,10 @@
#include "xenia/base/cvar.h"
#include "xenia/config.h"
#include "xenia/app/settings/settings.h"
#include "xenia/ui/qt/themeable_widget.h"
#include "xenia/ui/qt/settings/settings_widget_factory.h"
#include "xenia/config.h"
#include "xenia/ui/qt/settings/widgets/settings_widget.h"
#include "xenia/ui/qt/settings/widgets/settings_radio_button.h"
#include "xenia/ui/qt/settings/widgets/settings_combobox.h"
#include "xenia/ui/qt/settings/widgets/settings_text_edit.h"
#include "xenia/ui/qt/settings/widgets/settings_checkbox.h"
namespace xe {
namespace ui {
@ -19,6 +16,9 @@ namespace qt {
class SettingsPane : public Themeable<QWidget> {
Q_OBJECT
using SettingsGroup = xe::app::settings::SettingsGroup;
public:
explicit SettingsPane(QChar glyph, const QString& title,
QWidget* parent = nullptr)
@ -38,7 +38,6 @@ class SettingsPane : public Themeable<QWidget> {
protected:
void set_widget(QWidget* widget) { widget_ = widget; }
private:
QChar glyph_;
QString title_;

View File

@ -0,0 +1,251 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "settings_widget_factory.h"
#include <QHBoxLayout>
#include "xenia/ui/qt/widgets/checkbox.h"
#include "xenia/ui/qt/widgets/groupbox.h"
#include "xenia/ui/qt/widgets/line_edit.h"
#include "xenia/ui/qt/widgets/push_button.h"
#include "xenia/ui/qt/widgets/scroll_area.h"
#include "xenia/ui/qt/widgets/slider.h"
namespace xe {
namespace ui {
namespace qt {
const double kSubLabelSize = 6.5;
const int kLineEditMaxWidth = 420;
QWidget* SettingsWidgetFactory::BuildSettingsWidget(
const std::string& set_name) {
const auto& sets = settings_.settings();
auto set = std::find_if(
sets.begin(), sets.end(),
[&](const settings::SettingsSet& s) { return set_name == s.title; });
if (set == sets.end()) {
return nullptr;
}
QWidget* base_widget = new QWidget();
QVBoxLayout* layout = new QVBoxLayout();
base_widget->setLayout(layout);
for (const auto& group : set->groups) {
XGroupBox* group_box = new XGroupBox(group.title.c_str());
QVBoxLayout* gb_layout = new QVBoxLayout();
group_box->setLayout(gb_layout);
for (auto& item : group.items) {
QWidget* settings_widget = CreateWidgetForSettingsItem(*item);
if (settings_widget) {
gb_layout->addWidget(settings_widget);
}
}
layout->addWidget(group_box);
}
return base_widget;
}
QWidget* SettingsWidgetFactory::CreateWidgetForSettingsItem(
settings::ISettingsItem& item) {
try {
switch (item.type()) {
case settings::SettingsType::Boolean: {
return CreateCheckBoxWidget(
dynamic_cast<settings::BooleanSettingsItem&>(item));
break;
}
case settings::SettingsType::TextInput: {
return CreateTextInputWidget(
dynamic_cast<settings::TextInputSettingsItem&>(item));
break;
}
case settings::SettingsType::PathInput: {
return CreatePathInputWidget(
dynamic_cast<settings::FilePathInputSettingsItem&>(item));
break;
}
case settings::SettingsType::NumberInput: {
return CreateNumberInputWidget(
dynamic_cast<settings::NumberInputSettingsItem&>(item));
break;
}
case settings::SettingsType::MultiChoice: {
return nullptr; // TODO:
break;
}
case settings::SettingsType::Range: {
return CreateRangeInputWidget(
dynamic_cast<settings::RangeInputSettingsItem&>(item));
break;
}
case settings::SettingsType::Action: {
return CreateActionWidget(
dynamic_cast<settings::ActionSettingsItem&>(item));
break;
}
case settings::SettingsType::Custom: {
return nullptr;
// TODO: when/if custom widgets are required, build them here
break;
}
}
} catch (const std::bad_cast&) {
XELOGE("SettingsItem \"{}\" had wrong type value", item.title());
}
return nullptr;
}
QWidget* SettingsWidgetFactory::CreateCheckBoxWidget(
settings::BooleanSettingsItem& item) {
XCheckBox* checkbox = new XCheckBox();
checkbox->setText(item.title().c_str());
checkbox->setCheckState(*item.cvar()->current_value() ? Qt::Checked
: Qt::Unchecked);
XCheckBox::connect(checkbox, &XCheckBox::stateChanged, [&](int state) {
if (state == Qt::Checked) {
item.UpdateValue(true);
} else if (state == Qt::Unchecked) {
item.UpdateValue(false);
} else {
XELOGW("PartiallyChecked state not supported for SettingsCheckBox");
}
});
return CreateWidgetContainer(checkbox);
}
QWidget* SettingsWidgetFactory::CreateTextInputWidget(
settings::TextInputSettingsItem& item) {
QWidget* ctr = new QWidget();
QVBoxLayout* ctr_layout = new QVBoxLayout();
ctr->setLayout(ctr_layout);
QLabel* title_label = new QLabel(item.title().c_str());
XLineEdit* line_edit = new XLineEdit();
line_edit->setPlaceholderText(item.description().c_str());
line_edit->setMaximumWidth(kLineEditMaxWidth);
const auto& current_text = *item.cvar()->current_value();
line_edit->setText(QString(current_text.c_str()));
XLineEdit::connect(line_edit, &XLineEdit::textChanged,
[&](const QString& text) {
item.UpdateValue(std::string(text.toUtf8()));
});
ctr_layout->addWidget(title_label);
ctr_layout->addWidget(line_edit);
return CreateWidgetContainer(ctr);
}
QWidget* SettingsWidgetFactory::CreatePathInputWidget(
settings::FilePathInputSettingsItem& item) {
QWidget* ctr = new QWidget();
QVBoxLayout* ctr_layout = new QVBoxLayout();
ctr->setLayout(ctr_layout);
ctr_layout->setContentsMargins(0, 0, 0, 0);
ctr_layout->setSpacing(8);
QLabel* title_label = new QLabel(item.title().c_str());
QHBoxLayout* control_layout = new QHBoxLayout();
control_layout->setSpacing(8);
XLineEdit* line_edit = new XLineEdit();
line_edit->setPlaceholderText(item.description().c_str());
line_edit->setMaximumWidth(kLineEditMaxWidth);
const auto& current_path = *item.cvar()->current_value();
std::string current_path_str = std::string(current_path.u8string());
line_edit->setText(QString(current_path_str.c_str()));
XPushButton* browse_btn = new XPushButton();
browse_btn->SetIconFromGlyph(0xE838);
control_layout->addWidget(line_edit);
control_layout->addWidget(browse_btn);
control_layout->addStretch();
XLineEdit::connect(line_edit, &XLineEdit::textChanged,
[&](const QString& text) {
auto path = std::string(text.toUtf8());
item.UpdateValue(std::filesystem::path(path));
});
ctr_layout->addWidget(title_label);
ctr_layout->addLayout(control_layout);
return CreateWidgetContainer(ctr);
}
QWidget* SettingsWidgetFactory::CreateNumberInputWidget(
settings::NumberInputSettingsItem& item) {
// TODO: use XSpinBox (styled QSpinBox)
return nullptr;
}
QWidget* SettingsWidgetFactory::CreateRangeInputWidget(
settings::RangeInputSettingsItem& item) {
using xe::app::settings::ValueType;
QWidget* ctr = new QWidget();
QVBoxLayout* ctr_layout = new QVBoxLayout();
ctr->setLayout(ctr_layout);
QLabel* title_label = new QLabel(item.title().c_str());
XSlider* slider = new XSlider();
int min = xe::app::settings::number_value_to_int(item.min());
int max = xe::app::settings::number_value_to_int(item.max());
ctr_layout->addWidget(title_label);
ctr_layout->addWidget(slider);
return CreateWidgetContainer(ctr);
}
QWidget* SettingsWidgetFactory::CreateActionWidget(
settings::ActionSettingsItem& item) {
return nullptr;
}
QWidget* SettingsWidgetFactory::CreateWidgetContainer(QWidget* target_widget) {
QWidget* container_widget = new QWidget();
QVBoxLayout* widget_layout = new QVBoxLayout();
widget_layout->setContentsMargins(0, 0, 0, 0);
widget_layout->setSpacing(0);
container_widget->setLayout(widget_layout);
// label used to show warnings
QLabel* widget_label = new QLabel();
widget_label->setObjectName("subLabel");
widget_label->setProperty("type", "warning");
auto font = widget_label->font();
font.setPointSizeF(kSubLabelSize);
widget_label->setFont(font);
widget_label->setVisible(false);
widget_layout->addWidget(target_widget);
widget_layout->addWidget(widget_label);
return container_widget;
}
} // namespace qt
} // namespace ui
} // namespace xe

View File

@ -0,0 +1,50 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_SETTINGS_WIDGET_FACTORY_H_
#define XENIA_SETTINGS_WIDGET_FACTORY_H_
#include "xenia/app/settings/settings.h"
#include <QWidget>
#include <QVBoxLayout>
#include <QLabel>
namespace xe {
namespace ui {
namespace qt {
namespace settings = xe::app::settings;
class SettingsWidgetFactory {
public:
explicit SettingsWidgetFactory(
settings::Settings& settings = settings::Settings::Instance())
: settings_(settings) {}
QWidget* BuildSettingsWidget(const std::string& set_name);
QWidget* CreateWidgetForSettingsItem(settings::ISettingsItem& item);
private:
QWidget* CreateCheckBoxWidget(settings::BooleanSettingsItem& item);
QWidget* CreateTextInputWidget(settings::TextInputSettingsItem& item);
QWidget* CreatePathInputWidget(settings::FilePathInputSettingsItem& item);
QWidget* CreateNumberInputWidget(settings::NumberInputSettingsItem& item);
QWidget* CreateRangeInputWidget(settings::RangeInputSettingsItem& item);
//QWidget* CreateMultiChoiceWidget(settings::MultiChoiceSettingsItem<>& item);
QWidget* CreateActionWidget(settings::ActionSettingsItem& item);
QWidget* CreateWidgetContainer(QWidget* target);
settings::Settings& settings_;
};
} // namespace qt
} // namespace ui
} // namespace xe
#endif

View File

@ -1,41 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "settings_checkbox.h"
namespace xe {
namespace ui {
namespace qt {
void SettingsCheckBox::Initialize() {
if (!cvar_) {
return;
}
auto cvar = cvar_->as<bool>();
if (!cvar) {
return;
}
setChecked(*cvar->current_value());
connect(this, &SettingsCheckBox::stateChanged, [this](int state) {
if (state == Qt::Checked) {
UpdateValue(true);
} else if (state == Qt::Unchecked) {
UpdateValue(false);
} else {
XELOGW("PartiallyChecked state not supported for SettingsCheckBox");
}
});
}
} // namespace qt
} // namespace ui
} // namespace xe

View File

@ -1,39 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_QT_SETTINGS_SETTINGS_CHECKBOX_H_
#define XENIA_UI_QT_SETTINGS_SETTINGS_CHECKBOX_H_
#include "settings_widget.h"
#include "xenia/base/logging.h"
#include "xenia/ui/qt/widgets/checkbox.h"
namespace xe {
namespace ui {
namespace qt {
using SettingsCvar = cvar::ConfigVar<bool>;
class SettingsCheckBox : public SettingsWidget<bool, XCheckBox> {
public:
explicit SettingsCheckBox(const QString& text,
SettingsCvar* config_var = nullptr, QLabel* label = nullptr,
QWidget* parent = nullptr)
: SettingsWidget(config_var, label, text, parent) {
Initialize();
}
void Initialize();
};
} // namespace qt
} // namespace ui
} // namespace xe
#endif

View File

@ -1,77 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_QT_SETTINGS_SETTINGS_COMBOBOX_H_
#define XENIA_UI_QT_SETTINGS_SETTINGS_COMBOBOX_H_
#include "settings_widget.h"
#include "xenia/ui/qt/widgets/combobox.h"
namespace xe {
namespace ui {
namespace qt {
template <typename T>
class SettingsComboBox : public SettingsWidget<T, XComboBox> {
using SettingsCvar = cvar::ConfigVar<T>;
public:
SettingsComboBox(SettingsCvar* config_var = nullptr, QLabel* label = nullptr,
QWidget* parent = nullptr)
: SettingsWidget(config_var, label, parent) {}
void Initialize();
};
template <>
inline void SettingsComboBox<int>::Initialize() {
if (!cvar_) {
return;
}
if (!cvar_) {
return;
}
auto cvar = cvar_->as<int>();
if (!cvar) {
return;
}
setCurrentIndex(*cvar->current_value());
connect(this, QOverload<int>::of(&QComboBox::currentIndexChanged),
[this](int index) { UpdateValue(index); });
}
template <>
inline void SettingsComboBox<std::string>::Initialize() {
if (!cvar_) {
return;
}
if (!cvar_) {
return;
}
auto cvar = cvar_->as<std::string>();
if (!cvar) {
return;
}
setCurrentText(QString(*cvar->current_value()->c_str()));
connect(this, &QComboBox::currentTextChanged, [this](const QString& text) {
UpdateValue(std::string(text.toUtf8()));
});
}
} // namespace qt
} // namespace ui
} // namespace xe
#endif

View File

@ -1,77 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "settings_groupbox.h"
namespace xe {
namespace ui {
namespace qt {
double kSubLabelSize = 6.5;
SettingsCheckBox* SettingsGroupBox::CreateCheckBox(const QString& text,
cvar::ConfigVar<bool>* target) {
auto checkbox_layout = new QVBoxLayout();
checkbox_layout->setContentsMargins(0, 0, 0, 0);
checkbox_layout->setSpacing(0);
auto widget_label = new QLabel();
widget_label->setObjectName("subLabel");
widget_label->setProperty("type", "warning");
auto font = widget_label->font();
font.setPointSizeF(kSubLabelSize);
widget_label->setFont(font);
widget_label->setVisible(false);
auto checkbox = new SettingsCheckBox(text, target, widget_label);
checkbox_layout->addWidget(checkbox);
checkbox_layout->addWidget(widget_label);
layout_->addLayout(checkbox_layout);
return checkbox;
}
SettingsComboBox<int>* SettingsGroupBox::CreateComboBox(const QString& text,
cvar::ConfigVar<int>* target) {
auto combobox_layout = new QVBoxLayout();
combobox_layout->setContentsMargins(0, 0, 0, 0);
combobox_layout->setSpacing(0);
auto widget_label = new QLabel();
widget_label->setObjectName("subLabel");
widget_label->setProperty("type", "warning");
auto font = widget_label->font();
font.setPointSizeF(kSubLabelSize);
widget_label->setFont(font);
widget_label->setVisible(false);
auto combobox = new SettingsComboBox<int>();
combobox_layout->addWidget(combobox);
combobox_layout->addWidget(widget_label);
layout_->addLayout(combobox_layout);
return combobox;
}
SettingsComboBox<std::string>* SettingsGroupBox::CreateComboBox(
const QString& text, cvar::ConfigVar<std::string>* target) {
return nullptr;
}
SettingsRadioButton* SettingsGroupBox::CreateRadioButton(const QString& text,
cvar::ConfigVar<int>* target) {
return nullptr;
}
}
} // namespace ui
} // namespace xe

View File

@ -1,52 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_QT_SETTINGS_SETTINGS_GROUPBOX_H_
#define XENIA_UI_QT_SETTINGS_SETTINGS_GROUPBOX_H_
#include "settings_checkbox.h"
#include "settings_combobox.h"
#include "settings_radio_button.h"
#include "xenia/ui/qt/widgets/groupbox.h"
namespace xe {
namespace ui {
namespace qt {
class SettingsGroupBox : public XGroupBox {
public:
SettingsGroupBox(const QString& title, QWidget* parent = nullptr)
: XGroupBox(title, parent), layout_(nullptr) {
layout_ = new QVBoxLayout();
this->setLayout(layout_);
}
SettingsCheckBox* CreateCheckBox(const QString& text,
cvar::ConfigVar<bool>* target = nullptr);
SettingsComboBox<int>* CreateComboBox(const QString& text,
cvar::ConfigVar<int>* target = nullptr);
SettingsComboBox<std::string>* CreateComboBox(
const QString& text, cvar::ConfigVar<std::string>* target = nullptr);
SettingsRadioButton* CreateRadioButton(
const QString& text, cvar::ConfigVar<int>* target = nullptr);
template <typename T>
void AddSettingsWidget(SettingsWidget<T>* widget) {
layout_->addWidget(widget);
}
private:
QVBoxLayout* layout_;
};
} // namespace qt
} // namespace ui
} // namespace xe
#endif

View File

@ -1,26 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_QT_SETTINGS_SETTINGS_RADIOBOX_H_
#define XENIA_UI_QT_SETTINGS_SETTINGS_RADIOBOX_H_
#include "settings_widget.h"
#include "xenia/ui/qt/widgets/radio_button.h"
namespace xe {
namespace ui {
namespace qt {
class SettingsRadioButton : SettingsWidget<int, XRadioButton> {};
} // namespace qt
} // namespace ui
} // namespace xe
#endif

View File

@ -1,28 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_QT_SETTINGS_SETTINGS_TEXT_EDIT_H_
#define XENIA_UI_QT_SETTINGS_SETTINGS_TEXT_EDIT_H_
#include <filesystem>
#include <string>
#include "settings_widget.h"
#include "xenia/ui/qt/widgets/text_edit.h"
namespace xe {
namespace ui {
namespace qt {
class SettingsTextEdit : SettingsWidget<std::string, XTextEdit> {};
} // namespace qt
} // namespace ui
} // namespace xe
#endif

View File

@ -1,79 +0,0 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2020 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_UI_QT_SETTINGS_SETTINGS_WIDGET_H_
#define XENIA_UI_QT_SETTINGS_SETTINGS_WIDGET_H_
#include <QLabel>
#include <QVBoxLayout>
#include <functional>
#include "xenia/base/cvar.h"
#include "xenia/config.h"
#include "xenia/ui/qt/themeable_widget.h"
namespace xe {
namespace ui {
namespace qt {
template <typename T, typename Widget = QWidget>
class SettingsWidget : public Widget {
static_assert(std::is_base_of_v<QWidget, Widget>,
"SettingsWidget base must be a derivative of QWidget");
public:
template <typename... Args>
SettingsWidget(cvar::IConfigVar* config_var, QLabel* label = nullptr,
Args... args)
: Widget(args...), cvar_(config_var), label_(label) {
// default config update function
update_config_fn_ = [](T val, cvar::ConfigVar<T>& cvar) {
cvar.set_config_value(val);
};
}
void UpdateLabel(const QString& text) {
if (label_) {
label_->setText(text);
label_->setVisible(true);
} else {
label_->setVisible(false);
}
}
cvar::IConfigVar* config_var() const { return cvar_; }
void set_config_var(cvar::IConfigVar* cvar) { cvar_ = cvar; }
void set_update_config_fn(
const std::function<void(T, cvar::ConfigVar<T>&)>& fn) {
update_config_fn_ = fn;
}
void UpdateValue(T val) {
if (cvar_) {
auto cvar = cvar_->as<T>();
if (cvar) {
update_config_fn_(val, *cvar);
SaveToConfig();
}
}
}
void SaveToConfig() { Config::Instance().SaveConfig(); }
protected:
cvar::IConfigVar* cvar_;
std::function<void(T, cvar::ConfigVar<T>&)> update_config_fn_;
QLabel* label_;
};
} // namespace qt
} // namespace ui
} // namespace xe
#endif

1
third_party/pugixml vendored Submodule

@ -0,0 +1 @@
Subproject commit 8bd209fb8bfffaee17762839d0b177694f6e9039

17
third_party/pugixml.lua vendored Normal file
View File

@ -0,0 +1,17 @@
group("third_party")
project("pugixml")
uuid("4b27e817-23cf-4811-92f7-3f3f92057a83")
kind("StaticLib")
language("C++")
defines({
"_LIB",
"PUGIXML_NO_XPATH",
})
includedirs({
"pugixml/src",
})
files({
"pugixml/src/pugixml.cpp",
"pugixml/src/pugixml.hpp",
"pugixml/src/pugiconfig.hpp",
})

View File

@ -15,6 +15,7 @@ import re
import shutil
import subprocess
import sys
from functools import partial
__author__ = 'ben.vanik@gmail.com (Ben Vanik)'
@ -263,6 +264,33 @@ def generate_version_h():
with open('build/version.h', 'w') as f:
f.write(contents)
def generate_embedded_file_h(out_file, in_file, data_name):
n = 0
with open(in_file, "rb") as in_file:
c_file = open(out_file, "w")
static_content = '''
#ifndef XENIA_EMBEDDED_{0}_H_
#define XENIA_EMBEDDED_{0}_H_
namespace xe {{
namespace embedded {{
unsigned char {1}_data[] = {{'''.format(data_name.upper(), data_name)
array_content = ""
for c in iter(partial(in_file.read, 1), b''):
array_content += ("0x%02X," % ord(c))
n += 1
if n % 16 == 0:
array_content += "\n"
array_content = array_content[:len(array_content)-1]
final_content = static_content + "\n" + array_content + "\n" + '''
};
} // embedded
} // xe
#endif // XENIA_EMBEDDED_{0}_H_'''
c_file.write(final_content)
def embed_settings():
generate_embedded_file_h("build/settings.h", "assets/settings.xml", "settings")
def git_submodule_update():
"""Runs a full recursive git submodule init and update.
@ -346,6 +374,7 @@ def run_premake(target_os, action, cc=None):
if ret == 0:
generate_version_h()
embed_settings()
return ret