From d3164e3ed699e0ff3838857c41d00c9d6b02d6c2 Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Thu, 20 Jun 2024 21:02:43 +0200 Subject: [PATCH] Add TransformSettingSelection --- data/locale/en-US.ini | 14 + plugins/base/CMakeLists.txt | 2 + plugins/base/utils/transform-setting.cpp | 373 +++++++++++++++++++++++ plugins/base/utils/transform-setting.hpp | 58 ++++ 4 files changed, 447 insertions(+) create mode 100644 plugins/base/utils/transform-setting.cpp create mode 100644 plugins/base/utils/transform-setting.hpp diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 31308724..79ce61fc 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -1840,6 +1840,20 @@ AdvSceneSwitcher.settings.suffix.type.frameRate=" (Frame rate)" AdvSceneSwitcher.settings.suffix.type.group=" (Group)" AdvSceneSwitcher.settings.suffix.type.colorAlpha=" (Color alpha)" AdvSceneSwitcher.setting.select="--select setting--" +AdvSceneSwitcher.setting.transform.alignment="Positional Alignment" +AdvSceneSwitcher.setting.transform.cropBottom="[Crop]Bottom" +AdvSceneSwitcher.setting.transform.cropLeft="[Crop]Left" +AdvSceneSwitcher.setting.transform.cropRight="[Crop]Right" +AdvSceneSwitcher.setting.transform.cropTop="[Crop]Top" +AdvSceneSwitcher.setting.transform.boundingBoxType="Bounding Box Type" +AdvSceneSwitcher.setting.transform.boundingBoxAlign="Alignment in Bounding Box" +AdvSceneSwitcher.setting.transform.boundingBoxSize="Bounding Box Size" +AdvSceneSwitcher.setting.transform.rotation="Rotation" +AdvSceneSwitcher.setting.transform.scale="Scale" +AdvSceneSwitcher.setting.transform.size="Size" +AdvSceneSwitcher.setting.transform.position="Position" +AdvSceneSwitcher.setting.transform.height="Height" +AdvSceneSwitcher.setting.transform.width="Width" AdvSceneSwitcher.sceneItemSelection.configure="Configure scene item selection type" diff --git a/plugins/base/CMakeLists.txt b/plugins/base/CMakeLists.txt index 7617fe29..f2ec114b 100644 --- a/plugins/base/CMakeLists.txt +++ b/plugins/base/CMakeLists.txt @@ -172,6 +172,8 @@ target_sources( utils/striped-frame.hpp utils/text-helpers.cpp utils/text-helpers.hpp + utils/transform-setting.cpp + utils/transform-setting.hpp utils/transition-selection.cpp utils/transition-selection.hpp utils/websocket-helpers.cpp diff --git a/plugins/base/utils/transform-setting.cpp b/plugins/base/utils/transform-setting.cpp new file mode 100644 index 00000000..153e68a8 --- /dev/null +++ b/plugins/base/utils/transform-setting.cpp @@ -0,0 +1,373 @@ +#include "transform-setting.hpp" +#include "obs-module-helper.hpp" +#include "math-helpers.hpp" +#include "scene-item-transform-helpers.hpp" +#include "utility.hpp" + +#include +#include + +Q_DECLARE_METATYPE(advss::TransformSetting); + +namespace advss { + +TransformSetting::TransformSetting(const std::string &id, + const std::string &nestedId) + : _id(id), + _nestedId(nestedId) +{ +} + +bool TransformSetting::Save(obs_data_t *obj) const +{ + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_string(data, "id", _id.c_str()); + obs_data_set_string(data, "nestedId", _nestedId.c_str()); + obs_data_set_obj(obj, "transformSetting", data); + return true; +} + +bool TransformSetting::Load(obs_data_t *obj) +{ + OBSDataAutoRelease data = obs_data_get_obj(obj, "transformSetting"); + _id = obs_data_get_string(data, "id"); + _nestedId = obs_data_get_string(data, "nestedId"); + return true; +} + +bool TransformSetting::operator==(const TransformSetting &other) const +{ + return _id == other._id && _nestedId == other._nestedId; +} + +static void +getSoruceTransformSettingsHelper(std::vector &settings, + const std::string &jsonString, + const std::string &nestedId = "") +{ + try { + auto transform = nlohmann::json::parse(jsonString); + for (const auto &[key, value] : transform.items()) { + if (value.is_object() && nestedId.empty()) { + getSoruceTransformSettingsHelper( + settings, value.dump(), key); + } else { + settings.emplace_back(key, nestedId); + } + } + } catch (const nlohmann::json::exception &) { + return; + } +} + +std::vector GetSoruceTransformSettings(obs_scene_item *source) +{ + std::vector settings; + auto jsonString = GetSceneItemTransform(source); + getSoruceTransformSettingsHelper(settings, jsonString); + return settings; +} + +std::optional +GetTransformSettingValue(obs_scene_item *source, + const TransformSetting &setting) +{ + auto jsonString = GetSceneItemTransform(source); + if (setting.GetNestedID().empty()) { + return GetJsonField(jsonString, setting.GetID()); + } + auto nestedJsonString = GetJsonField(jsonString, setting.GetNestedID()); + if (nestedJsonString.has_value()) { + return GetJsonField(*nestedJsonString, setting.GetID()); + } + return {}; +} + +template +static void logConversionError(T exception, const char *func, + const char *target, const char *value) +{ + if (std::is_same::value) { + blog(LOG_WARNING, "%s invalid %s value (%s)", func, target, + value); + } else { + blog(LOG_WARNING, "%s value out of range for %s (%s)", func, + target, value); + } +} + +static void handleCrop(struct obs_sceneitem_crop &crop, const std::string &id, + const std::string &value) +{ + int val = 0; + try { + val = std::stoi(value); + } catch (const std::invalid_argument &e) { + logConversionError(e, __func__, id.c_str(), value.c_str()); + return; + } catch (const std::out_of_range &e) { + logConversionError(e, __func__, id.c_str(), value.c_str()); + return; + } + + if (id == "left") { + crop.left = val; + } else if (id == "top") { + crop.top = val; + } else if (id == "right") { + crop.right = val; + } else if (id == "bottom") { + crop.bottom = val; + } +} + +static void handlePosOrScaleOrBounds(struct obs_transform_info &info, + const TransformSetting &setting, + const std::string &value) +{ + float val = 0; + try { + val = std::stof(value); + } catch (const std::invalid_argument &e) { + logConversionError( + e, __func__, + (setting.GetID() + "-" + setting.GetNestedID()).c_str(), + value.c_str()); + return; + } catch (const std::out_of_range &e) { + logConversionError( + e, __func__, + (setting.GetID() + "-" + setting.GetNestedID()).c_str(), + value.c_str()); + return; + } + + struct vec2 *target = nullptr; + if (setting.GetNestedID() == "scale") { + target = &info.scale; + } else if (setting.GetNestedID() == "bounds") { + target = &info.bounds; + } + + if (setting.GetID() == "x") { + target->x = val; + } else if (setting.GetID() == "y") { + target->y = val; + } +} + +static void handleRot(struct obs_transform_info &info, const std::string &value) +{ + float val = 0; + try { + val = std::stof(value); + } catch (const std::invalid_argument &e) { + logConversionError(e, __func__, "rot", value.c_str()); + return; + } catch (const std::out_of_range &e) { + logConversionError(e, __func__, "rot", value.c_str()); + return; + } + + info.rot = val; +} + +void SetTransformSetting(obs_scene_item *source, + const TransformSetting &setting, + const std::string &value) +{ + auto id = setting.GetID(); + + struct obs_transform_info info = {}; + struct obs_sceneitem_crop crop = {}; + + obs_sceneitem_get_info(source, &info); + obs_sceneitem_get_crop(source, &crop); + + if (id == "alignment") { + try { + auto val = std::stoul(value); + info.alignment = val; + } catch (const std::invalid_argument &e) { + logConversionError(e, __func__, id.c_str(), + value.c_str()); + } catch (const std::out_of_range &e) { + logConversionError(e, __func__, id.c_str(), + value.c_str()); + } + } else if (id == "left" || id == "top" || id == "right" || + id == "bottom") { + handleCrop(crop, id, value); + } else if (id == "bounds_alignment") { + uint32_t val = 0; + try { + val = std::stoul(value); + info.bounds_alignment = val; + } catch (const std::invalid_argument &e) { + logConversionError(e, __func__, id.c_str(), + value.c_str()); + } catch (const std::out_of_range &e) { + logConversionError(e, __func__, id.c_str(), + value.c_str()); + } + } else if (id == "bounds_type") { + try { + obs_bounds_type val = + static_cast(std::stoul(value)); + info.bounds_type = val; + } catch (const std::invalid_argument &e) { + logConversionError(e, __func__, id.c_str(), + value.c_str()); + } catch (const std::out_of_range &e) { + logConversionError(e, __func__, id.c_str(), + value.c_str()); + } + } else if (id == "x" || id == "y") { + handlePosOrScaleOrBounds(info, setting, value); + } else if (id == "width" || id == "height") { + float val = 0; + try { + val = std::stof(value); + } catch (const std::invalid_argument &e) { + logConversionError( + e, __func__, + (setting.GetID() + "-" + setting.GetNestedID()) + .c_str(), + value.c_str()); + return; + } catch (const std::out_of_range &e) { + logConversionError( + e, __func__, + (setting.GetID() + "-" + setting.GetNestedID()) + .c_str(), + value.c_str()); + return; + } + auto source2 = obs_sceneitem_get_source(source); + if (setting.GetID() == "width") { + val = val / obs_source_get_width(source2); + handlePosOrScaleOrBounds(info, + TransformSetting("x", "scale"), + std::to_string(val)); + } else if (setting.GetID() == "height") { + val = val / obs_source_get_height(source2); + handlePosOrScaleOrBounds(info, + TransformSetting("y", "scale"), + std::to_string(val)); + } + } else if (id == "rot") { + handleRot(info, value); + } + + obs_sceneitem_defer_update_begin(source); + obs_sceneitem_set_info(source, &info); + obs_sceneitem_set_crop(source, &crop); + obs_sceneitem_defer_update_end(source); + obs_sceneitem_force_update_transform(source); +} + +TransformSettingSelection::TransformSettingSelection(QWidget *parent) + : QWidget(parent), + _settings(new FilterComboBox( + this, obs_module_text("AdvSceneSwitcher.setting.select"))) +{ + _settings->setSizeAdjustPolicy(QComboBox::AdjustToContents); + _settings->setMaximumWidth(350); + + QWidget::connect(_settings, SIGNAL(currentIndexChanged(int)), this, + SLOT(SelectionIdxChanged(int))); + + auto layout = new QHBoxLayout(); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(_settings); + setLayout(layout); +} + +void TransformSettingSelection::SetSource(obs_scene_item *source) +{ + _settings->clear(); + Populate(source); +} + +void TransformSettingSelection::SetSetting(const TransformSetting &setting) +{ + QVariant variant; + variant.setValue(setting); + _settings->setCurrentIndex(_settings->findData(variant)); +} + +void TransformSettingSelection::SelectionIdxChanged(int idx) +{ + if (idx == -1) { + return; + } + auto setting = _settings->itemData(idx).value(); + emit SelectionChanged(setting); +} + +static QString mapIdToLocale(const TransformSetting &setting) +{ + static const std::unordered_map + idMap = { + {"alignment", + "AdvSceneSwitcher.setting.transform.alignment"}, + {"bottom", + "AdvSceneSwitcher.setting.transform.cropBottom"}, + {"left", "AdvSceneSwitcher.setting.transform.cropLeft"}, + {"right", + "AdvSceneSwitcher.setting.transform.cropRight"}, + {"top", "AdvSceneSwitcher.setting.transform.cropTop"}, + {"bounds_alignment", + "AdvSceneSwitcher.setting.transform.boundingBoxAlign"}, + {"bounds_type", + "AdvSceneSwitcher.setting.transform.boundingBoxType"}, + {"rot", "AdvSceneSwitcher.setting.transform.rotation"}, + {"height", "AdvSceneSwitcher.setting.transform.height"}, + {"width", "AdvSceneSwitcher.setting.transform.width"}, + {"x", "x"}, + {"y", "y"}, + }; + + static const std::unordered_map + nestedIdMap = { + {"bounds", + "AdvSceneSwitcher.setting.transform.boundingBoxSize"}, + {"pos", "AdvSceneSwitcher.setting.transform.position"}, + {"scale", "AdvSceneSwitcher.setting.transform.scale"}, + {"size", "AdvSceneSwitcher.setting.transform.size"}, + }; + + auto idNameIt = idMap.find(setting.GetID()); + QString name = idNameIt == idMap.end() + ? QString::fromStdString(setting.GetID()) + : obs_module_text(idNameIt->second.data()); + + if (!setting.GetNestedID().empty()) { + auto idNameIt = nestedIdMap.find(setting.GetNestedID()); + const auto nestedName = + idNameIt == nestedIdMap.end() + ? QString::fromStdString(setting.GetID()) + : obs_module_text(idNameIt->second.data()); + name = "[" + nestedName + "] " + name; + } + return name; +} + +void TransformSettingSelection::Populate(obs_scene_item *source) +{ + auto settings = GetSoruceTransformSettings(source); + for (const auto &setting : settings) { + QVariant variant; + variant.setValue(setting); + auto name = mapIdToLocale(setting); + _settings->addItem(name, variant); + } + _settings->setCurrentIndex(-1); + TransformSetting emptySetting; + emit SelectionChanged(emptySetting); + + adjustSize(); + updateGeometry(); +} + +} // namespace advss diff --git a/plugins/base/utils/transform-setting.hpp b/plugins/base/utils/transform-setting.hpp new file mode 100644 index 00000000..4b3c66ce --- /dev/null +++ b/plugins/base/utils/transform-setting.hpp @@ -0,0 +1,58 @@ +#pragma once +#include "filter-combo-box.hpp" + +#include +#include +#include +#include +#include +#include + +namespace advss { + +class TransformSetting { +public: + TransformSetting() = default; + TransformSetting(const std::string &id, const std::string &nestedId); + bool Save(obs_data_t *obj) const; + bool Load(obs_data_t *obj); + std::string GetID() const { return _id; } + std::string GetNestedID() const { return _nestedId; } + EXPORT bool operator==(const TransformSetting &other) const; + +private: + std::string _id = ""; + std::string _nestedId = ""; + friend class TransformSettingSelection; +}; + +std::vector +GetSoruceTransformSettings(obs_scene_item *source); +std::optional +GetTransformSettingValue(obs_scene_item *source, + const TransformSetting &setting); +void SetTransformSetting(obs_scene_item *source, + const TransformSetting &setting, + const std::string &value); + +class TransformSettingSelection : public QWidget { + Q_OBJECT + +public: + TransformSettingSelection(QWidget *parent = nullptr); + void SetSource(obs_scene_item *); + void SetSetting(const TransformSetting &); + +private slots: + void SelectionIdxChanged(int); + +signals: + void SelectionChanged(const TransformSetting &); + +private: + void Populate(obs_scene_item *); + + FilterComboBox *_settings; +}; + +} // namespace advss