diff --git a/src/Utils.cpp b/src/Utils.cpp index 570e676d..ecd6039f 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -28,6 +28,25 @@ with this program. If not, see Q_DECLARE_METATYPE(OBSScene); +const QHash boundTypeNames = { + { OBS_BOUNDS_STRETCH, "OBS_BOUNDS_STRETCH" }, + { OBS_BOUNDS_SCALE_INNER, "OBS_BOUNDS_SCALE_INNER" }, + { OBS_BOUNDS_SCALE_OUTER, "OBS_BOUNDS_SCALE_OUTER" }, + { OBS_BOUNDS_SCALE_TO_WIDTH, "OBS_BOUNDS_SCALE_TO_WIDTH" }, + { OBS_BOUNDS_SCALE_TO_HEIGHT, "OBS_BOUNDS_SCALE_TO_HEIGHT" }, + { OBS_BOUNDS_MAX_ONLY, "OBS_BOUNDS_MAX_ONLY" }, + { OBS_BOUNDS_NONE, "OBS_BOUNDS_NONE" }, +}; + +QString getBoundsNameFromType(obs_bounds_type type) { + QString fallback = boundTypeNames.value(OBS_BOUNDS_NONE); + return boundTypeNames.value(type, fallback); +} + +obs_bounds_type getBoundsTypeFromName(QString name) { + return boundTypeNames.key(name); +} + obs_data_array_t* Utils::StringListToArray(char** strings, char* key) { if (!strings) return obs_data_array_create(); @@ -110,7 +129,7 @@ obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_source_t* source, obs_data_t* i if (obs_data_has_user_value(item, "name") && (QString)obs_source_get_name(obs_sceneitem_get_source(sceneItem)) != (QString)obs_data_get_string(item, "name")) { - return nullptr; + return nullptr; } } else if (obs_data_has_user_value(item, "name")) { @@ -553,3 +572,72 @@ bool Utils::SetFilenameFormatting(const char* filenameFormatting) { config_save(profile); return true; } + +// Transform properties copy-pasted from WSRequestHandler_SceneItems.cpp because typedefs can't be extended yet + +/** + * @typedef {Object} `SceneItemTransform` + * @property {int} `position.x` The x position of the scene item from the left. + * @property {int} `position.y` The y position of the scene item from the top. + * @property {int} `position.alignment` The point on the scene item that the item is manipulated from. + * @property {double} `rotation` The clockwise rotation of the scene item in degrees around the point of alignment. + * @property {double} `scale.x` The x-scale factor of the scene item. + * @property {double} `scale.y` The y-scale factor of the scene item. + * @property {int} `crop.top` The number of pixels cropped off the top of the scene item before scaling. + * @property {int} `crop.right` The number of pixels cropped off the right of the scene item before scaling. + * @property {int} `crop.bottom` The number of pixels cropped off the bottom of the scene item before scaling. + * @property {int} `crop.left` The number of pixels cropped off the left of the scene item before scaling. + * @property {bool} `visible` If the scene item is visible. + * @property {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". + * @property {int} `bounds.alignment` Alignment of the bounding box. + * @property {double} `bounds.x` Width of the bounding box. + * @property {double} `bounds.y` Height of the bounding box. + */ +obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) { + vec2 pos, scale, bounds; + obs_sceneitem_crop crop; + + obs_sceneitem_get_pos(sceneItem, &pos); + obs_sceneitem_get_scale(sceneItem, &scale); + obs_sceneitem_get_crop(sceneItem, &crop); + obs_sceneitem_get_bounds(sceneItem, &bounds); + + uint32_t alignment = obs_sceneitem_get_alignment(sceneItem); + float rotation = obs_sceneitem_get_rot(sceneItem); + bool isVisible = obs_sceneitem_visible(sceneItem); + + obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem); + uint32_t boundsAlignment = obs_sceneitem_get_bounds_alignment(sceneItem); + QString boundsTypeName = getBoundsNameFromType(boundsType); + + OBSDataAutoRelease posData = obs_data_create(); + obs_data_set_double(posData, "x", pos.x); + obs_data_set_double(posData, "y", pos.y); + obs_data_set_int(posData, "alignment", alignment); + + OBSDataAutoRelease scaleData = obs_data_create(); + obs_data_set_double(scaleData, "x", scale.x); + obs_data_set_double(scaleData, "y", scale.y); + + OBSDataAutoRelease cropData = obs_data_create(); + obs_data_set_int(cropData, "left", crop.left); + obs_data_set_int(cropData, "top", crop.top); + obs_data_set_int(cropData, "right", crop.right); + obs_data_set_int(cropData, "bottom", crop.bottom); + + OBSDataAutoRelease boundsData = obs_data_create(); + obs_data_set_string(boundsData, "type", boundsTypeName.toUtf8()); + obs_data_set_int(boundsData, "alignment", boundsAlignment); + obs_data_set_double(boundsData, "x", bounds.x); + obs_data_set_double(boundsData, "y", bounds.y); + + obs_data_t* data = obs_data_create(); + obs_data_set_obj(data, "position", posData); + obs_data_set_double(data, "rotation", rotation); + obs_data_set_obj(data, "scale", scaleData); + obs_data_set_obj(data, "crop", cropData); + obs_data_set_bool(data, "visible", isVisible); + obs_data_set_obj(data, "bounds", boundsData); + + return data; +} diff --git a/src/Utils.h b/src/Utils.h index 040f6d01..3c2d1b5b 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -41,6 +41,7 @@ class Utils { static obs_sceneitem_t* GetSceneItemFromItem(obs_source_t* source, obs_data_t* item); static obs_source_t* GetTransitionFromName(QString transitionName); static obs_source_t* GetSceneFromNameOrCurrent(QString sceneName); + static obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item); static bool IsValidAlignment(const uint32_t alignment); diff --git a/src/WSEvents.cpp b/src/WSEvents.cpp index 29aa8428..ca3d496d 100644 --- a/src/WSEvents.cpp +++ b/src/WSEvents.cpp @@ -256,6 +256,8 @@ void WSEvents::connectSceneSignals(obs_source_t* scene) { "item_remove", OnSceneItemDelete, this); signal_handler_disconnect(sh, "item_visible", OnSceneItemVisibilityChanged, this); + signal_handler_disconnect(sh, + "item_transform", OnSceneItemTransform, this); } currentScene = scene; @@ -271,6 +273,8 @@ void WSEvents::connectSceneSignals(obs_source_t* scene) { "item_remove", OnSceneItemDelete, this); signal_handler_connect(sh, "item_visible", OnSceneItemVisibilityChanged, this); + signal_handler_connect(sh, + "item_transform", OnSceneItemTransform, this); } } @@ -911,6 +915,42 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) { instance->broadcastUpdate("SceneItemVisibilityChanged", fields); } +/** + * An item's transfrom has been changed. + * + * @return {String} `scene-name` Name of the scene. + * @return {String} `item-name` Name of the item in the scene. + * @return {SceneItemProperties} `transform` Scene item transform properties + * + * @api events + * @name SceneItemTransformChanged + * @category sources + * @since unreleased + */ +void WSEvents::OnSceneItemTransform(void* param, calldata_t* data) { + WSEvents* instance = static_cast(param); + + obs_scene_t* scene = nullptr; + calldata_get_ptr(data, "scene", &scene); + + obs_sceneitem_t* sceneItem = nullptr; + calldata_get_ptr(data, "item", &sceneItem); + + const char* sceneName = + obs_source_get_name(obs_scene_get_source(scene)); + const char* sceneItemName = + obs_source_get_name(obs_sceneitem_get_source(sceneItem)); + + OBSDataAutoRelease transform = Utils::GetSceneItemPropertiesData(sceneItem); + + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_string(fields, "scene-name", sceneName); + obs_data_set_string(fields, "item-name", sceneItemName); + obs_data_set_obj(fields, "transform", transform); + + instance->broadcastUpdate("SceneItemTransformChanged", fields); +} + /** * The selected preview scene has changed (only available in Studio Mode). * diff --git a/src/WSEvents.h b/src/WSEvents.h index 48de519d..c9f370fd 100644 --- a/src/WSEvents.h +++ b/src/WSEvents.h @@ -116,4 +116,5 @@ private: static void OnSceneItemAdd(void* param, calldata_t* data); static void OnSceneItemDelete(void* param, calldata_t* data); static void OnSceneItemVisibilityChanged(void* param, calldata_t* data); + static void OnSceneItemTransform(void* param, calldata_t* data); }; diff --git a/src/WSRequestHandler_SceneItems.cpp b/src/WSRequestHandler_SceneItems.cpp index ddc8d336..6b19ae97 100644 --- a/src/WSRequestHandler_SceneItems.cpp +++ b/src/WSRequestHandler_SceneItems.cpp @@ -21,7 +21,7 @@ * @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling. * @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling. * @return {bool} `visible` If the source is visible. -* @return {String} `bounds.type` Type of bounding box. +* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". * @return {int} `bounds.alignment` Alignment of the bounding box. * @return {double} `bounds.x` Width of the bounding box. * @return {double} `bounds.y` Height of the bounding box. @@ -53,77 +53,9 @@ HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* return req->SendErrorResponse("specified scene item doesn't exist"); } - OBSDataAutoRelease data = obs_data_create(); + OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem); obs_data_set_string(data, "name", itemName.toUtf8()); - OBSDataAutoRelease posData = obs_data_create(); - vec2 pos; - obs_sceneitem_get_pos(sceneItem, &pos); - obs_data_set_double(posData, "x", pos.x); - obs_data_set_double(posData, "y", pos.y); - obs_data_set_int(posData, "alignment", obs_sceneitem_get_alignment(sceneItem)); - obs_data_set_obj(data, "position", posData); - - obs_data_set_double(data, "rotation", obs_sceneitem_get_rot(sceneItem)); - - OBSDataAutoRelease scaleData = obs_data_create(); - vec2 scale; - obs_sceneitem_get_scale(sceneItem, &scale); - obs_data_set_double(scaleData, "x", scale.x); - obs_data_set_double(scaleData, "y", scale.y); - obs_data_set_obj(data, "scale", scaleData); - - OBSDataAutoRelease cropData = obs_data_create(); - obs_sceneitem_crop crop; - obs_sceneitem_get_crop(sceneItem, &crop); - obs_data_set_int(cropData, "left", crop.left); - obs_data_set_int(cropData, "top", crop.top); - obs_data_set_int(cropData, "right", crop.right); - obs_data_set_int(cropData, "bottom", crop.bottom); - obs_data_set_obj(data, "crop", cropData); - - obs_data_set_bool(data, "visible", obs_sceneitem_visible(sceneItem)); - - OBSDataAutoRelease boundsData = obs_data_create(); - obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem); - if (boundsType == OBS_BOUNDS_NONE) { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_NONE"); - } - else { - switch (boundsType) { - case OBS_BOUNDS_STRETCH: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_STRETCH"); - break; - } - case OBS_BOUNDS_SCALE_INNER: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_INNER"); - break; - } - case OBS_BOUNDS_SCALE_OUTER: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_OUTER"); - break; - } - case OBS_BOUNDS_SCALE_TO_WIDTH: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_WIDTH"); - break; - } - case OBS_BOUNDS_SCALE_TO_HEIGHT: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_HEIGHT"); - break; - } - case OBS_BOUNDS_MAX_ONLY: { - obs_data_set_string(boundsData, "type", "OBS_BOUNDS_MAX_ONLY"); - break; - } - } - obs_data_set_int(boundsData, "alignment", obs_sceneitem_get_bounds_alignment(sceneItem)); - vec2 bounds; - obs_sceneitem_get_bounds(sceneItem, &bounds); - obs_data_set_double(boundsData, "x", bounds.x); - obs_data_set_double(boundsData, "y", bounds.y); - } - obs_data_set_obj(data, "bounds", boundsData); - return req->SendOKResponse(data); } @@ -143,7 +75,7 @@ HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* * @param {int} `crop.left` The new amount of pixels cropped off the left of the source before scaling. * @param {int} `crop.right` The new amount of pixels cropped off the right of the source before scaling. * @param {bool} `visible` The new visibility of the source. 'true' shows source, 'false' hides source. -* @param {String} `bounds.type` The new bounds type of the source. +* @param {String} `bounds.type` The new bounds type of the source. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". * @param {int} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10) * @param {double} `bounds.x` The new width of the bounding box. * @param {double} `bounds.y` The new height of the bounding box. @@ -306,7 +238,7 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* if (badRequest) { return req->SendErrorResponse(errorMessage); } - + return req->SendOKResponse(); } @@ -431,7 +363,7 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* r if (!sceneItem) { return req->SendErrorResponse("specified scene item doesn't exist"); } - + vec2 item_position = { 0 }; item_position.x = obs_data_get_double(req->data, "x"); item_position.y = obs_data_get_double(req->data, "y"); @@ -598,7 +530,7 @@ static void DuplicateSceneItem(void *_data, obs_scene_t *scene) { * @return {Object} `item` New item info * @return {int} `̀€item.id` New item ID * @return {String} `item.name` New item name - * + * * @api requests * @name DuplicateSceneItem * @category scene items