diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 6a5a17fc4..2ac4236e0 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -925,8 +925,6 @@ void OBSBasic::LogScenes() void OBSBasic::Load(const char *file) { - disableSaving++; - obs_data_t *data = obs_data_create_from_json_file_safe(file, "bak"); if (!data) { disableSaving--; @@ -7024,8 +7022,23 @@ void OBSBasic::on_actionCopyTransform_triggered() ui->actionPasteTransform->setEnabled(true); } +void undo_redo(const std::string &data) +{ + obs_data_t *dat = obs_data_create_from_json(data.c_str()); + obs_source_t *source = + obs_get_source_by_name(obs_data_get_string(dat, "scene_name")); + reinterpret_cast(App()->GetMainWindow()) + ->SetCurrentScene(source); + obs_source_release(source); + obs_data_release(dat); + + obs_scene_load_transform_states(data.c_str()); +} + void OBSBasic::on_actionPasteTransform_triggered() { + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); auto func = [](obs_scene_t *scene, obs_sceneitem_t *item, void *param) { if (!obs_sceneitem_selected(item)) return true; @@ -7041,6 +7054,19 @@ void OBSBasic::on_actionPasteTransform_triggered() }; obs_scene_enum_items(GetCurrentScene(), func, nullptr); + + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action( + QTStr("Undo.Transform.Paste") + .arg(obs_source_get_name(GetCurrentSceneSource())), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } static bool reset_tr(obs_scene_t *scene, obs_sceneitem_t *item, void *param) @@ -7076,6 +7102,22 @@ static bool reset_tr(obs_scene_t *scene, obs_sceneitem_t *item, void *param) void OBSBasic::on_actionResetTransform_triggered() { + obs_scene_t *scene = GetCurrentScene(); + + obs_data_t *wrapper = obs_scene_save_transform_states(scene, false); + obs_scene_enum_items(scene, reset_tr, nullptr); + obs_data_t *rwrapper = obs_scene_save_transform_states(scene, false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action( + QTStr("Undo.Transform.Reset") + .arg(obs_source_get_name(obs_scene_get_source(scene))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); + obs_scene_enum_items(GetCurrentScene(), reset_tr, nullptr); } @@ -7153,19 +7195,61 @@ static bool RotateSelectedSources(obs_scene_t *scene, obs_sceneitem_t *item, void OBSBasic::on_actionRotate90CW_triggered() { float f90CW = 90.0f; + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.Rotate") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } void OBSBasic::on_actionRotate90CCW_triggered() { float f90CCW = -90.0f; + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.Rotate") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } void OBSBasic::on_actionRotate180_triggered() { float f180 = 180.0f; + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.Rotate") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } static bool MultiplySelectedItemScale(obs_scene_t *scene, obs_sceneitem_t *item, @@ -7200,16 +7284,44 @@ void OBSBasic::on_actionFlipHorizontal_triggered() { vec2 scale; vec2_set(&scale, -1.0f, 1.0f); + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.HFlip") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } void OBSBasic::on_actionFlipVertical_triggered() { vec2 scale; vec2_set(&scale, 1.0f, -1.0f); + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.VFlip") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item, @@ -7249,15 +7361,43 @@ static bool CenterAlignSelectedItems(obs_scene_t *scene, obs_sceneitem_t *item, void OBSBasic::on_actionFitToScreen_triggered() { obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.FitToScreen") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } void OBSBasic::on_actionStretchToScreen_triggered() { obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.StretchToScreen") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } enum class CenterType { @@ -7318,19 +7458,61 @@ static bool center_to_scene(obs_scene_t *, obs_sceneitem_t *item, void *param) void OBSBasic::on_actionCenterToScreen_triggered() { CenterType centerType = CenterType::Scene; + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), center_to_scene, ¢erType); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.Center") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } void OBSBasic::on_actionVerticalCenter_triggered() { CenterType centerType = CenterType::Vertical; + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), center_to_scene, ¢erType); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.VCenter") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } void OBSBasic::on_actionHorizontalCenter_triggered() { CenterType centerType = CenterType::Horizontal; + obs_data_t *wrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); obs_scene_enum_items(GetCurrentScene(), center_to_scene, ¢erType); + obs_data_t *rwrapper = + obs_scene_save_transform_states(GetCurrentScene(), false); + + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + undo_s.add_action(QTStr("Undo.Transform.VCenter") + .arg(obs_source_get_name(obs_scene_get_source( + GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); + obs_data_release(rwrapper); } void OBSBasic::EnablePreviewDisplay(bool enable) @@ -7421,6 +7603,46 @@ void OBSBasic::Nudge(int dist, MoveDir dir) break; } + if (!recent_nudge) { + recent_nudge = true; + obs_data_t *wrapper = obs_scene_save_transform_states( + GetCurrentScene(), true); + std::string undo_data(obs_data_get_json(wrapper)); + + nudge_timer = new QTimer; + QObject::connect( + nudge_timer, &QTimer::timeout, + [this, &recent_nudge = recent_nudge, undo_data]() { + obs_data_t *rwrapper = + obs_scene_save_transform_states( + GetCurrentScene(), true); + std::string redo_data( + obs_data_get_json(rwrapper)); + + undo_s.add_action( + QTStr("Undo.Transform") + .arg(obs_source_get_name( + GetCurrentSceneSource())), + undo_redo, undo_redo, undo_data, + redo_data, NULL); + + recent_nudge = false; + obs_data_release(rwrapper); + }); + connect(nudge_timer, &QTimer::timeout, nudge_timer, + &QTimer::deleteLater); + nudge_timer->setSingleShot(true); + + obs_data_release(wrapper); + } + + if (nudge_timer) { + nudge_timer->stop(); + nudge_timer->start(1000); + } else { + blog(LOG_ERROR, "No nudge timer!"); + } + obs_scene_enum_items(GetCurrentScene(), nudge_callback, &offset); } diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 40fd716ce..d47a77d9d 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -157,6 +157,7 @@ class OBSBasic : public OBSMainWindow { friend class OBSBasicPreview; friend class OBSBasicStatusBar; friend class OBSBasicSourceSelect; + friend class OBSBasicTransform; friend class OBSBasicSettings; friend class Auth; friend class AutoConfig; @@ -222,6 +223,9 @@ private: QPointer cpuUsageTimer; QPointer diskFullTimer; + QPointer nudge_timer; + bool recent_nudge = false; + os_cpu_usage_info_t *cpuUsageInfo = nullptr; OBSService service; diff --git a/UI/window-basic-preview.cpp b/UI/window-basic-preview.cpp index 29e4d7152..ac03f3850 100644 --- a/UI/window-basic-preview.cpp +++ b/UI/window-basic-preview.cpp @@ -36,6 +36,9 @@ OBSBasicPreview::~OBSBasicPreview() gs_vertexbuffer_destroy(rectFill); obs_leave_graphics(); + + if (wrapper) + obs_data_release(wrapper); } vec2 OBSBasicPreview::GetMouseEventPos(QMouseEvent *event) @@ -581,6 +584,11 @@ void OBSBasicPreview::mousePressEvent(QMouseEvent *event) vec2_zero(&lastMoveOffset); mousePos = startPos; + if (wrapper) + obs_data_release(wrapper); + wrapper = + obs_scene_save_transform_states(main->GetCurrentScene(), true); + changed = false; } void OBSBasicPreview::UpdateCursor(uint32_t &flags) @@ -713,6 +721,41 @@ void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event) hoveredPreviewItems.push_back(item); selectedItems.clear(); } + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + obs_data_t *rwrapper = + obs_scene_save_transform_states(main->GetCurrentScene(), false); + + auto undo_redo = [](const std::string &data) { + obs_data_t *dat = obs_data_create_from_json(data.c_str()); + obs_source_t *source = obs_get_source_by_name( + obs_data_get_string(dat, "scene_name")); + reinterpret_cast(App()->GetMainWindow()) + ->SetCurrentScene(source); + obs_source_release(source); + obs_data_release(dat); + + obs_scene_load_transform_states(data.c_str()); + }; + + if (wrapper && rwrapper) { + std::string undo_data(obs_data_get_json(wrapper)); + std::string redo_data(obs_data_get_json(rwrapper)); + if (changed && undo_data.compare(redo_data) != 0) + main->undo_s.add_action( + QTStr("Undo.Transform") + .arg(obs_source_get_name( + main->GetCurrentSceneSource())), + undo_redo, undo_redo, undo_data, redo_data, + NULL); + } + + if (wrapper) + obs_data_release(wrapper); + + if (rwrapper) + obs_data_release(rwrapper); + + wrapper = NULL; } struct SelectedItemBounds { @@ -1434,6 +1477,8 @@ void OBSBasicPreview::StretchItem(const vec2 &pos) void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event) { + changed = true; + if (scrollMode && event->buttons() == Qt::LeftButton) { scrollingOffset.x += event->x() - scrollingFrom.x; scrollingOffset.y += event->y() - scrollingFrom.y; diff --git a/UI/window-basic-preview.hpp b/UI/window-basic-preview.hpp index 01386b8d9..c273a1e6c 100644 --- a/UI/window-basic-preview.hpp +++ b/UI/window-basic-preview.hpp @@ -106,6 +106,9 @@ private: void ProcessClick(const vec2 &pos); + obs_data_t *wrapper = NULL; + bool changed; + public: OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags = Qt::WindowFlags()); diff --git a/UI/window-basic-transform.cpp b/UI/window-basic-transform.cpp index eaf0594cd..4eb38cb18 100644 --- a/UI/window-basic-transform.cpp +++ b/UI/window-basic-transform.cpp @@ -73,10 +73,42 @@ OBSBasicTransform::OBSBasicTransform(OBSBasic *parent) SetScene(scene); SetItem(item); + obs_data_t *wrapper = obs_scene_save_transform_states(scene, false); + undo_data = std::string(obs_data_get_json(wrapper)); + + obs_data_release(wrapper); + channelChangedSignal.Connect(obs_get_signal_handler(), "channel_change", OBSChannelChanged, this); } +OBSBasicTransform::~OBSBasicTransform() +{ + obs_data_t *wrapper = + obs_scene_save_transform_states(main->GetCurrentScene(), false); + + auto undo_redo = [](const std::string &data) { + obs_data_t *dat = obs_data_create_from_json(data.c_str()); + obs_source_t *source = obs_get_source_by_name( + obs_data_get_string(dat, "scene_name")); + reinterpret_cast(App()->GetMainWindow()) + ->SetCurrentScene(source); + obs_source_release(source); + obs_data_release(dat); + obs_scene_load_transform_states(data.c_str()); + }; + + std::string redo_data(obs_data_get_json(wrapper)); + if (undo_data.compare(redo_data) != 0) + main->undo_s.add_action( + QTStr("Undo.Transform") + .arg(obs_source_get_name(obs_scene_get_source( + main->GetCurrentScene()))), + undo_redo, undo_redo, undo_data, redo_data, NULL); + + obs_data_release(wrapper); +} + void OBSBasicTransform::SetScene(OBSScene scene) { transformSignal.Disconnect(); diff --git a/UI/window-basic-transform.hpp b/UI/window-basic-transform.hpp index bc0528ded..65f01e041 100644 --- a/UI/window-basic-transform.hpp +++ b/UI/window-basic-transform.hpp @@ -21,6 +21,8 @@ private: OBSSignal selectSignal; OBSSignal deselectSignal; + std::string undo_data; + bool ignoreTransformSignal = false; bool ignoreItemChange = false; @@ -46,4 +48,5 @@ private slots: public: OBSBasicTransform(OBSBasic *parent); + ~OBSBasicTransform(); }; diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index 37eb40eea..1b07e88b3 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -1890,6 +1890,113 @@ static void signal_parent(obs_scene_t *parent, const char *command, signal_handler_signal(parent->source->context.signals, command, params); } +struct passthrough { + obs_data_array_t *ids; + bool all_items; +}; + +bool save_transform_states(obs_scene_t *scene, obs_sceneitem_t *item, + void *vp_pass) +{ + struct passthrough *pass = (struct passthrough *)vp_pass; + if (obs_sceneitem_selected(item) || pass->all_items) { + obs_data_t *temp = obs_data_create(); + obs_data_array_t *item_ids = (obs_data_array_t *)pass->ids; + + struct obs_transform_info info; + struct obs_sceneitem_crop crop; + obs_sceneitem_get_info(item, &info); + obs_sceneitem_get_crop(item, &crop); + + struct vec2 pos = info.pos; + struct vec2 scale = info.scale; + float rot = info.rot; + uint32_t alignment = info.alignment; + uint32_t bounds_type = info.bounds_type; + uint32_t bounds_alignment = info.bounds_alignment; + struct vec2 bounds = info.bounds; + + obs_data_set_int(temp, "id", obs_sceneitem_get_id(item)); + obs_data_set_vec2(temp, "pos", &pos); + obs_data_set_vec2(temp, "scale", &scale); + obs_data_set_int(temp, "rot", rot); + obs_data_set_int(temp, "alignment", alignment); + obs_data_set_int(temp, "bounds_type", bounds_type); + obs_data_set_vec2(temp, "bounds", &bounds); + obs_data_set_int(temp, "bounds_alignment", bounds_alignment); + obs_data_set_int(temp, "top", crop.top); + obs_data_set_int(temp, "bottom", crop.bottom); + obs_data_set_int(temp, "left", crop.left); + obs_data_set_int(temp, "right", crop.right); + + obs_data_array_push_back(item_ids, temp); + + obs_data_release(temp); + } + + UNUSED_PARAMETER(scene); + return true; +} + +obs_data_t *obs_scene_save_transform_states(obs_scene_t *scene, bool all_items) +{ + obs_data_t *wrapper = obs_data_create(); + obs_data_array_t *item_ids = obs_data_array_create(); + struct passthrough pass = {item_ids, all_items}; + + obs_scene_enum_items(scene, save_transform_states, (void *)&pass); + obs_data_set_array(wrapper, "item_ids", item_ids); + obs_data_set_string(wrapper, "scene_name", + obs_source_get_name(obs_scene_get_source(scene))); + + obs_data_array_release(item_ids); + + return wrapper; +} + +void load_transform_states(obs_data_t *temp, void *vp_scene) +{ + obs_scene_t *scene = (obs_scene_t *)vp_scene; + int64_t id = obs_data_get_int(temp, "id"); + obs_sceneitem_t *item = obs_scene_find_sceneitem_by_id(scene, id); + + struct obs_transform_info info; + struct obs_sceneitem_crop crop; + obs_data_get_vec2(temp, "pos", &info.pos); + obs_data_get_vec2(temp, "scale", &info.scale); + info.rot = obs_data_get_int(temp, "rot"); + info.alignment = obs_data_get_int(temp, "alignment"); + info.bounds_type = + (enum obs_bounds_type)obs_data_get_int(temp, "bounds_type"); + info.bounds_alignment = obs_data_get_int(temp, "bounds_alignment"); + obs_data_get_vec2(temp, "bounds", &info.bounds); + crop.top = obs_data_get_int(temp, "top"); + crop.bottom = obs_data_get_int(temp, "bottom"); + crop.left = obs_data_get_int(temp, "left"); + crop.right = obs_data_get_int(temp, "right"); + + obs_sceneitem_defer_update_begin(item); + + obs_sceneitem_set_info(item, &info); + obs_sceneitem_set_crop(item, &crop); + + obs_sceneitem_defer_update_end(item); +} + +void obs_scene_load_transform_states(const char *data) +{ + obs_data_t *dat = obs_data_create_from_json(data); + obs_data_array_t *item_ids = obs_data_get_array(dat, "item_ids"); + obs_source_t *source = + obs_get_source_by_name(obs_data_get_string(dat, "scene_name")); + obs_scene_t *scene = obs_scene_from_source(source); + obs_data_array_enum(item_ids, load_transform_states, (void *)scene); + + obs_data_release(dat); + obs_data_array_release(item_ids); + obs_source_release(source); +} + void obs_sceneitem_select(obs_sceneitem_t *item, bool select) { struct calldata params; diff --git a/libobs/obs.h b/libobs/obs.h index fd7f88e5f..3c245dd7d 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -1598,6 +1598,14 @@ EXPORT void obs_sceneitem_set_id(obs_sceneitem_t *sceneitem, int64_t id); /** Tries to find the sceneitem of the source in a given scene. Returns NULL if not found */ EXPORT obs_sceneitem_t *obs_scene_sceneitem_from_source(obs_scene_t *scene, obs_source_t *source); + +/** Save all the transform states for a current scene's sceneitems */ +EXPORT obs_data_t *obs_scene_save_transform_states(obs_scene_t *scene, + bool all_items); + +/** Load all the transform states of sceneitems in that scene */ +EXPORT void obs_scene_load_transform_states(const char *state); + /** Gets a sceneitem's order in its scene */ EXPORT int obs_sceneitem_get_order_position(obs_sceneitem_t *item);