diff --git a/src/xenia/app/resources/themes/Xenia/stylesheets/SettingsTab.css b/src/xenia/app/resources/themes/Xenia/stylesheets/SettingsTab.css index 3f2c2723b..321d1fea2 100644 --- a/src/xenia/app/resources/themes/Xenia/stylesheets/SettingsTab.css +++ b/src/xenia/app/resources/themes/Xenia/stylesheets/SettingsTab.css @@ -32,4 +32,8 @@ QWidget#navigationContainer { QLabel#subLabel[type="warning"] { color: $warning; +} + +QWidget#SettingsTab QWidget#settingsContainer QLabel#titleLabel { + color: $light2; } \ No newline at end of file diff --git a/src/xenia/ui/qt/settings/settings_widget_factory.cc b/src/xenia/ui/qt/settings/settings_widget_factory.cc index e7b6acc5b..5ba309e76 100644 --- a/src/xenia/ui/qt/settings/settings_widget_factory.cc +++ b/src/xenia/ui/qt/settings/settings_widget_factory.cc @@ -9,12 +9,13 @@ #include "settings_widget_factory.h" #include -#include "xenia/ui/qt/widgets/checkbox.h" +#include "xenia/ui/qt/settings/widgets/settings_checkbox.h" +#include "xenia/ui/qt/settings/widgets/settings_combobox.h" +#include "xenia/ui/qt/settings/widgets/settings_line_edit.h" +#include "xenia/ui/qt/settings/widgets/settings_slider.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 { @@ -23,6 +24,12 @@ namespace qt { const double kSubLabelSize = 6.5; const int kLineEditMaxWidth = 420; +QLabel* create_title_label(const std::string& title) { + auto label = new QLabel(title.c_str()); + label->setObjectName("titleLabel"); + return label; +} + QWidget* SettingsWidgetFactory::BuildSettingsWidget( const std::string& set_name) { const auto& sets = settings_.settings(); @@ -53,6 +60,8 @@ QWidget* SettingsWidgetFactory::BuildSettingsWidget( layout->addWidget(group_box); } + base_widget->setObjectName("settingsContainer"); + return base_widget; } @@ -81,7 +90,8 @@ QWidget* SettingsWidgetFactory::CreateWidgetForSettingsItem( break; } case settings::SettingsType::MultiChoice: { - return nullptr; // TODO: + return CreateMultiChoiceWidget( + dynamic_cast(item)); break; } case settings::SettingsType::Range: { @@ -109,20 +119,7 @@ QWidget* SettingsWidgetFactory::CreateWidgetForSettingsItem( 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"); - } - }); + SettingsCheckBox* checkbox = new SettingsCheckBox(item); return CreateWidgetContainer(checkbox); } @@ -133,19 +130,9 @@ QWidget* SettingsWidgetFactory::CreateTextInputWidget( QVBoxLayout* ctr_layout = new QVBoxLayout(); ctr->setLayout(ctr_layout); - QLabel* title_label = new QLabel(item.title().c_str()); + QLabel* title_label = create_title_label(item.title()); - 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())); - }); + SettingsLineEdit* line_edit = new SettingsLineEdit(item); ctr_layout->addWidget(title_label); ctr_layout->addWidget(line_edit); @@ -161,32 +148,20 @@ QWidget* SettingsWidgetFactory::CreatePathInputWidget( ctr_layout->setContentsMargins(0, 0, 0, 0); ctr_layout->setSpacing(8); - QLabel* title_label = new QLabel(item.title().c_str()); + QLabel* title_label = create_title_label(item.title()); 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())); - + SettingsLineEdit* line_edit = new SettingsLineEdit(item); XPushButton* browse_btn = new XPushButton(); browse_btn->SetIconFromGlyph(0xE838); + // TODO: Setup browse button logic 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); @@ -201,20 +176,45 @@ QWidget* SettingsWidgetFactory::CreateNumberInputWidget( QWidget* SettingsWidgetFactory::CreateRangeInputWidget( settings::RangeInputSettingsItem& item) { - using xe::app::settings::ValueType; + using xe::app::settings::ValueType; QWidget* ctr = new QWidget(); - QVBoxLayout* ctr_layout = new QVBoxLayout(); + QHBoxLayout* ctr_layout = new QHBoxLayout(); + ctr_layout->setContentsMargins(0, 0, 0, 0); + ctr_layout->setSpacing(20); ctr->setLayout(ctr_layout); - QLabel* title_label = new QLabel(item.title().c_str()); + QLabel* title_label = create_title_label(item.title()); - XSlider* slider = new XSlider(); + SettingsSlider* slider = new SettingsSlider(item); int min = xe::app::settings::number_value_to_int(item.min()); int max = xe::app::settings::number_value_to_int(item.max()); + SettingsSlider::connect(slider, &SettingsSlider::valueChanged, + [&](int value) { item.UpdateValue(value); }); + ctr_layout->addWidget(title_label); ctr_layout->addWidget(slider); + ctr_layout->addStretch(); + + return CreateWidgetContainer(ctr); +} + +QWidget* SettingsWidgetFactory::CreateMultiChoiceWidget( + settings::IMultiChoiceSettingsItem& item) { + QWidget* ctr = new QWidget(); + QHBoxLayout* ctr_layout = new QHBoxLayout(); + ctr_layout->setContentsMargins(0, 0, 0, 0); + ctr_layout->setSpacing(20); + ctr->setLayout(ctr_layout); + + QLabel* title_label = create_title_label(item.title()); + + SettingsComboBox* combobox = new SettingsComboBox(item); + + ctr_layout->addWidget(title_label); + ctr_layout->addWidget(combobox); + ctr_layout->addStretch(); return CreateWidgetContainer(ctr); } diff --git a/src/xenia/ui/qt/settings/settings_widget_factory.h b/src/xenia/ui/qt/settings/settings_widget_factory.h index 727266dea..0fe093130 100644 --- a/src/xenia/ui/qt/settings/settings_widget_factory.h +++ b/src/xenia/ui/qt/settings/settings_widget_factory.h @@ -35,7 +35,7 @@ class SettingsWidgetFactory { QWidget* CreatePathInputWidget(settings::FilePathInputSettingsItem& item); QWidget* CreateNumberInputWidget(settings::NumberInputSettingsItem& item); QWidget* CreateRangeInputWidget(settings::RangeInputSettingsItem& item); - //QWidget* CreateMultiChoiceWidget(settings::MultiChoiceSettingsItem<>& item); + QWidget* CreateMultiChoiceWidget(settings::IMultiChoiceSettingsItem& item); QWidget* CreateActionWidget(settings::ActionSettingsItem& item); QWidget* CreateWidgetContainer(QWidget* target); diff --git a/src/xenia/ui/qt/settings/widgets/settings_checkbox.cc b/src/xenia/ui/qt/settings/widgets/settings_checkbox.cc new file mode 100644 index 000000000..b8f9c3fbc --- /dev/null +++ b/src/xenia/ui/qt/settings/widgets/settings_checkbox.cc @@ -0,0 +1,58 @@ +/** + ****************************************************************************** + * 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_checkbox.h" + +namespace xe { +namespace ui { +namespace qt { + +using namespace xe::app::settings; +using namespace xe::cvar; + +SettingsCheckBox::SettingsCheckBox(BooleanSettingsItem& item) + : XCheckBox(item.title().c_str()), item_(item) { + assert_true(Initialize(), "Could not initialize SettingsCheckBox"); +} + +SettingsCheckBox::~SettingsCheckBox() { item_.cvar()->RemoveListener(this); } + +bool SettingsCheckBox::Initialize() { + item_.cvar()->RegisterListener(this); + + setCheckState(*item_.cvar()->current_value() ? Qt::Checked : Qt::Unchecked); + + // update cvar when checkbox state changes + XCheckBox::connect(this, &XCheckBox::stateChanged, [&](int state) { + is_value_updating_ = true; + if (state == Qt::Checked) { + item_.UpdateValue(true); + } else if (state == Qt::Unchecked) { + item_.UpdateValue(false); + } else { + XELOGW("PartiallyChecked state not supported for SettingsCheckBox"); + } + is_value_updating_ = false; + }); + + return true; +} + +void SettingsCheckBox::OnValueUpdated(const ICommandVar& var) { + const auto& new_value = item_.cvar()->current_value(); + if (!this->is_value_updating_) { + // emit state change on ui thread + QMetaObject::invokeMethod(this, "setChecked", Qt::QueuedConnection, + Q_ARG(bool, *new_value)); + } +} + +} // namespace qt +} // namespace ui +} // namespace xe \ No newline at end of file diff --git a/src/xenia/ui/qt/settings/widgets/settings_checkbox.h b/src/xenia/ui/qt/settings/widgets/settings_checkbox.h new file mode 100644 index 000000000..79e2eedc5 --- /dev/null +++ b/src/xenia/ui/qt/settings/widgets/settings_checkbox.h @@ -0,0 +1,44 @@ +/** + ****************************************************************************** + * 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_CHECKBOX_H_ +#define XENIA_SETTINGS_CHECKBOX_H_ + +#include +#include "xenia/app/settings/settings.h" +#include "xenia/ui/qt/widgets/checkbox.h" + +namespace xe { +namespace ui { +namespace qt { + +using namespace xe::app::settings; +using namespace xe::cvar; + +class SettingsCheckBox : public XCheckBox, ICommandVarListener { + Q_OBJECT; + + public: + explicit SettingsCheckBox(BooleanSettingsItem& item); + ~SettingsCheckBox(); + + + bool Initialize(); + void OnValueUpdated(const ICommandVar& var) override; + + private: + BooleanSettingsItem& item_; + std::atomic_bool is_value_updating_ = false; +}; + +} // namespace qt +} // namespace ui +} // namespace xe + +#endif \ No newline at end of file diff --git a/src/xenia/ui/qt/settings/widgets/settings_combobox.cc b/src/xenia/ui/qt/settings/widgets/settings_combobox.cc new file mode 100644 index 000000000..7f3957fb4 --- /dev/null +++ b/src/xenia/ui/qt/settings/widgets/settings_combobox.cc @@ -0,0 +1,61 @@ +/** + ****************************************************************************** + * 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_combobox.h" + +namespace xe { +namespace ui { +namespace qt { + +using namespace xe::app::settings; +using namespace xe::cvar; + +const int kComboboxMaxWidth = 120; + +SettingsComboBox::SettingsComboBox(IMultiChoiceSettingsItem& item) + : XComboBox(), item_(item) { + assert_true(Initialize(), "Could not initialize SettingsComboBox"); +} + +SettingsComboBox::~SettingsComboBox() { item_.cvar()->RemoveListener(this); } + +bool SettingsComboBox::Initialize() { + setMaximumWidth(kComboboxMaxWidth); + + // load combobox items from setting + for (const std::string& option : item_.option_names()) { + addItem(option.c_str()); + } + + // set default value to match cvar + setCurrentIndex(item_.current_index()); + + // update cvar when index changes + XComboBox::connect(this, QOverload::of(&QComboBox::currentIndexChanged), + [&](int index) { + is_value_updating_ = true; + item_.UpdateIndex(index); + is_value_updating_ = false; + }); + + return true; +} + +void SettingsComboBox::OnValueUpdated(const ICommandVar& var) { + if (!is_value_updating_) { + int index = item_.current_index(); + // emit state change on ui thread + QMetaObject::invokeMethod(this, "setCurrentIndex", Qt::QueuedConnection, + Q_ARG(int, index)); + } +} + +} // namespace qt +} // namespace ui +} // namespace xe \ No newline at end of file diff --git a/src/xenia/ui/qt/settings/widgets/settings_combobox.h b/src/xenia/ui/qt/settings/widgets/settings_combobox.h new file mode 100644 index 000000000..3e7f0fb40 --- /dev/null +++ b/src/xenia/ui/qt/settings/widgets/settings_combobox.h @@ -0,0 +1,42 @@ +/** + ****************************************************************************** + * 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_COMBOBOX_H_ +#define XENIA_SETTINGS_COMBOBOX_H_ + +#include "xenia/app/settings/settings.h" +#include "xenia/base/cvar.h" +#include "xenia/ui/qt/widgets/combobox.h" + +namespace xe { +namespace ui { +namespace qt { + +using namespace xe::app::settings; +using namespace xe::cvar; +class SettingsComboBox : public XComboBox, ICommandVarListener { + Q_OBJECT; + + public: + explicit SettingsComboBox(IMultiChoiceSettingsItem& item); + ~SettingsComboBox(); + + bool Initialize(); + void OnValueUpdated(const ICommandVar& var) override; + + private: + IMultiChoiceSettingsItem& item_; + std::atomic_bool is_value_updating_ = false; +}; + +} // namespace qt +} // namespace ui +} // namespace xe + +#endif \ No newline at end of file diff --git a/src/xenia/ui/qt/settings/widgets/settings_line_edit.cc b/src/xenia/ui/qt/settings/widgets/settings_line_edit.cc new file mode 100644 index 000000000..e7ea94db0 --- /dev/null +++ b/src/xenia/ui/qt/settings/widgets/settings_line_edit.cc @@ -0,0 +1,98 @@ +/** + ****************************************************************************** + * 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_line_edit.h" + +namespace xe { +namespace ui { +namespace qt { + +using namespace xe::app::settings; +using namespace xe::cvar; + +const int kLineEditMaxWidth = 480; + +SettingsLineEdit::SettingsLineEdit(TextInputSettingsItem& item) + : XLineEdit(), item_(item), type_(Type::Text) { + assert_true(Initialize(), "Could not initialize SettingsLineEdit"); +} + +SettingsLineEdit::SettingsLineEdit(FilePathInputSettingsItem& item) + : XLineEdit(), item_(item), type_(Type::Path) { + assert_true(Initialize(), "Could not initialize SettingsLineEdit"); +} + +SettingsLineEdit::~SettingsLineEdit() { + if (type_ == Type::Path) { + auto& item = dynamic_cast(item_); + item.cvar()->RemoveListener(this); + } else if (type_ == Type::Text) { + auto& item = dynamic_cast(item_); + item.cvar()->RemoveListener(this); + } +} + +bool SettingsLineEdit::Initialize() { + if (type_ == Type::Path) { + auto item = dynamic_cast(&item_); + if (!item) return false; + + this->setPlaceholderText(item->description().c_str()); + this->setMaximumWidth(kLineEditMaxWidth); + + const auto& current_path = *item->cvar()->current_value(); + std::string current_path_str = std::string(current_path.u8string()); + this->setText(QString(current_path_str.c_str())); + + XLineEdit::connect(this, &XLineEdit::textChanged, + [=](const QString& text) { + auto path = std::string(text.toUtf8()); + item->UpdateValue(std::filesystem::path(path)); + }); + + return true; + } else if (type_ == Type::Text) { + auto item = dynamic_cast(&item_); + if (!item) return false; + + this->setPlaceholderText(item->description().c_str()); + this->setMaximumWidth(kLineEditMaxWidth); + + const auto& current_text = *item->cvar()->current_value(); + this->setText(QString(current_text.c_str())); + + XLineEdit::connect(this, &XLineEdit::textChanged, + [=](const QString& text) { + item->UpdateValue(std::string(text.toUtf8())); + }); + + return true; + } + + return false; +} + +void SettingsLineEdit::OnValueUpdated(const ICommandVar& var) { + if (!is_value_updating_) { + QString text = this->text(); + if (type_ == Type::Path) { + auto& item = dynamic_cast(item_); + text = item.cvar()->current_value()->string().c_str(); + } else if (type_ == Type::Text) { + auto& item = dynamic_cast(item_); + text = item.cvar()->current_value()->c_str(); + } + QMetaObject::invokeMethod(this, "setText", Qt::QueuedConnection, + Q_ARG(QString, text)); + } +} + +} // namespace qt +} // namespace ui +} // namespace xe \ No newline at end of file diff --git a/src/xenia/ui/qt/settings/widgets/settings_line_edit.h b/src/xenia/ui/qt/settings/widgets/settings_line_edit.h new file mode 100644 index 000000000..f76861aa7 --- /dev/null +++ b/src/xenia/ui/qt/settings/widgets/settings_line_edit.h @@ -0,0 +1,47 @@ +/** + ****************************************************************************** + * 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_LINE_EDIT_H_ +#define XENIA_SETTINGS_LINE_EDIT_H_ + +#include "xenia/app/settings/settings.h" +#include "xenia/base/cvar.h" +#include "xenia/ui/qt/widgets/line_edit.h" + +namespace xe { +namespace ui { +namespace qt { + +using namespace xe::app::settings; +using namespace xe::cvar; + +class SettingsLineEdit : public XLineEdit, ICommandVarListener { + Q_OBJECT; + + enum class Type { Text, Path }; + + public: + explicit SettingsLineEdit(TextInputSettingsItem& item); + explicit SettingsLineEdit(FilePathInputSettingsItem& item); + ~SettingsLineEdit(); + + bool Initialize(); + void OnValueUpdated(const ICommandVar& var) override; + + private: + ISettingsItem& item_; + std::atomic_bool is_value_updating_ = false; + Type type_; +}; + +} // namespace qt +} // namespace ui +} // namespace xe + +#endif \ No newline at end of file diff --git a/src/xenia/ui/qt/settings/widgets/settings_slider.cc b/src/xenia/ui/qt/settings/widgets/settings_slider.cc new file mode 100644 index 000000000..88a183cbf --- /dev/null +++ b/src/xenia/ui/qt/settings/widgets/settings_slider.cc @@ -0,0 +1,55 @@ +/** + ****************************************************************************** + * 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_slider.h" + +namespace xe { +namespace ui { +namespace qt { + +using namespace xe::app::settings; +using namespace xe::cvar; + +SettingsSlider::SettingsSlider(RangeInputSettingsItem& item) + : XSlider(), item_(item) { + assert_true(Initialize(), "Could not initialize SettingsSlider"); +} + +SettingsSlider::~SettingsSlider() { item_.cvar()->RemoveListener(this); } + +bool SettingsSlider::Initialize() { + int min = number_value_to_int(item_.min()); + int max = number_value_to_int(item_.max()); + + this->setMinimum(min); + this->setMaximum(max); + + int current_value = number_value_to_int(item_.current_value()); + setValue(current_value); + + XSlider::connect(this, &XSlider::valueChanged, [&](int value) { + // TODO: handle UpdateValue returning false for value out of range + item_.UpdateValue(value); + }); + + return true; +} + +void SettingsSlider::OnValueUpdated(const ICommandVar& var) { + if (!is_value_updating_) { + int current_value = number_value_to_int(item_.current_value()); + // update value on UI thread + QMetaObject::invokeMethod(this, "setValue", Qt::QueuedConnection, + Q_ARG(int, current_value)); + } +} + +} // namespace qt +} // namespace ui +} // namespace xe \ No newline at end of file diff --git a/src/xenia/ui/qt/settings/widgets/settings_slider.h b/src/xenia/ui/qt/settings/widgets/settings_slider.h new file mode 100644 index 000000000..c083454e0 --- /dev/null +++ b/src/xenia/ui/qt/settings/widgets/settings_slider.h @@ -0,0 +1,43 @@ +/** + ****************************************************************************** + * 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_SLIDER_H_ +#define XENIA_SETTINGS_SLIDER_H_ + +#include "xenia/app/settings/settings.h" +#include "xenia/base/cvar.h" +#include "xenia/ui/qt/widgets/slider.h" + +namespace xe { +namespace ui { +namespace qt { + +using namespace xe::app::settings; +using namespace xe::cvar; + +class SettingsSlider : public XSlider, ICommandVarListener { + Q_OBJECT; + + public: + explicit SettingsSlider(RangeInputSettingsItem& item); + ~SettingsSlider(); + + bool Initialize(); + void OnValueUpdated(const ICommandVar& var) override; + + private: + RangeInputSettingsItem& item_; + std::atomic_bool is_value_updating_ = false; +}; + +} // namespace qt +} // namespace ui +} // namespace xe + +#endif \ No newline at end of file