#include "macro-action-scene-transform.hpp" #include "json-helpers.hpp" #include "scene-item-transform-helpers.hpp" #include "layout-helpers.hpp" #include namespace advss { const std::string MacroActionSceneTransform::id = "scene_transform"; bool MacroActionSceneTransform::_registered = MacroActionFactory::Register( MacroActionSceneTransform::id, {MacroActionSceneTransform::Create, 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: ApplySettings(_settings); // Resolves variables 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() { auto items = _source.GetSceneItems(_scene); for (const auto &item : items) { Transform(item); } return true; } void MacroActionSceneTransform::LogAction() const { ablog(LOG_INFO, "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; } bool MacroActionSceneTransform::Load(obs_data_t *obj) { // Convert old data format // TODO: Remove in future version if (obs_data_has_user_value(obj, "source")) { auto sourceName = obs_data_get_string(obj, "source"); obs_data_set_string(obj, "sceneItem", sourceName); } 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 // TODO: Remove in future version if (!obs_data_has_user_value(obj, "settings")) { LoadTransformState(obj, _info, _crop); _settings = ConvertSettings(); } return true; } std::string MacroActionSceneTransform::GetShortDesc() const { if (_source.ToString().empty()) { return ""; } return _scene.ToString() + " - " + _source.ToString(); } std::shared_ptr MacroActionSceneTransform::Create(Macro *m) { return std::make_shared(m); } std::shared_ptr MacroActionSceneTransform::Copy() const { return std::make_shared(*this); } void MacroActionSceneTransform::ResolveVariablesToFixedValues() { _scene.ResolveVariables(); _source.ResolveVariables(); _settings.ResolveVariables(); _rotation.ResolveVariables(); } std::string MacroActionSceneTransform::ConvertSettings() { auto data = obs_data_create(); SaveTransformState(data, _info, _crop); std::string json = obs_data_get_json(data); obs_data_release(data); return json; } void MacroActionSceneTransform::ApplySettings(const std::string &settings) { auto data = obs_data_create_from_json(settings.c_str()); if (!data) { return; } LoadTransformState(data, _info, _crop); auto items = _source.GetSceneItems(_scene); if (items.empty()) { return; } if (obs_data_has_user_value(data, "size")) { auto obj = obs_data_get_obj(data, "size"); auto source = obs_sceneitem_get_source(items[0]); if (double h = obs_data_get_double(obj, "height")) { _info.scale.y = h / double(obs_source_get_height(source)); } if (double w = obs_data_get_double(obj, "width")) { _info.scale.x = w / double(obs_source_get_width(source)); } obs_data_release(obj); } 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), _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()) { _rotation->setMinimum(-360); _rotation->setMaximum(360); _rotation->setSuffix("°"); populateActionSelection(_action); QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), this, SLOT(SceneChanged(const SceneSelection &))); QWidget::connect(_scenes, SIGNAL(SceneChanged(const SceneSelection &)), _sources, SLOT(SceneChanged(const SceneSelection &))); 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, SLOT(SettingsChanged())); QHBoxLayout *entryLayout = new QHBoxLayout; std::unordered_map widgetPlaceholders = { {"{{scenes}}", _scenes}, {"{{rotation}}", _rotation}, {"{{sources}}", _sources}, {"{{action}}", _action}, {"{{settings}}", _settings}, {"{{getSettings}}", _getSettings}, }; PlaceWidgets( obs_module_text("AdvSceneSwitcher.action.sceneTransform.entry"), entryLayout, widgetPlaceholders); _buttonLayout->addWidget(_getSettings); _buttonLayout->addStretch(); QVBoxLayout *mainLayout = new QVBoxLayout; mainLayout->addLayout(entryLayout); mainLayout->addWidget(_settings); mainLayout->addLayout(_buttonLayout); setLayout(mainLayout); _entryData = entryData; UpdateEntryData(); _loading = false; } void MacroActionSceneTransformEdit::UpdateEntryData() { if (!_entryData) { return; } _scenes->SetScene(_entryData->_scene); _sources->SetSceneItem(_entryData->_source); _action->setCurrentIndex( _action->findData(static_cast(_entryData->_action))); _rotation->SetValue(_entryData->_rotation); _settings->setPlainText(_entryData->_settings); SetWidgetVisibility(); } void MacroActionSceneTransformEdit::SceneChanged(const SceneSelection &s) { if (_loading || !_entryData) { return; } auto lock = LockContext(); _entryData->_scene = s; } void MacroActionSceneTransformEdit::SourceChanged(const SceneItemSelection &item) { if (_loading || !_entryData) { return; } auto lock = LockContext(); _entryData->_source = item; emit HeaderInfoChanged( QString::fromStdString(_entryData->GetShortDesc())); adjustSize(); updateGeometry(); } 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)) { return; } auto items = _entryData->_source.GetSceneItems(_entryData->_scene); if (items.empty()) { return; } auto settings = GetSceneItemTransform(items[0]); _settings->setPlainText(FormatJsonString(settings)); } void MacroActionSceneTransformEdit::SettingsChanged() { if (_loading || !_entryData) { return; } auto lock = LockContext(); _entryData->_settings = _settings->toPlainText().toStdString(); adjustSize(); 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