SceneSwitcher/plugins/base/utils/transform-setting.cpp
2025-04-22 15:35:45 +02:00

384 lines
10 KiB
C++

#include "transform-setting.hpp"
#include "json-helpers.hpp"
#include "obs-module-helper.hpp"
#include "math-helpers.hpp"
#include "scene-item-transform-helpers.hpp"
#include <nlohmann/json.hpp>
#include <QLayout>
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<TransformSetting> &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<TransformSetting> GetSoruceTransformSettings(obs_scene_item *source)
{
std::vector<TransformSetting> settings;
auto jsonString = GetSceneItemTransform(source);
getSoruceTransformSettingsHelper(settings, jsonString);
return settings;
}
std::optional<std::string>
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<class T>
static void logConversionError(T, const char *func, const char *target,
const char *value)
{
if (std::is_same<T, std::invalid_argument>::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;
} else if (setting.GetNestedID() == "pos") {
target = &info.pos;
}
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 = {};
#if (LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 1, 0))
obs_sceneitem_get_info2(source, &info);
#else
obs_sceneitem_get_info(source, &info);
#endif
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<obs_bounds_type>(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);
#if (LIBOBS_API_VER >= MAKE_SEMANTIC_VERSION(30, 1, 0))
obs_sceneitem_set_info2(source, &info);
#else
obs_sceneitem_set_info(source, &info);
#endif
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<TransformSetting>();
emit SelectionChanged(setting);
}
static QString mapIdToLocale(const TransformSetting &setting)
{
static const std::unordered_map<std::string_view, std::string_view>
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<std::string_view, std::string_view>
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