diff --git a/UI/source-tree.cpp b/UI/source-tree.cpp index 72cd516f2..29b0c9c57 100644 --- a/UI/source-tree.cpp +++ b/UI/source-tree.cpp @@ -228,16 +228,10 @@ void SourceTreeItem::ExitEditMode(bool save) /* ----------------------------------------- */ /* check for existing source */ - bool exists = false; - - if (obs_sceneitem_is_group(sceneitem)) { - exists = !!obs_scene_get_group(scene, newName.c_str()); - } else { - obs_source_t *existingSource = - obs_get_source_by_name(newName.c_str()); - obs_source_release(existingSource); - exists = !!existingSource; - } + obs_source_t *existingSource = + obs_get_source_by_name(newName.c_str()); + obs_source_release(existingSource); + bool exists = !!existingSource; if (exists) { OBSMessageBox::information(main, @@ -550,11 +544,15 @@ void SourceTreeModel::ReorderItems() void SourceTreeModel::Add(obs_sceneitem_t *item) { - beginInsertRows(QModelIndex(), 0, 0); - items.insert(0, item); - endInsertRows(); + if (obs_sceneitem_is_group(item)) { + SceneChanged(); + } else { + beginInsertRows(QModelIndex(), 0, 0); + items.insert(0, item); + endInsertRows(); - st->UpdateWidget(createIndex(0, 0, nullptr), item); + st->UpdateWidget(createIndex(0, 0, nullptr), item); + } } void SourceTreeModel::Remove(obs_sceneitem_t *item) @@ -652,8 +650,8 @@ QString SourceTreeModel::GetNewGroupName() int i = 2; for (;;) { - obs_sceneitem_t *group = obs_scene_get_group(scene, - QT_TO_UTF8(name)); + obs_source_t *group = obs_get_source_by_name(QT_TO_UTF8(name)); + obs_source_release(group); if (!group) break; name = QTStr("Basic.Main.Group").arg(QString::number(i++)); diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index d23b8ae81..575f5677c 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -315,8 +315,14 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, SaveAudioDevice(AUX_AUDIO_2, 4, saveData, audioSources); SaveAudioDevice(AUX_AUDIO_3, 5, saveData, audioSources); + /* -------------------------------- */ + /* save non-group sources */ + auto FilterAudioSources = [&](obs_source_t *source) { + if (obs_source_is_group(source)) + return false; + return find(begin(audioSources), end(audioSources), source) == end(audioSources); }; @@ -328,6 +334,18 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, return (*static_cast(data))(source); }, static_cast(&FilterAudioSources)); + /* -------------------------------- */ + /* save group sources separately */ + + /* saving separately ensures they won't be loaded in older versions */ + obs_data_array_t *groupsArray = obs_save_sources_filtered( + [](void*, obs_source_t *source) + { + return obs_source_is_group(source); + }, nullptr); + + /* -------------------------------- */ + obs_source_t *transition = obs_get_output_source(0); obs_source_t *currentScene = obs_scene_get_source(scene); const char *sceneName = obs_source_get_name(currentScene); @@ -341,10 +359,12 @@ static obs_data_t *GenerateSaveData(obs_data_array_t *sceneOrder, obs_data_set_array(saveData, "scene_order", sceneOrder); obs_data_set_string(saveData, "name", sceneCollection); obs_data_set_array(saveData, "sources", sourcesArray); + obs_data_set_array(saveData, "groups", groupsArray); obs_data_set_array(saveData, "quick_transitions", quickTransitionData); obs_data_set_array(saveData, "transitions", transitions); obs_data_set_array(saveData, "saved_projectors", savedProjectorList); obs_data_array_release(sourcesArray); + obs_data_array_release(groupsArray); obs_data_set_string(saveData, "current_transition", obs_source_get_name(transition)); @@ -719,6 +739,7 @@ void OBSBasic::Load(const char *file) obs_data_array_t *sceneOrder = obs_data_get_array(data, "scene_order"); obs_data_array_t *sources = obs_data_get_array(data, "sources"); + obs_data_array_t *groups = obs_data_get_array(data, "groups"); obs_data_array_t *transitions= obs_data_get_array(data, "transitions"); const char *sceneName = obs_data_get_string(data, "current_scene"); @@ -759,6 +780,13 @@ void OBSBasic::Load(const char *file) LoadAudioDevice(AUX_AUDIO_2, 4, data); LoadAudioDevice(AUX_AUDIO_3, 5, data); + if (!sources) { + sources = groups; + groups = nullptr; + } else { + obs_data_array_push_back_array(sources, groups); + } + obs_load_sources(sources, nullptr, nullptr); if (transitions) @@ -803,6 +831,7 @@ retryScene: obs_source_release(curProgramScene); obs_data_array_release(sources); + obs_data_array_release(groups); obs_data_array_release(sceneOrder); /* ------------------- */ @@ -4043,8 +4072,9 @@ QMenu *OBSBasic::CreateAddSourcePopupMenu() popup->addSeparator(); QAction *addGroup = new QAction(QTStr("Group"), this); + addGroup->setData(QT_UTF8("group")); connect(addGroup, SIGNAL(triggered(bool)), - ui->sources, SLOT(AddGroup())); + this, SLOT(AddSourceFromAction())); popup->addAction(addGroup); if (!foundDeprecated) { diff --git a/UI/window-basic-source-select.cpp b/UI/window-basic-source-select.cpp index 72b355806..da81782c4 100644 --- a/UI/window-basic-source-select.cpp +++ b/UI/window-basic-source-select.cpp @@ -38,6 +38,25 @@ bool OBSBasicSourceSelect::EnumSources(void *data, obs_source_t *source) return true; } +bool OBSBasicSourceSelect::EnumGroups(void *data, obs_source_t *source) +{ + OBSBasicSourceSelect *window = static_cast(data); + const char *name = obs_source_get_name(source); + const char *id = obs_source_get_id(source); + + if (strcmp(id, window->id) == 0) { + OBSBasic *main = reinterpret_cast( + App()->GetMainWindow()); + OBSScene scene = main->GetCurrentScene(); + + obs_sceneitem_t *existing = obs_scene_get_group(scene, name); + if (!existing) + window->ui->sourceList->addItem(QT_UTF8(name)); + } + + return true; +} + void OBSBasicSourceSelect::OBSSourceAdded(void *data, calldata_t *calldata) { OBSBasicSourceSelect *window = static_cast(data); @@ -277,6 +296,8 @@ OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_) const char *name = obs_source_get_name(sceneSource); ui->sourceList->addItem(QT_UTF8(name)); } + } else if (strcmp(id_, "group") == 0) { + obs_enum_sources(EnumGroups, this); } else { obs_enum_sources(EnumSources, this); } diff --git a/UI/window-basic-source-select.hpp b/UI/window-basic-source-select.hpp index 7bab4768c..90496798c 100644 --- a/UI/window-basic-source-select.hpp +++ b/UI/window-basic-source-select.hpp @@ -32,6 +32,7 @@ private: const char *id; static bool EnumSources(void *data, obs_source_t *source); + static bool EnumGroups(void *data, obs_source_t *source); static void OBSSourceRemoved(void *data, calldata_t *calldata); static void OBSSourceAdded(void *data, calldata_t *calldata); diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index e7c718fce..65707166a 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -20,7 +20,10 @@ #include "graphics/math-defs.h" #include "obs-scene.h" +const struct obs_source_info group_info; + static void resize_group(obs_sceneitem_t *group); +static void resize_scene(obs_scene_t *scene); static void signal_parent(obs_scene_t *parent, const char *name, calldata_t *params); static void get_ungrouped_transform(obs_sceneitem_t *group, @@ -67,12 +70,25 @@ static const char *scene_getname(void *unused) return "Scene"; } +static const char *group_getname(void *unused) +{ + UNUSED_PARAMETER(unused); + return "Group"; +} + static void *scene_create(obs_data_t *settings, struct obs_source *source) { pthread_mutexattr_t attr; struct obs_scene *scene = bzalloc(sizeof(struct obs_scene)); scene->source = source; + if (source->info.id == group_info.id) { + scene->is_group = true; + scene->custom_size = true; + scene->cx = 0; + scene->cy = 0; + } + signal_handler_add_array(obs_source_get_signal_handler(source), obs_scene_signals); @@ -637,26 +653,17 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data) obs_source_t *source; const char *scale_filter_str; struct obs_scene_item *item; - bool is_group; bool visible; bool lock; if (obs_data_get_bool(item_data, "group_item_backup")) return; - is_group = obs_data_get_bool(item_data, "is_group"); - - if (is_group) { - obs_scene_t *sub_scene = obs_scene_create_private(name); - source = sub_scene->source; - - } else { - source = obs_get_source_by_name(name); - if (!source) { - blog(LOG_WARNING, "[scene_load_item] Source %s not " - "found!", name); - return; - } + source = obs_get_source_by_name(name); + if (!source) { + blog(LOG_WARNING, "[scene_load_item] Source %s not " + "found!", name); + return; } item = obs_scene_add(scene, source); @@ -669,10 +676,7 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data) return; } - if (is_group) { - item->is_group = true; - ((obs_scene_t *)source->context.data)->is_group = true; - } + item->is_group = source->info.id == group_info.id; obs_data_set_default_int(item_data, "align", OBS_ALIGN_TOP | OBS_ALIGN_LEFT); @@ -708,13 +712,6 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data) item->crop.right = (uint32_t)obs_data_get_int(item_data, "crop_right"); item->crop.bottom = (uint32_t)obs_data_get_int(item_data, "crop_bottom"); - if (item->is_group) { - obs_data_t *group_data = obs_data_get_obj(item_data, - "group_source"); - scene_load(source->context.data, group_data); - obs_data_release(group_data); - } - scale_filter_str = obs_data_get_string(item_data, "scale_filter"); item->scale_filter = OBS_SCALE_DISABLE; @@ -808,16 +805,12 @@ static void scene_save_item(obs_data_array_t *array, obs_data_set_int (item_data, "crop_right", (int)item->crop.right); obs_data_set_int (item_data, "crop_bottom", (int)item->crop.bottom); obs_data_set_int (item_data, "id", item->id); - obs_data_set_bool (item_data, "is_group", item->is_group); obs_data_set_bool (item_data, "group_item_backup", !!backup_group); if (item->is_group) { - obs_data_t *group_data = obs_data_create(); obs_scene_t *group_scene = item->source->context.data; obs_sceneitem_t *group_item; - scene_save(group_scene, group_data); - /* save group items as part of main scene, but ignored. * causes an automatic ungroup if scene collection file * is loaded in previous versions. */ @@ -830,9 +823,6 @@ static void scene_save_item(obs_data_array_t *array, } full_unlock(group_scene); - - obs_data_set_obj(item_data, "group_source", group_data); - obs_data_release(group_data); } if (item->scale_filter == OBS_SCALE_POINT) @@ -1125,6 +1115,27 @@ const struct obs_source_info scene_info = .enum_all_sources = scene_enum_all_sources }; +const struct obs_source_info group_info = +{ + .id = "group", + .type = OBS_SOURCE_TYPE_SCENE, + .output_flags = OBS_SOURCE_VIDEO | + OBS_SOURCE_CUSTOM_DRAW | + OBS_SOURCE_COMPOSITE, + .get_name = group_getname, + .create = scene_create, + .destroy = scene_destroy, + .video_tick = scene_video_tick, + .video_render = scene_video_render, + .audio_render = scene_audio_render, + .get_width = scene_getwidth, + .get_height = scene_getheight, + .load = scene_load, + .save = scene_save, + .enum_active_sources = scene_enum_active_sources, + .enum_all_sources = scene_enum_all_sources +}; + static inline obs_scene_t *create_id(const char *id, const char *name) { struct obs_source *source = obs_source_create(id, name, NULL, @@ -1505,8 +1516,7 @@ static inline bool source_has_audio(obs_source_t *source) } static obs_sceneitem_t *obs_scene_add_internal(obs_scene_t *scene, - obs_source_t *source, obs_sceneitem_t *insert_after, - bool is_group) + obs_source_t *source, obs_sceneitem_t *insert_after) { struct obs_scene_item *last; struct obs_scene_item *item; @@ -1546,7 +1556,7 @@ static obs_sceneitem_t *obs_scene_add_internal(obs_scene_t *scene, item->actions_mutex = mutex; item->user_visible = true; item->locked = false; - item->is_group = is_group; + item->is_group = source->info.id == group_info.id; item->private_settings = obs_data_create(); os_atomic_set_long(&item->active_refs, 1); vec2_set(&item->scale, 1.0f, 1.0f); @@ -1602,8 +1612,7 @@ static obs_sceneitem_t *obs_scene_add_internal(obs_scene_t *scene, obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source) { - obs_sceneitem_t *item = obs_scene_add_internal(scene, source, NULL, - false); + obs_sceneitem_t *item = obs_scene_add_internal(scene, source, NULL); struct calldata params; uint8_t stack[128]; @@ -2425,12 +2434,11 @@ obs_sceneitem_t *obs_scene_insert_group(obs_scene_t *scene, return NULL; } - obs_scene_t *sub_scene = create_private_id("scene", name); + obs_scene_t *sub_scene = create_id("group", name); obs_sceneitem_t *last_item = items ? items[count - 1] : NULL; obs_sceneitem_t *item = obs_scene_add_internal( - scene, sub_scene->source, last_item, true); - sub_scene->custom_size = true; + scene, sub_scene->source, last_item); obs_scene_release(sub_scene); @@ -2531,6 +2539,8 @@ void obs_sceneitem_group_ungroup(obs_sceneitem_t *item) last = last->next; } subscene->first_item = NULL; + subscene->cx = 0; + subscene->cy = 0; full_unlock(subscene); /* ------------------------- */ @@ -2822,10 +2832,7 @@ obs_sceneitem_t *obs_sceneitem_get_group(obs_scene_t *scene, bool obs_source_is_group(const obs_source_t *source) { - if (!source || source->info.id != scene_info.id) - return false; - - return ((obs_scene_t *)source->context.data)->is_group; + return source && source->info.id == group_info.id; } bool obs_scene_is_group(const obs_scene_t *scene) @@ -2840,7 +2847,7 @@ void obs_sceneitem_group_enum_items(obs_sceneitem_t *group, if (!group || !group->is_group) return; - obs_scene_t *scene = obs_scene_from_source(group->source); + obs_scene_t *scene = group->source->context.data; if (scene) obs_scene_enum_items(scene, callback, param); } diff --git a/libobs/obs.c b/libobs/obs.c index f835b05a1..ef4db62b1 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -741,6 +741,7 @@ static inline void obs_free_hotkeys(void) } extern const struct obs_source_info scene_info; +extern const struct obs_source_info group_info; extern void log_system_info(void); @@ -771,6 +772,7 @@ static bool obs_init(const char *locale, const char *module_config_path, obs->module_config_path = bstrdup(module_config_path); obs->locale = bstrdup(locale); obs_register_source(&scene_info); + obs_register_source(&group_info); add_default_module_paths(); return true; } @@ -1304,10 +1306,14 @@ void obs_enum_sources(bool (*enum_proc)(void*, obs_source_t*), void *param) obs_source_t *next_source = (obs_source_t*)source->context.next; - if ((source->info.type == OBS_SOURCE_TYPE_INPUT) != 0 && - !source->context.private && - !enum_proc(param, source)) + if (source->info.id == group_info.id && + !enum_proc(param, source)) { break; + } else if (source->info.type == OBS_SOURCE_TYPE_INPUT && + !source->context.private && + !enum_proc(param, source)) { + break; + } source = next_source; }