From f92ed3139decaec72f7b7568c558ed95846f9b9b Mon Sep 17 00:00:00 2001 From: WarmUpTill Date: Thu, 20 Jul 2023 21:39:19 +0200 Subject: [PATCH] Add more transform actions * Reset * Rotation * Flip (horizontal / vertical) * Fit to screen * Stretch to screen * Center (horizontal / vertical / to screen) --- data/locale/de-DE.ini | 2 +- data/locale/en-US.ini | 12 +- data/locale/es-ES.ini | 2 +- data/locale/tr-TR.ini | 2 +- data/locale/zh-CN.ini | 2 +- .../macro-action-scene-transform.cpp | 344 ++++++++++++++++-- .../macro-action-scene-transform.hpp | 29 +- 7 files changed, 361 insertions(+), 32 deletions(-) diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index 84af4322..72b7024a 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -516,7 +516,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="An Position verschieben" AdvSceneSwitcher.action.sceneOrder.entry="Auf{{scenes}}{{actions}}{{sources}}{{position}}" AdvSceneSwitcher.action.sceneTransform="Szenenelement transformieren" AdvSceneSwitcher.action.sceneTransform.getTransform="Transformation erhalten" -AdvSceneSwitcher.action.sceneTransform.entry="Auf{{scenes}}transformieren{{sources}}" +AdvSceneSwitcher.action.sceneTransform.entry="Auf{{scenes}}{{action}}{{rotation}}{{sources}}" AdvSceneSwitcher.action.file="Datei" AdvSceneSwitcher.action.file.type.write="Schreiben" AdvSceneSwitcher.action.file.type.append="Anhängen" diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 16f9df11..8a246df3 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -617,8 +617,18 @@ AdvSceneSwitcher.action.sceneOrder.type.moveBottom="Move to bottom" AdvSceneSwitcher.action.sceneOrder.type.movePosition="Move to position" AdvSceneSwitcher.action.sceneOrder.entry="On{{scenes}}{{actions}}{{sources}}{{position}}" AdvSceneSwitcher.action.sceneTransform="Scene item transform" +AdvSceneSwitcher.action.sceneTransform.type.manual="Transform" +AdvSceneSwitcher.action.sceneTransform.type.reset="Reset transform" +AdvSceneSwitcher.action.sceneTransform.type.rotate="Rotate by" +AdvSceneSwitcher.action.sceneTransform.type.flipHorizontal="Flip horizontally" +AdvSceneSwitcher.action.sceneTransform.type.flipVertical="Flip vertically" +AdvSceneSwitcher.action.sceneTransform.type.fitToScreen="Fit to screen" +AdvSceneSwitcher.action.sceneTransform.type.stretchToScreen="Stretch to screen" +AdvSceneSwitcher.action.sceneTransform.type.centerToScreen="Center to screen" +AdvSceneSwitcher.action.sceneTransform.type.centerVertically="Center vertically" +AdvSceneSwitcher.action.sceneTransform.type.centerHorizontally="Center horizontally" AdvSceneSwitcher.action.sceneTransform.getTransform="Get transform" -AdvSceneSwitcher.action.sceneTransform.entry="On{{scenes}}transform{{sources}}" +AdvSceneSwitcher.action.sceneTransform.entry="On{{scenes}}{{action}}{{rotation}}{{sources}}" AdvSceneSwitcher.action.file="File" AdvSceneSwitcher.action.file.type.write="Write" AdvSceneSwitcher.action.file.type.append="Append" diff --git a/data/locale/es-ES.ini b/data/locale/es-ES.ini index 282c20ba..71a586dd 100644 --- a/data/locale/es-ES.ini +++ b/data/locale/es-ES.ini @@ -432,7 +432,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="Mover a la posición" AdvSceneSwitcher.action.sceneOrder.entry="En{{scenes}}{{actions}}{{sources}}{{position}}" AdvSceneSwitcher.action.sceneTransform="Transformar elemento de escena" AdvSceneSwitcher.action.sceneTransform.getTransform="Obtener transformación" -AdvSceneSwitcher.action.sceneTransform.entry="En{{scenes}}transformar{{sources}}" +AdvSceneSwitcher.action.sceneTransform.entry="En{{scenes}}{{action}}{{rotation}}{{sources}}" AdvSceneSwitcher.action.file="Archivo" AdvSceneSwitcher.action.file.type.write="Escribir" AdvSceneSwitcher.action.file.type.append="Agregar" diff --git a/data/locale/tr-TR.ini b/data/locale/tr-TR.ini index 92200066..09fbd9b3 100644 --- a/data/locale/tr-TR.ini +++ b/data/locale/tr-TR.ini @@ -360,7 +360,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="Konuma taşı" AdvSceneSwitcher.action.sceneOrder.entry="Açık{{scenes}}{{actions}}{{sources}}{{position}}" AdvSceneSwitcher.action.sceneTransform="Sahne öğesi dönüşümü" AdvSceneSwitcher.action.sceneTransform.getTransform="Dönüşümü al" -AdvSceneSwitcher.action.sceneTransform.entry="Açık{{scenes}}transform{{sources}}" +AdvSceneSwitcher.action.sceneTransform.entry="Açık{{scenes}}{{action}}{{rotation}}{{sources}}" AdvSceneSwitcher.action.file="Dosya" AdvSceneSwitcher.action.file.type.write="Yaz" AdvSceneSwitcher.action.file.type.append="Ekle" diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index 619f07d3..aefe1112 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -569,7 +569,7 @@ AdvSceneSwitcher.action.sceneOrder.type.movePosition="移动到数值位置" AdvSceneSwitcher.action.sceneOrder.entry="在 {{scenes}} {{actions}} {{sources}} {{position}}" AdvSceneSwitcher.action.sceneTransform="场景项目转换" AdvSceneSwitcher.action.sceneTransform.getTransform="获得转换" -AdvSceneSwitcher.action.sceneTransform.entry="在 {{scenes}} 上转换 {{sources}}" +AdvSceneSwitcher.action.sceneTransform.entry="在 {{scenes}}{{action}}{{rotation}}{{sources}}" AdvSceneSwitcher.action.file="文件" AdvSceneSwitcher.action.file.type.write="写入" AdvSceneSwitcher.action.file.type.append="追加写入" diff --git a/src/macro-core/macro-action-scene-transform.cpp b/src/macro-core/macro-action-scene-transform.cpp index f898157e..4b678597 100644 --- a/src/macro-core/macro-action-scene-transform.cpp +++ b/src/macro-core/macro-action-scene-transform.cpp @@ -1,6 +1,8 @@ #include "macro-action-scene-transform.hpp" #include "utility.hpp" +#include + namespace advss { const std::string MacroActionSceneTransform::id = "scene_transform"; @@ -11,17 +13,243 @@ bool MacroActionSceneTransform::_registered = MacroActionFactory::Register( MacroActionSceneTransformEdit::Create, "AdvSceneSwitcher.action.sceneTransform"}); +const static std::map actions = { + {MacroActionSceneTransform::Action::MANUAL_TRANSFORM, + "AdvSceneSwitcher.action.sceneTransform.type.manual"}, + {MacroActionSceneTransform::Action::RESET, + "AdvSceneSwitcher.action.sceneTransform.type.reset"}, + {MacroActionSceneTransform::Action::ROTATE, + "AdvSceneSwitcher.action.sceneTransform.type.rotate"}, + {MacroActionSceneTransform::Action::FLIP_HORIZONTAL, + "AdvSceneSwitcher.action.sceneTransform.type.flipHorizontal"}, + {MacroActionSceneTransform::Action::FLIP_VERTICAL, + "AdvSceneSwitcher.action.sceneTransform.type.flipVertical"}, + {MacroActionSceneTransform::Action::FIT_TO_SCREEN, + "AdvSceneSwitcher.action.sceneTransform.type.fitToScreen"}, + {MacroActionSceneTransform::Action::STRETCH_TO_SCREEN, + "AdvSceneSwitcher.action.sceneTransform.type.stretchToScreen"}, + {MacroActionSceneTransform::Action::CENTER_TO_SCREEN, + "AdvSceneSwitcher.action.sceneTransform.type.centerToScreen"}, + {MacroActionSceneTransform::Action::CENTER_VERTICALLY, + "AdvSceneSwitcher.action.sceneTransform.type.centerVertically"}, + {MacroActionSceneTransform::Action::CENTER_HORIZONTALLY, + "AdvSceneSwitcher.action.sceneTransform.type.centerHorizontally"}, +}; + +/* --------------- Helpers based on OBS window-basic-main.cpp --------------- */ + +static void getItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br) +{ + matrix4 boxTransform; + obs_sceneitem_get_box_transform(item, &boxTransform); + + vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); + vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f); + + auto GetMinPos = [&](float x, float y) { + vec3 pos; + vec3_set(&pos, x, y, 0.0f); + vec3_transform(&pos, &pos, &boxTransform); + vec3_min(&tl, &tl, &pos); + vec3_max(&br, &br, &pos); + }; + + GetMinPos(0.0f, 0.0f); + GetMinPos(1.0f, 0.0f); + GetMinPos(0.0f, 1.0f); + GetMinPos(1.0f, 1.0f); +} + +static vec3 getItemTL(obs_sceneitem_t *item) +{ + vec3 tl, br; + getItemBox(item, tl, br); + return tl; +} + +static void setItemTL(obs_sceneitem_t *item, const vec3 &tl) +{ + vec3 newTL; + vec2 pos; + + obs_sceneitem_get_pos(item, &pos); + newTL = getItemTL(item); + pos.x += tl.x - newTL.x; + pos.y += tl.y - newTL.y; + obs_sceneitem_set_pos(item, &pos); +} + +static void reset(obs_sceneitem_t *item) +{ + obs_transform_info info; + vec2_set(&info.pos, 0.0f, 0.0f); + vec2_set(&info.scale, 1.0f, 1.0f); + info.rot = 0.0f; + info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; + info.bounds_type = OBS_BOUNDS_NONE; + info.bounds_alignment = OBS_ALIGN_CENTER; + vec2_set(&info.bounds, 0.0f, 0.0f); + obs_sceneitem_set_info(item, &info); + + obs_sceneitem_crop crop = {}; + obs_sceneitem_set_crop(item, &crop); +} + +static void rotate(obs_sceneitem_t *item, float rot) +{ + vec3 tl = getItemTL(item); + rot += obs_sceneitem_get_rot(item); + if (rot >= 360.0f) { + rot -= 360.0f; + } else if (rot <= -360.0f) { + rot += 360.0f; + } + obs_sceneitem_set_rot(item, rot); + obs_sceneitem_force_update_transform(item); + setItemTL(item, tl); +}; + +static void flip(obs_sceneitem_t *item, const vec2 &mul) +{ + vec3 tl = getItemTL(item); + vec2 scale; + obs_sceneitem_get_scale(item, &scale); + vec2_mul(&scale, &scale, &mul); + obs_sceneitem_set_scale(item, &scale); + obs_sceneitem_force_update_transform(item); + setItemTL(item, tl); +} + +static void centerAlign(obs_sceneitem_t *item, obs_bounds_type boundsType) +{ + obs_video_info ovi; + obs_get_video_info(&ovi); + + obs_transform_info itemInfo; + vec2_set(&itemInfo.pos, 0.0f, 0.0f); + vec2_set(&itemInfo.scale, 1.0f, 1.0f); + itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; + itemInfo.rot = 0.0f; + + vec2_set(&itemInfo.bounds, float(ovi.base_width), + float(ovi.base_height)); + itemInfo.bounds_type = boundsType; + itemInfo.bounds_alignment = OBS_ALIGN_CENTER; + + obs_sceneitem_set_info(item, &itemInfo); +} + +enum class CenterType { + SCREEN, + VERTICAL, + HORIZONTAL, +}; + +static void center(obs_sceneitem_t *item, const CenterType ¢erType) +{ + // Get center x, y coordinates of items + vec3 tl, br; + getItemBox(item, tl, br); + + float left = tl.x; + float top = tl.y; + float right = br.x; + float bottom = br.y; + + vec3 center; + center.x = (right + left) / 2.0f; + center.y = (top + bottom) / 2.0f; + center.z = 0.0f; + + // Get coordinates of screen center + obs_video_info ovi; + obs_get_video_info(&ovi); + + vec3 screenCenter; + vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), + 0.0f); + + vec3_mulf(&screenCenter, &screenCenter, 0.5f); + + // Calculate difference between screen center and item center + vec3 offset; + vec3_sub(&offset, &screenCenter, ¢er); + + // Shift item by offset + vec3_add(&tl, &tl, &offset); + + vec3 itemTL = getItemTL(item); + + if (centerType == CenterType::VERTICAL) { + tl.x = itemTL.x; + } else if (centerType == CenterType::HORIZONTAL) { + tl.y = itemTL.y; + } + + setItemTL(item, tl); +} + +/* ------------------------------------------------------------------------- */ + +void MacroActionSceneTransform::Transform(obs_scene_item *item) +{ + if (!item) { + return; + } + + switch (_action) { + case MacroActionSceneTransform::Action::RESET: + obs_sceneitem_defer_update_begin(item); + reset(item); + obs_sceneitem_defer_update_end(item); + + break; + case MacroActionSceneTransform::Action::ROTATE: + rotate(item, _rotation); + break; + case MacroActionSceneTransform::Action::FLIP_HORIZONTAL: { + vec2 scale; + vec2_set(&scale, -1.0f, 1.0f); + flip(item, scale); + break; + } + case MacroActionSceneTransform::Action::FLIP_VERTICAL: { + vec2 scale; + vec2_set(&scale, 1.0f, -1.0f); + flip(item, scale); + break; + } + case MacroActionSceneTransform::Action::FIT_TO_SCREEN: + centerAlign(item, OBS_BOUNDS_SCALE_INNER); + break; + case MacroActionSceneTransform::Action::STRETCH_TO_SCREEN: + centerAlign(item, OBS_BOUNDS_STRETCH); + break; + case MacroActionSceneTransform::Action::CENTER_TO_SCREEN: + center(item, CenterType::SCREEN); + break; + case MacroActionSceneTransform::Action::CENTER_VERTICALLY: + center(item, CenterType::VERTICAL); + break; + case MacroActionSceneTransform::Action::CENTER_HORIZONTALLY: + center(item, CenterType::HORIZONTAL); + break; + case MacroActionSceneTransform::Action::MANUAL_TRANSFORM: + obs_sceneitem_defer_update_begin(item); + obs_sceneitem_set_info(item, &_info); + obs_sceneitem_set_crop(item, &_crop); + obs_sceneitem_defer_update_end(item); + break; + } +} + bool MacroActionSceneTransform::PerformAction() { ApplySettings(_settings); // Resolves variables auto items = _source.GetSceneItems(_scene); for (auto &item : items) { - obs_sceneitem_defer_update_begin(item); - obs_sceneitem_set_info(item, &_info); - obs_sceneitem_set_crop(item, &_crop); - obs_sceneitem_defer_update_end(item); - + Transform(item); obs_sceneitem_release(item); } return true; @@ -30,15 +258,18 @@ bool MacroActionSceneTransform::PerformAction() void MacroActionSceneTransform::LogAction() const { vblog(LOG_INFO, - "performed transform action for source \"%s\" on scene \"%s\"", - _source.ToString(true).c_str(), _scene.ToString(true).c_str()); + "performed transform action %d for source \"%s\" on scene \"%s\"", + static_cast(_action), _source.ToString(true).c_str(), + _scene.ToString(true).c_str()); } bool MacroActionSceneTransform::Save(obs_data_t *obj) const { MacroAction::Save(obj); + obs_data_set_int(obj, "action", static_cast(_action)); _scene.Save(obj); _source.Save(obj); + _rotation.Save(obj, "rotation"); _settings.Save(obj, "settings"); return true; } @@ -53,8 +284,15 @@ bool MacroActionSceneTransform::Load(obs_data_t *obj) } MacroAction::Load(obj); + if (!obs_data_has_user_value(obj, "action")) { + _action = + Action::MANUAL_TRANSFORM; // TODO: Remove fallback in future + } else { + _action = static_cast(obs_data_get_int(obj, "action")); + } _scene.Load(obj); _source.Load(obj); + _rotation.Load(obj, "rotation"); _settings.Load(obj, "settings"); // Convert old data format @@ -113,15 +351,31 @@ void MacroActionSceneTransform::ApplySettings(const std::string &settings) obs_data_release(data); } +static inline void populateActionSelection(QComboBox *list) +{ + for (const auto &[type, name] : actions) { + list->addItem(obs_module_text(name.c_str()), + static_cast(type)); + } +} + MacroActionSceneTransformEdit::MacroActionSceneTransformEdit( QWidget *parent, std::shared_ptr entryData) - : QWidget(parent) + : QWidget(parent), + _scenes(new SceneSelectionWidget(window(), true, false, false, true)), + _sources(new SceneItemSelectionWidget(parent)), + _action(new QComboBox()), + _rotation(new VariableDoubleSpinBox()), + _getSettings(new QPushButton(obs_module_text( + "AdvSceneSwitcher.action.sceneTransform.getTransform"))), + _settings(new VariableTextEdit(this)), + _buttonLayout(new QHBoxLayout()) { - _scenes = new SceneSelectionWidget(window(), true, false, false, true); - _sources = new SceneItemSelectionWidget(parent); - _getSettings = new QPushButton(obs_module_text( - "AdvSceneSwitcher.action.sceneTransform.getTransform")); - _settings = new VariableTextEdit(this); + _rotation->setMinimum(-360); + _rotation->setMaximum(360); + _rotation->setSuffix("°"); + + populateActionSelection(_action); QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), this, SLOT(SceneChanged(const SceneSelection &))); @@ -130,6 +384,12 @@ MacroActionSceneTransformEdit::MacroActionSceneTransformEdit( QWidget::connect(_sources, SIGNAL(SceneItemChanged(const SceneItemSelection &)), this, SLOT(SourceChanged(const SceneItemSelection &))); + QWidget::connect(_action, SIGNAL(currentIndexChanged(int)), this, + SLOT(ActionChanged(int))); + QWidget::connect( + _rotation, + SIGNAL(NumberVariableChanged(const NumberVariable &)), + this, SLOT(RotationChanged(const NumberVariable &))); QWidget::connect(_getSettings, SIGNAL(clicked()), this, SLOT(GetSettingsClicked())); QWidget::connect(_settings, SIGNAL(textChanged()), this, @@ -138,23 +398,21 @@ MacroActionSceneTransformEdit::MacroActionSceneTransformEdit( QHBoxLayout *entryLayout = new QHBoxLayout; std::unordered_map widgetPlaceholders = { - {"{{scenes}}", _scenes}, - {"{{sources}}", _sources}, - {"{{settings}}", _settings}, - {"{{getSettings}}", _getSettings}, + {"{{scenes}}", _scenes}, {"{{rotation}}", _rotation}, + {"{{sources}}", _sources}, {"{{action}}", _action}, + {"{{settings}}", _settings}, {"{{getSettings}}", _getSettings}, }; PlaceWidgets( obs_module_text("AdvSceneSwitcher.action.sceneTransform.entry"), entryLayout, widgetPlaceholders); - QHBoxLayout *buttonLayout = new QHBoxLayout; - buttonLayout->addWidget(_getSettings); - buttonLayout->addStretch(); + _buttonLayout->addWidget(_getSettings); + _buttonLayout->addStretch(); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(entryLayout); mainLayout->addWidget(_settings); - mainLayout->addLayout(buttonLayout); + mainLayout->addLayout(_buttonLayout); setLayout(mainLayout); _entryData = entryData; @@ -170,10 +428,11 @@ void MacroActionSceneTransformEdit::UpdateEntryData() _scenes->SetScene(_entryData->_scene); _sources->SetSceneItem(_entryData->_source); + _action->setCurrentIndex( + _action->findData(static_cast(_entryData->_action))); + _rotation->SetValue(_entryData->_rotation); _settings->setPlainText(_entryData->_settings); - - adjustSize(); - updateGeometry(); + SetWidgetVisibility(); } void MacroActionSceneTransformEdit::SceneChanged(const SceneSelection &s) @@ -198,6 +457,28 @@ void MacroActionSceneTransformEdit::SourceChanged(const SceneItemSelection &item QString::fromStdString(_entryData->GetShortDesc())); } +void MacroActionSceneTransformEdit::ActionChanged(int idx) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_action = static_cast( + _action->itemData(idx).toInt()); + SetWidgetVisibility(); +} + +void MacroActionSceneTransformEdit::RotationChanged(const DoubleVariable &value) +{ + if (_loading || !_entryData) { + return; + } + + auto lock = LockContext(); + _entryData->_rotation = value; +} + void MacroActionSceneTransformEdit::GetSettingsClicked() { if (_loading || !_entryData || !_entryData->_scene.GetScene(false)) { @@ -229,4 +510,19 @@ void MacroActionSceneTransformEdit::SettingsChanged() updateGeometry(); } +void MacroActionSceneTransformEdit::SetWidgetVisibility() +{ + SetLayoutVisible( + _buttonLayout, + _entryData->_action == + MacroActionSceneTransform::Action::MANUAL_TRANSFORM); + _settings->setVisible( + _entryData->_action == + MacroActionSceneTransform::Action::MANUAL_TRANSFORM); + _rotation->setVisible(_entryData->_action == + MacroActionSceneTransform::Action::ROTATE); + adjustSize(); + updateGeometry(); +} + } // namespace advss diff --git a/src/macro-core/macro-action-scene-transform.hpp b/src/macro-core/macro-action-scene-transform.hpp index 557e6f94..c61bb83c 100644 --- a/src/macro-core/macro-action-scene-transform.hpp +++ b/src/macro-core/macro-action-scene-transform.hpp @@ -3,6 +3,7 @@ #include "scene-selection.hpp" #include "scene-item-selection.hpp" #include "variable-text-edit.hpp" +#include "variable-spinbox.hpp" #include @@ -22,11 +23,27 @@ public: return std::make_shared(m); } + enum class Action { + RESET, + ROTATE, + FLIP_HORIZONTAL, + FLIP_VERTICAL, + FIT_TO_SCREEN, + STRETCH_TO_SCREEN, + CENTER_TO_SCREEN, + CENTER_VERTICALLY, + CENTER_HORIZONTALLY, + MANUAL_TRANSFORM = 100, + }; + + Action _action = Action::RESET; SceneSelection _scene; SceneItemSelection _source; StringVariable _settings = ""; + DoubleVariable _rotation = 90.0; private: + void Transform(obs_scene_item *); void ApplySettings(const std::string &); std::string ConvertSettings(); @@ -57,19 +74,25 @@ public: private slots: void SceneChanged(const SceneSelection &); void SourceChanged(const SceneItemSelection &); + void ActionChanged(int); + void RotationChanged(const NumberVariable &value); void GetSettingsClicked(); void SettingsChanged(); signals: void HeaderInfoChanged(const QString &); -protected: +private: + void SetWidgetVisibility(); + SceneSelectionWidget *_scenes; SceneItemSelectionWidget *_sources; + QComboBox *_action; + VariableDoubleSpinBox *_rotation; QPushButton *_getSettings; VariableTextEdit *_settings; - std::shared_ptr _entryData; + QHBoxLayout *_buttonLayout; -private: + std::shared_ptr _entryData; bool _loading = true; };