diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 4478e2b5c..503025e90 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -256,7 +256,8 @@ set(obs_SOURCES obs-proxy-style.cpp locked-checkbox.cpp visibility-checkbox.cpp - media-slider.cpp) + media-slider.cpp + undo-stack-obs.cpp) set(obs_HEADERS ${obs_PLATFORM_HEADERS} @@ -327,7 +328,8 @@ set(obs_HEADERS log-viewer.hpp obs-proxy-style.hpp obs-proxy-style.hpp - media-slider.hpp) + media-slider.hpp + undo-stack-obs.hpp) set(obs_importers_HEADERS importers/importers.hpp) diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index 5556d2691..261e2d39e 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -586,6 +586,9 @@ Paste.Filters + + + @@ -599,6 +602,7 @@ + @@ -2034,7 +2038,23 @@ Basic.MainMenu.View.ContextBar - + + + false + + + Undo + + + + + false + + + Redo + + + OBSBasicPreview diff --git a/UI/source-tree.cpp b/UI/source-tree.cpp index f7dd79ce2..cf2229531 100644 --- a/UI/source-tree.cpp +++ b/UI/source-tree.cpp @@ -403,6 +403,34 @@ void SourceTreeItem::ExitEditMode(bool save) /* rename */ SignalBlocker sourcesSignalBlocker(this); + std::string prevName(obs_source_get_name(source)); + std::string scene_name = + obs_source_get_name(main->GetCurrentSceneSource()); + auto undo = [scene_name, prevName, main](const std::string &data) { + obs_source_t *source = obs_get_source_by_name(data.c_str()); + obs_source_set_name(source, prevName.c_str()); + obs_source_release(source); + + obs_source_t *scene_source = + obs_get_source_by_name(scene_name.c_str()); + main->SetCurrentScene(scene_source); + obs_source_release(scene_source); + }; + + auto redo = [scene_name, main, newName](const std::string &data) { + obs_source_t *source = obs_get_source_by_name(data.c_str()); + obs_source_set_name(source, newName.c_str()); + obs_source_release(source); + + obs_source_t *scene_source = + obs_get_source_by_name(scene_name.c_str()); + main->SetCurrentScene(scene_source); + obs_source_release(scene_source); + }; + + main->undo_s.add_action(QTStr("Undo.Rename").arg(newName.c_str()), undo, + redo, newName, prevName, NULL); + obs_source_set_name(source, newName.c_str()); label->setText(QT_UTF8(newName.c_str())); } diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index af3a84842..6a5a17fc4 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -17,7 +17,10 @@ along with this program. If not, see . ******************************************************************************/ +#include #include +#include +#include #include #include #include @@ -58,6 +61,7 @@ #include "remote-text.hpp" #include "ui-validation.hpp" #include "media-controls.hpp" +#include "undo-stack-obs.hpp" #include #include @@ -203,7 +207,7 @@ extern void RegisterTwitchAuth(); extern void RegisterRestreamAuth(); OBSBasic::OBSBasic(QWidget *parent) - : OBSMainWindow(parent), ui(new Ui::OBSBasic) + : OBSMainWindow(parent), undo_s(ui), ui(new Ui::OBSBasic) { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) qRegisterMetaTypeStreamOperators>( @@ -366,6 +370,16 @@ OBSBasic::OBSBasic(QWidget *parent) assignDockToggle(ui->controlsDock, ui->toggleControls); assignDockToggle(statsDock, ui->toggleStats); + // Register shortcuts for Undo/Redo + ui->actionMainUndo->setShortcut(Qt::CTRL + Qt::Key_Z); + QList shrt; + shrt << QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Z) + << QKeySequence(Qt::CTRL + Qt::Key_Y); + ui->actionMainRedo->setShortcuts(shrt); + + ui->actionMainUndo->setShortcutContext(Qt::ApplicationShortcut); + ui->actionMainRedo->setShortcutContext(Qt::ApplicationShortcut); + //hide all docking panes ui->toggleScenes->setChecked(false); ui->toggleSources->setChecked(false); @@ -3622,6 +3636,33 @@ void OBSBasic::DuplicateSelectedScene() OBS_SCENE_DUP_REFS); source = obs_scene_get_source(scene); SetCurrentScene(source, true); + + auto undo = [](const std::string &data) { + obs_source_t *source = + obs_get_source_by_name(data.c_str()); + obs_source_remove(source); + obs_source_release(source); + }; + + auto redo = [this, name](const std::string &data) { + obs_source_t *source = + obs_get_source_by_name(data.c_str()); + obs_scene_t *scene = obs_scene_from_source(source); + obs_source_release(source); + scene = obs_scene_duplicate(scene, name.c_str(), + OBS_SCENE_DUP_REFS); + source = obs_scene_get_source(scene); + SetCurrentScene(source, true); + obs_scene_release(scene); + }; + + undo_s.add_action( + QTStr("Undo.Scene.Duplicate") + .arg(obs_source_get_name(source)), + undo, redo, obs_source_get_name(source), + obs_source_get_name(obs_scene_get_source(curScene)), + NULL); + obs_scene_release(scene); break; @@ -3631,15 +3672,107 @@ void OBSBasic::DuplicateSelectedScene() void OBSBasic::RemoveSelectedScene() { OBSScene scene = GetCurrentScene(); - if (scene) { - obs_source_t *source = obs_scene_get_source(scene); - if (QueryRemoveSource(source)) { - obs_source_remove(source); + obs_source_t *source = obs_scene_get_source(scene); - if (api) - api->on_event( - OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); - } + OBSSource curProgramScene = OBSGetStrongRef(programScene); + + if (source && QueryRemoveSource(source)) { + vector item_ids; + obs_data_t *wrapper = obs_save_source(source); + obs_data_array_t *arr = obs_data_array_create(); + struct wrap { + obs_data_array_t *arr; + vector &items; + }; + + wrap passthrough = {arr, item_ids}; + obs_scene_enum_items( + scene, + [](obs_scene_t *, obs_sceneitem_t *item, + void *vp_wrap) { + wrap *passthrough = (wrap *)vp_wrap; + passthrough->items.push_back(obs_source_get_name( + obs_sceneitem_get_source(item))); + obs_data_array_t *arr = passthrough->arr; + obs_sceneitem_save(item, arr); + obs_source_addref( + obs_sceneitem_get_source(item)); + return true; + }, + (void *)&passthrough); + obs_data_array_t *list_order = SaveSceneListOrder(); + obs_data_set_array(wrapper, "arr", arr); + obs_data_set_array(wrapper, "list_order", list_order); + obs_data_set_string(wrapper, "name", + obs_source_get_name(source)); + + auto d = [item_ids](bool remove_ref) { + for (auto &item : item_ids) { + obs_source_t *source = + obs_get_source_by_name(item.c_str()); + blog(LOG_INFO, "%s", item.c_str()); + if (remove_ref) { + obs_source_release(source); + obs_source_release(source); + } + } + }; + + auto undo = [this, d](const std::string &data) { + obs_data_t *dat = + obs_data_create_from_json(data.c_str()); + obs_source_release(obs_load_source(dat)); + obs_data_array_t *arr = obs_data_get_array(dat, "arr"); + obs_data_array_t *list_order = + obs_data_get_array(dat, "list_order"); + const char *sname = obs_data_get_string(dat, "name"); + obs_source_t *source = obs_get_source_by_name(sname); + obs_scene_t *scene = obs_scene_from_source(source); + + obs_sceneitems_add(scene, arr); + LoadSceneListOrder(list_order); + SetCurrentScene(source); + + obs_data_release(dat); + obs_data_array_release(arr); + obs_data_array_release(list_order); + obs_source_release(source); + + d(true); + }; + obs_data_t *rwrapper = obs_data_create(); + obs_data_set_string(rwrapper, "name", + obs_source_get_name(source)); + auto redo = [d](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, "name")); + obs_source_remove(source); + obs_source_release(source); + obs_data_release(dat); + + d(false); + }; + + std::string undo_data = obs_data_get_json(wrapper); + std::string redo_data = obs_data_get_json(wrapper); + undo_s.add_action( + QTStr("Undo.Delete").arg(obs_source_get_name(source)), + undo, redo, undo_data, redo_data, [d](bool undo) { + if (undo) { + d(true); + } + }); + + obs_source_remove(source); + obs_data_release(wrapper); + obs_data_release(rwrapper); + obs_data_array_release(arr); + obs_data_array_release(list_order); + + if (api) + api->on_event(OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED); } } @@ -4330,6 +4463,7 @@ void OBSBasic::closeEvent(QCloseEvent *event) /* Clear all scene data (dialogs, widgets, widget sub-items, scenes, * sources, etc) so that all references are released before shutdown */ + undo_s.release(); ClearSceneData(); App()->quit(); @@ -4686,20 +4820,34 @@ void OBSBasic::on_actionAddScene_triggered() return; } + auto undo_fn = [](const std::string &data) { + obs_source_t *t = obs_get_source_by_name(data.c_str()); + if (t) { + obs_source_release(t); + obs_source_remove(t); + } + }; + + auto redo_fn = [this](const std::string &data) { + obs_scene_t *scene = obs_scene_create(data.c_str()); + obs_source_t *source = obs_scene_get_source(scene); + SetCurrentScene(source); + obs_scene_release(scene); + }; + undo_s.add_action(QTStr("Undo.Add").arg(QString(name.c_str())), + undo_fn, redo_fn, name, name, NULL); + obs_scene_t *scene = obs_scene_create(name.c_str()); source = obs_scene_get_source(scene); SetCurrentScene(source); + RefreshSources(scene); obs_scene_release(scene); } } void OBSBasic::on_actionRemoveScene_triggered() { - OBSScene scene = GetCurrentScene(); - obs_source_t *source = obs_scene_get_source(scene); - - if (source && QueryRemoveSource(source)) - obs_source_remove(source); + RemoveSelectedScene(); } void OBSBasic::ChangeSceneIndex(bool relative, int offset, int invalidIdx) @@ -5109,10 +5257,11 @@ void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem) void OBSBasic::AddSource(const char *id) { if (id && *id) { - OBSBasicSourceSelect sourceSelect(this, id); + OBSBasicSourceSelect sourceSelect(this, id, undo_s); sourceSelect.exec(); - if (sourceSelect.newSource && strcmp(id, "group") != 0) + if (sourceSelect.newSource && strcmp(id, "group") != 0) { CreatePropertiesWindow(sourceSelect.newSource); + } } } @@ -5253,9 +5402,11 @@ void OBSBasic::on_actionRemoveSource_triggered() if (!items.size()) return; - auto removeMultiple = [this](size_t count) { + bool confirmed = false; + + if (items.size() > 1) { QString text = QTStr("ConfirmRemove.TextMultiple") - .arg(QString::number(count)); + .arg(QString::number(items.size())); QMessageBox remove_items(this); remove_items.setText(text); @@ -5267,21 +5418,139 @@ void OBSBasic::on_actionRemoveSource_triggered() remove_items.setWindowTitle(QTStr("ConfirmRemove.Title")); remove_items.exec(); - return Yes == remove_items.clickedButton(); - }; - - if (items.size() == 1) { + confirmed = remove_items.clickedButton(); + } else { OBSSceneItem &item = items[0]; obs_source_t *source = obs_sceneitem_get_source(item); - if (source && QueryRemoveSource(source)) - obs_sceneitem_remove(item); - } else { - if (removeMultiple(items.size())) { - for (auto &item : items) - obs_sceneitem_remove(item); - } + confirmed = true; } + if (!confirmed) + return; + + struct source_save { + std::string name; + std::string scene_name; + int pos; + bool in_group = false; + int64_t group_id; + }; + vector item_save; + + obs_data_t *wrapper = obs_data_create(); + obs_data_array_t *data = obs_data_array_create(); + for (const auto &item : items) { + obs_sceneitem_save(item, data); + obs_source_t *source = obs_sceneitem_get_source(item); + obs_source_addref(source); + obs_source_set_hidden(source, true); + + obs_sceneitem_t *grp = + obs_sceneitem_get_group(GetCurrentScene(), item); + obs_scene_t *scene = obs_sceneitem_get_scene(item); + source_save save = { + obs_source_get_name(source), + obs_source_get_name(obs_scene_get_source(scene)), + obs_sceneitem_get_order_position(item), + grp ? true : false, obs_sceneitem_get_id(grp)}; + + item_save.push_back(save); + } + + obs_scene_t *scene = GetCurrentScene(); + const char *name = obs_source_get_name(obs_scene_get_source(scene)); + obs_data_set_array(wrapper, "data_array", data); + obs_data_set_string(wrapper, "name", name); + std::string undo_data(obs_data_get_json(wrapper)); + + auto undo_fn = [this, item_save](const std::string &data) { + obs_data_t *dat = obs_data_create_from_json(data.c_str()); + obs_data_array_t *sources_data = + obs_data_get_array(dat, "data_array"); + const char *name = obs_data_get_string(dat, "name"); + obs_source_t *src = obs_get_source_by_name(name); + obs_scene_t *scene = obs_scene_from_source(src); + + obs_sceneitems_add(scene, sources_data); + SetCurrentScene(scene); + + for (const auto &save : item_save) { + obs_source_t *source = + obs_get_source_by_name(save.name.c_str()); + obs_source_set_hidden(source, false); + if (save.in_group) { + obs_sceneitem_t *grp = + obs_scene_find_sceneitem_by_id( + scene, save.group_id); + obs_sceneitem_t *item = + obs_scene_sceneitem_from_source(scene, + source); + obs_sceneitem_group_add_item(grp, item); + obs_sceneitem_set_order_position(item, + save.pos); + + obs_sceneitem_release(item); + } + + obs_source_release(source); + obs_source_release(source); + } + + obs_source_release(src); + obs_data_array_release(sources_data); + obs_data_release(dat); + }; + + auto redo_fn = [item_save](const std::string &) { + for (const auto &save : item_save) { + obs_source_t *source = + obs_get_source_by_name(save.name.c_str()); + obs_source_t *scene_source = + obs_get_source_by_name(save.scene_name.c_str()); + obs_scene_t *scene = + obs_scene_from_source(scene_source); + if (!scene) + scene = obs_group_from_source(scene_source); + + obs_sceneitem_t *item = + obs_scene_sceneitem_from_source(scene, source); + obs_sceneitem_remove(item); + obs_source_set_hidden(source, true); + + obs_sceneitem_release(item); + obs_source_release(scene_source); + /* usually want to release source, but redo needs to add a reference to keep alive */ + } + }; + + auto d = [item_save](bool is_undo) { + if (!is_undo) + return; + + for (const auto &item : item_save) { + obs_source_t *source = + obs_get_source_by_name(item.name.c_str()); + obs_source_release(source); + obs_source_release(source); + } + }; + + QString action_name; + if (items.size() > 1) + action_name = QTStr("Undo.Sources.Multi") + .arg(QString::number(items.size())); + else + action_name = + QTStr("Undo.Delete") + .arg(QString(obs_source_get_name( + obs_sceneitem_get_source(items[0])))); + undo_s.add_action(action_name, undo_fn, redo_fn, undo_data, "", d); + + obs_data_array_release(data); + obs_data_release(wrapper); + + for (auto &item : items) + obs_sceneitem_remove(item); } void OBSBasic::on_actionInteract_triggered() @@ -5519,6 +5788,27 @@ static void RenameListItem(OBSBasic *parent, QListWidget *listWidget, obs_source_release(foundSource); } else { + auto undo = [prev = std::string(prevName)]( + const std::string &data) { + obs_source_t *source = + obs_get_source_by_name(data.c_str()); + obs_source_set_name(source, prev.c_str()); + obs_source_release(source); + }; + + auto redo = [name](const std::string &data) { + obs_source_t *source = + obs_get_source_by_name(data.c_str()); + obs_source_set_name(source, name.c_str()); + obs_source_release(source); + }; + + std::string undo_data(name); + std::string redo_data(prevName); + parent->undo_s.add_action( + QTStr("Undo.Rename").arg(name.c_str()), undo, redo, + undo_data, redo_data, NULL); + listItem->setText(QT_UTF8(name.c_str())); obs_source_set_name(source, name.c_str()); } @@ -7762,6 +8052,16 @@ bool OBSBasic::sysTrayMinimizeToTray() "SysTrayMinimizeToTray"); } +void OBSBasic::on_actionMainUndo_triggered() +{ + undo_s.undo(); +} + +void OBSBasic::on_actionMainRedo_triggered() +{ + undo_s.redo(); +} + void OBSBasic::on_actionCopySource_triggered() { copyStrings.clear(); diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index 0ee5ec96b..40fd716ce 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -36,6 +36,7 @@ #include "window-basic-about.hpp" #include "auth-base.hpp" #include "log-viewer.hpp" +#include "undo-stack-obs.hpp" #include @@ -166,6 +167,7 @@ class OBSBasic : public OBSMainWindow { friend class ExtraBrowsersDelegate; friend class DeviceCaptureToolbar; friend class DeviceToolbarPropertiesThread; + friend class OBSBasicSourceSelect; friend struct BasicOutputHandler; friend struct OBSStudioAPI; @@ -608,6 +610,10 @@ public slots: void UnpauseRecording(); private slots: + + void on_actionMainUndo_triggered(); + void on_actionMainRedo_triggered(); + void AddSceneItem(OBSSceneItem item); void AddScene(OBSSource source); void RemoveScene(OBSSource source); @@ -740,6 +746,7 @@ private: OBSSource prevFTBSource = nullptr; public: + undo_stack undo_s; OBSSource GetProgramSource(); OBSScene GetCurrentScene(); diff --git a/UI/window-basic-source-select.cpp b/UI/window-basic-source-select.cpp index 57b7b121f..cee30e520 100644 --- a/UI/window-basic-source-select.cpp +++ b/UI/window-basic-source-select.cpp @@ -28,6 +28,9 @@ struct AddSourceData { bool OBSBasicSourceSelect::EnumSources(void *data, obs_source_t *source) { + if (obs_source_is_hidden(source)) + return false; + OBSBasicSourceSelect *window = static_cast(data); const char *name = obs_source_get_name(source); @@ -179,7 +182,7 @@ bool AddNew(QWidget *parent, const char *id, const char *name, return false; obs_source_t *source = obs_get_source_by_name(name); - if (source) { + if (source && parent) { OBSMessageBox::information(parent, QTStr("NameExists.Title"), QTStr("NameExists.Text")); @@ -236,6 +239,63 @@ void OBSBasicSourceSelect::on_buttonBox_accepted() if (!AddNew(this, id, QT_TO_UTF8(ui->sourceName->text()), visible, newSource)) return; + + OBSBasic *main = + reinterpret_cast(App()->GetMainWindow()); + std::string scene_name = + obs_source_get_name(main->GetCurrentSceneSource()); + auto undo = [scene_name, main](const std::string &data) { + obs_source_t *source = + obs_get_source_by_name(data.c_str()); + obs_source_release(source); + obs_source_remove(source); + + obs_source_t *scene_source = + obs_get_source_by_name(scene_name.c_str()); + main->SetCurrentScene(scene_source); + obs_source_release(scene_source); + + main->RefreshSources(main->GetCurrentScene()); + }; + obs_data_t *wrapper = obs_data_create(); + obs_data_set_string(wrapper, "id", id); + obs_sceneitem_t *item = obs_scene_sceneitem_from_source( + main->GetCurrentScene(), newSource); + obs_data_set_int(wrapper, "item_id", + obs_sceneitem_get_id(item)); + obs_data_set_string( + wrapper, "name", + ui->sourceName->text().toUtf8().constData()); + obs_data_set_bool(wrapper, "visible", visible); + + auto redo = [scene_name, main](const std::string &data) { + obs_data_t *dat = + obs_data_create_from_json(data.c_str()); + OBSSource source; + AddNew(NULL, obs_data_get_string(dat, "id"), + obs_data_get_string(dat, "name"), + obs_data_get_bool(dat, "visible"), source); + obs_sceneitem_t *item = obs_scene_sceneitem_from_source( + main->GetCurrentScene(), source); + obs_sceneitem_set_id(item, (int64_t)obs_data_get_int( + dat, "item_id")); + + obs_source_t *scene_source = + obs_get_source_by_name(scene_name.c_str()); + main->SetCurrentScene(scene_source); + obs_source_release(scene_source); + + main->RefreshSources(main->GetCurrentScene()); + obs_data_release(dat); + obs_sceneitem_release(item); + }; + undo_s.add_action(QTStr("Undo.Add").arg(ui->sourceName->text()), + undo, redo, + std::string(obs_source_get_name(newSource)), + std::string(obs_data_get_json(wrapper)), + NULL); + obs_data_release(wrapper); + obs_sceneitem_release(item); } done(DialogCode::Accepted); @@ -261,8 +321,12 @@ template static inline T GetOBSRef(QListWidgetItem *item) return item->data(static_cast(QtDataRole::OBSRef)).value(); } -OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_) - : QDialog(parent), ui(new Ui::OBSBasicSourceSelect), id(id_) +OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_, + undo_stack &undo_s) + : QDialog(parent), + ui(new Ui::OBSBasicSourceSelect), + id(id_), + undo_s(undo_s) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); diff --git a/UI/window-basic-source-select.hpp b/UI/window-basic-source-select.hpp index 90496798c..089dc60f3 100644 --- a/UI/window-basic-source-select.hpp +++ b/UI/window-basic-source-select.hpp @@ -21,6 +21,7 @@ #include #include "ui_OBSBasicSourceSelect.h" +#include "undo-stack-obs.hpp" class OBSBasic; @@ -30,6 +31,7 @@ class OBSBasicSourceSelect : public QDialog { private: std::unique_ptr ui; const char *id; + undo_stack &undo_s; static bool EnumSources(void *data, obs_source_t *source); static bool EnumGroups(void *data, obs_source_t *source); @@ -45,7 +47,8 @@ private slots: void SourceRemoved(OBSSource source); public: - OBSBasicSourceSelect(OBSBasic *parent, const char *id); + OBSBasicSourceSelect(OBSBasic *parent, const char *id, + undo_stack &undo_s); OBSSource newSource; diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 142c01235..83037fc25 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -621,6 +621,9 @@ struct obs_source { * to handle things but it's the best option) */ bool removed; + /* used to indicate if the source should show up when queried for user ui */ + bool temp_removed; + bool active; bool showing; diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index 049a71d40..37eb40eea 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -1477,6 +1477,33 @@ obs_sceneitem_t *obs_scene_find_source_recursive(obs_scene_t *scene, return item; } +struct sceneitem_check { + obs_source_t *source_in; + obs_sceneitem_t *item_out; +}; + +bool check_sceneitem_exists(obs_scene_t *scene, obs_sceneitem_t *item, + void *vp_check) +{ + UNUSED_PARAMETER(scene); + struct sceneitem_check *check = (struct sceneitem_check *)vp_check; + if (obs_sceneitem_get_source(item) == check->source_in) { + check->item_out = item; + obs_sceneitem_addref(item); + return false; + } + + return true; +} + +obs_sceneitem_t *obs_scene_sceneitem_from_source(obs_scene_t *scene, + obs_source_t *source) +{ + struct sceneitem_check check = {source, NULL}; + obs_scene_enum_items(scene, check_sceneitem_exists, (void *)&check); + return check.item_out; +} + obs_sceneitem_t *obs_scene_find_sceneitem_by_id(obs_scene_t *scene, int64_t id) { struct obs_scene_item *item; @@ -1830,6 +1857,22 @@ void obs_sceneitem_remove(obs_sceneitem_t *item) obs_sceneitem_release(item); } +void obs_sceneitem_save(obs_sceneitem_t *item, obs_data_array_t *arr) +{ + scene_save_item(arr, item, NULL); +} + +void sceneitem_restore(obs_data_t *data, void *vp) +{ + obs_scene_t *scene = (obs_scene_t *)vp; + scene_load_item(scene, data); +} + +void obs_sceneitems_add(obs_scene_t *scene, obs_data_array_t *data) +{ + obs_data_array_enum(data, sceneitem_restore, scene); +} + obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item) { return item ? item->parent : NULL; @@ -1977,6 +2020,24 @@ void obs_sceneitem_set_order(obs_sceneitem_t *item, obs_scene_release(scene); } +int obs_sceneitem_get_order_position(obs_sceneitem_t *item) +{ + struct obs_scene *scene = item->parent; + struct obs_scene_item *next = scene->first_item; + + full_lock(scene); + + int index = 0; + while (next && next != item) { + next = next->next; + ++index; + } + + full_unlock(scene); + + return index; +} + void obs_sceneitem_set_order_position(obs_sceneitem_t *item, int position) { if (!item) @@ -2385,6 +2446,11 @@ int64_t obs_sceneitem_get_id(const obs_sceneitem_t *item) return item->id; } +void obs_sceneitem_set_id(obs_sceneitem_t *item, int64_t id) +{ + item->id = id; +} + obs_data_t *obs_sceneitem_get_private_settings(obs_sceneitem_t *item) { if (!obs_ptr_valid(item, "obs_sceneitem_get_private_settings")) diff --git a/libobs/obs-source.c b/libobs/obs-source.c index e8c33be23..d148d17f2 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -929,8 +929,9 @@ void obs_source_update(obs_source_t *source, obs_data_t *settings) if (!obs_source_valid(source, "obs_source_update")) return; - if (settings) + if (settings) { obs_data_apply(source->context.settings, settings); + } if (source->info.output_flags & OBS_SOURCE_VIDEO) { os_atomic_inc_long(&source->defer_update_count); @@ -4283,6 +4284,16 @@ void obs_source_enum_filters(obs_source_t *source, pthread_mutex_unlock(&source->filter_mutex); } +void obs_source_set_hidden(obs_source_t *source, bool hidden) +{ + source->temp_removed = hidden; +} + +bool obs_source_is_hidden(obs_source_t *source) +{ + return source->temp_removed; +} + obs_source_t *obs_source_get_filter_by_name(obs_source_t *source, const char *name) { diff --git a/libobs/obs.h b/libobs/obs.h index dd2a69fde..fd7f88e5f 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -898,6 +898,14 @@ EXPORT void obs_source_remove(obs_source_t *source); /** Returns true if the source should be released */ EXPORT bool obs_source_removed(const obs_source_t *source); +/** The 'hidden' flag is not the same as a sceneitem's visibility. It is a + * property the determines if it can be found through searches. **/ +/** Simply sets a 'hidden' flag when the source is still alive but shouldn't be found */ +EXPORT void obs_source_set_hidden(obs_source_t *source, bool hidden); + +/** Returns the current 'hidden' state on the source */ +EXPORT bool obs_source_is_hidden(obs_source_t *source); + /** Returns capability flags of a source */ EXPORT uint32_t obs_source_get_output_flags(const obs_source_t *source); @@ -1578,6 +1586,21 @@ EXPORT void obs_sceneitem_release(obs_sceneitem_t *item); /** Removes a scene item. */ EXPORT void obs_sceneitem_remove(obs_sceneitem_t *item); +/** Adds a scene item. */ +EXPORT void obs_sceneitems_add(obs_scene_t *scene, obs_data_array_t *data); + +/** Saves Sceneitem into an array, arr **/ +EXPORT void obs_sceneitem_save(obs_sceneitem_t *item, obs_data_array_t *arr); + +/** Set the ID of a sceneitem */ +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); +/** Gets a sceneitem's order in its scene */ +EXPORT int obs_sceneitem_get_order_position(obs_sceneitem_t *item); + /** Gets the scene parent associated with the scene item. */ EXPORT obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item);