OBSBasic_SceneItems.cpp 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383
  1. /******************************************************************************
  2. Copyright (C) 2023 by Lain Bailey <[email protected]>
  3. Zachary Lund <[email protected]>
  4. Philippe Groarke <[email protected]>
  5. This program is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 2 of the License, or
  8. (at your option) any later version.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. ******************************************************************************/
  16. #include "OBSBasic.hpp"
  17. #include "ColorSelect.hpp"
  18. #include "OBSProjector.hpp"
  19. #include <components/VolumeControl.hpp>
  20. #include <dialogs/NameDialog.hpp>
  21. #include <dialogs/OBSBasicAdvAudio.hpp>
  22. #include <dialogs/OBSBasicSourceSelect.hpp>
  23. #include <utility/item-widget-helpers.hpp>
  24. #include <qt-wrappers.hpp>
  25. #include <QWidgetAction>
  26. #include <sstream>
  27. using namespace std;
  28. namespace {
  29. bool isHiddenInMixer(obs_source_t *source)
  30. {
  31. OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source);
  32. bool hidden = obs_data_get_bool(priv_settings, "mixer_hidden");
  33. return hidden;
  34. }
  35. void setHiddenInMixer(obs_source_t *source, bool hidden)
  36. {
  37. OBSDataAutoRelease priv_settings = obs_source_get_private_settings(source);
  38. obs_data_set_bool(priv_settings, "mixer_hidden", hidden);
  39. }
  40. std::string getNewSourceName(std::string_view name)
  41. {
  42. std::string newName{name};
  43. int suffix = 1;
  44. for (;;) {
  45. OBSSourceAutoRelease existing_source = obs_get_source_by_name(newName.c_str());
  46. if (!existing_source) {
  47. break;
  48. }
  49. char nextName[256];
  50. std::snprintf(nextName, sizeof(nextName), "%s (%d)", name.data(), ++suffix);
  51. newName = nextName;
  52. }
  53. return newName;
  54. }
  55. } // namespace
  56. static inline bool HasAudioDevices(const char *source_id)
  57. {
  58. const char *output_id = source_id;
  59. obs_properties_t *props = obs_get_source_properties(output_id);
  60. size_t count = 0;
  61. if (!props)
  62. return false;
  63. obs_property_t *devices = obs_properties_get(props, "device_id");
  64. if (devices)
  65. count = obs_property_list_item_count(devices);
  66. obs_properties_destroy(props);
  67. return count != 0;
  68. }
  69. void OBSBasic::CreateFirstRunSources()
  70. {
  71. bool hasDesktopAudio = HasAudioDevices(App()->OutputAudioSource());
  72. bool hasInputAudio = HasAudioDevices(App()->InputAudioSource());
  73. #ifdef __APPLE__
  74. /* On macOS 13 and above, the SCK based audio capture provides a
  75. * better alternative to the device-based audio capture. */
  76. if (__builtin_available(macOS 13.0, *)) {
  77. hasDesktopAudio = false;
  78. }
  79. #endif
  80. if (hasDesktopAudio)
  81. ResetAudioDevice(App()->OutputAudioSource(), "default", Str("Basic.DesktopDevice1"), 1);
  82. if (hasInputAudio)
  83. ResetAudioDevice(App()->InputAudioSource(), "default", Str("Basic.AuxDevice1"), 3);
  84. }
  85. OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item)
  86. {
  87. return item ? GetOBSRef<OBSSceneItem>(item) : nullptr;
  88. }
  89. OBSSceneItem OBSBasic::GetCurrentSceneItem()
  90. {
  91. return ui->sources->Get(GetTopSelectedSourceItem());
  92. }
  93. static void RenameListValues(QListWidget *listWidget, const QString &newName, const QString &prevName)
  94. {
  95. QList<QListWidgetItem *> items = listWidget->findItems(prevName, Qt::MatchExactly);
  96. for (int i = 0; i < items.count(); i++)
  97. items[i]->setText(newName);
  98. }
  99. void OBSBasic::RenameSources(OBSSource source, QString newName, QString prevName)
  100. {
  101. RenameListValues(ui->scenes, newName, prevName);
  102. if (vcamConfig.type == VCamOutputType::SourceOutput && prevName == QString::fromStdString(vcamConfig.source))
  103. vcamConfig.source = newName.toStdString();
  104. if (vcamConfig.type == VCamOutputType::SceneOutput && prevName == QString::fromStdString(vcamConfig.scene))
  105. vcamConfig.scene = newName.toStdString();
  106. SaveProject();
  107. obs_scene_t *scene = obs_scene_from_source(source);
  108. if (scene)
  109. OBSProjector::UpdateMultiviewProjectors();
  110. UpdateContextBar();
  111. UpdatePreviewProgramIndicators();
  112. }
  113. bool OBSBasic::QueryRemoveSource(obs_source_t *source)
  114. {
  115. if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE && !obs_source_is_group(source)) {
  116. int count = ui->scenes->count();
  117. if (count == 1) {
  118. OBSMessageBox::information(this, QTStr("FinalScene.Title"), QTStr("FinalScene.Text"));
  119. return false;
  120. }
  121. }
  122. const char *name = obs_source_get_name(source);
  123. QString text = QTStr("ConfirmRemove.Text").arg(QT_UTF8(name));
  124. QMessageBox remove_source(this);
  125. remove_source.setText(text);
  126. QPushButton *Yes = remove_source.addButton(QTStr("Yes"), QMessageBox::YesRole);
  127. remove_source.setDefaultButton(Yes);
  128. remove_source.addButton(QTStr("No"), QMessageBox::NoRole);
  129. remove_source.setIcon(QMessageBox::Question);
  130. remove_source.setWindowTitle(QTStr("ConfirmRemove.Title"));
  131. remove_source.exec();
  132. return Yes == remove_source.clickedButton();
  133. }
  134. void OBSBasic::ReorderSources(OBSScene scene)
  135. {
  136. if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
  137. return;
  138. ui->sources->ReorderItems();
  139. SaveProject();
  140. }
  141. void OBSBasic::RefreshSources(OBSScene scene)
  142. {
  143. if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
  144. return;
  145. ui->sources->RefreshItems();
  146. SaveProject();
  147. }
  148. void OBSBasic::SourceCreated(void *data, calldata_t *params)
  149. {
  150. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  151. if (obs_scene_from_source(source) != NULL)
  152. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "AddScene", WaitConnection(),
  153. Q_ARG(OBSSource, OBSSource(source)));
  154. }
  155. void OBSBasic::SourceRemoved(void *data, calldata_t *params)
  156. {
  157. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  158. if (obs_scene_from_source(source) != NULL)
  159. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "RemoveScene",
  160. Q_ARG(OBSSource, OBSSource(source)));
  161. }
  162. void OBSBasic::SourceRenamed(void *data, calldata_t *params)
  163. {
  164. obs_source_t *source = (obs_source_t *)calldata_ptr(params, "source");
  165. const char *newName = calldata_string(params, "new_name");
  166. const char *prevName = calldata_string(params, "prev_name");
  167. QMetaObject::invokeMethod(static_cast<OBSBasic *>(data), "RenameSources", Q_ARG(OBSSource, source),
  168. Q_ARG(QString, QT_UTF8(newName)), Q_ARG(QString, QT_UTF8(prevName)));
  169. blog(LOG_INFO, "Source '%s' renamed to '%s'", prevName, newName);
  170. }
  171. void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceId, const char *deviceDesc, int channel)
  172. {
  173. bool disable = deviceId && strcmp(deviceId, "disabled") == 0;
  174. OBSSourceAutoRelease source;
  175. OBSDataAutoRelease settings;
  176. source = obs_get_output_source(channel);
  177. if (source) {
  178. if (disable) {
  179. obs_set_output_source(channel, nullptr);
  180. } else {
  181. settings = obs_source_get_settings(source);
  182. const char *oldId = obs_data_get_string(settings, "device_id");
  183. if (strcmp(oldId, deviceId) != 0) {
  184. obs_data_set_string(settings, "device_id", deviceId);
  185. obs_source_update(source, settings);
  186. }
  187. }
  188. } else if (!disable) {
  189. std::string name = getNewSourceName(deviceDesc);
  190. settings = obs_data_create();
  191. obs_data_set_string(settings, "device_id", deviceId);
  192. source = obs_source_create(sourceId, name.c_str(), settings, nullptr);
  193. obs_set_output_source(channel, source);
  194. }
  195. }
  196. void OBSBasic::SetDeinterlacingMode()
  197. {
  198. QAction *action = reinterpret_cast<QAction *>(sender());
  199. obs_deinterlace_mode mode = (obs_deinterlace_mode)action->property("mode").toInt();
  200. OBSSceneItem sceneItem = GetCurrentSceneItem();
  201. obs_source_t *source = obs_sceneitem_get_source(sceneItem);
  202. obs_deinterlace_mode oldMode = obs_source_get_deinterlace_mode(source);
  203. obs_source_set_deinterlace_mode(source, mode);
  204. auto undo_redo = [](const std::string &uuid, obs_deinterlace_mode val) {
  205. OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str());
  206. if (source)
  207. obs_source_set_deinterlace_mode(source, val);
  208. };
  209. const char *uuid = obs_source_get_uuid(source);
  210. if (uuid && *uuid) {
  211. QString actionString = QTStr("Undo.DeinterlacingMode").arg(obs_source_get_name(source));
  212. auto undoFunction = std::bind(undo_redo, std::placeholders::_1, oldMode);
  213. auto redoFunction = std::bind(undo_redo, std::placeholders::_1, mode);
  214. undo_s.add_action(actionString, undoFunction, redoFunction, uuid, uuid);
  215. }
  216. }
  217. void OBSBasic::SetDeinterlacingOrder()
  218. {
  219. QAction *action = reinterpret_cast<QAction *>(sender());
  220. obs_deinterlace_field_order order = (obs_deinterlace_field_order)action->property("order").toInt();
  221. OBSSceneItem sceneItem = GetCurrentSceneItem();
  222. obs_source_t *source = obs_sceneitem_get_source(sceneItem);
  223. obs_deinterlace_field_order oldOrder = obs_source_get_deinterlace_field_order(source);
  224. obs_source_set_deinterlace_field_order(source, order);
  225. auto undo_redo = [](const std::string &uuid, obs_deinterlace_field_order val) {
  226. OBSSourceAutoRelease source = obs_get_source_by_uuid(uuid.c_str());
  227. if (source)
  228. obs_source_set_deinterlace_field_order(source, val);
  229. };
  230. const char *uuid = obs_source_get_uuid(source);
  231. if (uuid && *uuid) {
  232. QString actionString = QTStr("Undo.DeinterlacingOrder").arg(obs_source_get_name(source));
  233. auto undoFunction = std::bind(undo_redo, std::placeholders::_1, oldOrder);
  234. auto redoFunction = std::bind(undo_redo, std::placeholders::_1, order);
  235. undo_s.add_action(actionString, undoFunction, redoFunction, uuid, uuid);
  236. }
  237. }
  238. QMenu *OBSBasic::AddDeinterlacingMenu(QMenu *menu, obs_source_t *source)
  239. {
  240. obs_deinterlace_mode deinterlaceMode = obs_source_get_deinterlace_mode(source);
  241. obs_deinterlace_field_order deinterlaceOrder = obs_source_get_deinterlace_field_order(source);
  242. QAction *action;
  243. #define ADD_MODE(name, mode) \
  244. action = menu->addAction(QTStr("" name), this, &OBSBasic::SetDeinterlacingMode); \
  245. action->setProperty("mode", (int)mode); \
  246. action->setCheckable(true); \
  247. action->setChecked(deinterlaceMode == mode);
  248. ADD_MODE("Disable", OBS_DEINTERLACE_MODE_DISABLE);
  249. ADD_MODE("Deinterlacing.Discard", OBS_DEINTERLACE_MODE_DISCARD);
  250. ADD_MODE("Deinterlacing.Retro", OBS_DEINTERLACE_MODE_RETRO);
  251. ADD_MODE("Deinterlacing.Blend", OBS_DEINTERLACE_MODE_BLEND);
  252. ADD_MODE("Deinterlacing.Blend2x", OBS_DEINTERLACE_MODE_BLEND_2X);
  253. ADD_MODE("Deinterlacing.Linear", OBS_DEINTERLACE_MODE_LINEAR);
  254. ADD_MODE("Deinterlacing.Linear2x", OBS_DEINTERLACE_MODE_LINEAR_2X);
  255. ADD_MODE("Deinterlacing.Yadif", OBS_DEINTERLACE_MODE_YADIF);
  256. ADD_MODE("Deinterlacing.Yadif2x", OBS_DEINTERLACE_MODE_YADIF_2X);
  257. #undef ADD_MODE
  258. menu->addSeparator();
  259. #define ADD_ORDER(name, order) \
  260. action = menu->addAction(QTStr("Deinterlacing." name), this, &OBSBasic::SetDeinterlacingOrder); \
  261. action->setProperty("order", (int)order); \
  262. action->setCheckable(true); \
  263. action->setChecked(deinterlaceOrder == order);
  264. ADD_ORDER("TopFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_TOP);
  265. ADD_ORDER("BottomFieldFirst", OBS_DEINTERLACE_FIELD_ORDER_BOTTOM);
  266. #undef ADD_ORDER
  267. return menu;
  268. }
  269. void OBSBasic::SetScaleFilter()
  270. {
  271. QAction *action = reinterpret_cast<QAction *>(sender());
  272. obs_scale_type mode = (obs_scale_type)action->property("mode").toInt();
  273. OBSSceneItem sceneItem = GetCurrentSceneItem();
  274. obs_scale_type oldMode = obs_sceneitem_get_scale_filter(sceneItem);
  275. obs_sceneitem_set_scale_filter(sceneItem, mode);
  276. auto undo_redo = [](const std::string &uuid, int64_t id, obs_scale_type val) {
  277. OBSSourceAutoRelease s = obs_get_source_by_uuid(uuid.c_str());
  278. obs_scene_t *sc = obs_group_or_scene_from_source(s);
  279. obs_sceneitem_t *si = obs_scene_find_sceneitem_by_id(sc, id);
  280. if (si)
  281. obs_sceneitem_set_scale_filter(si, val);
  282. };
  283. OBSSource source = obs_sceneitem_get_source(sceneItem);
  284. OBSSource sceneSource = obs_scene_get_source(obs_sceneitem_get_scene(sceneItem));
  285. int64_t id = obs_sceneitem_get_id(sceneItem);
  286. const char *name = obs_source_get_name(sceneSource);
  287. const char *uuid = obs_source_get_uuid(sceneSource);
  288. if (uuid && *uuid) {
  289. QString actionString = QTStr("Undo.ScaleFiltering").arg(obs_source_get_name(source), name);
  290. auto undoFunction = std::bind(undo_redo, std::placeholders::_1, id, oldMode);
  291. auto redoFunction = std::bind(undo_redo, std::placeholders::_1, id, mode);
  292. undo_s.add_action(actionString, undoFunction, redoFunction, uuid, uuid);
  293. }
  294. }
  295. QMenu *OBSBasic::AddScaleFilteringMenu(QMenu *menu, obs_sceneitem_t *item)
  296. {
  297. obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(item);
  298. QAction *action;
  299. #define ADD_MODE(name, mode) \
  300. action = menu->addAction(QTStr("" name), this, &OBSBasic::SetScaleFilter); \
  301. action->setProperty("mode", (int)mode); \
  302. action->setCheckable(true); \
  303. action->setChecked(scaleFilter == mode);
  304. ADD_MODE("Disable", OBS_SCALE_DISABLE);
  305. ADD_MODE("ScaleFiltering.Point", OBS_SCALE_POINT);
  306. ADD_MODE("ScaleFiltering.Bilinear", OBS_SCALE_BILINEAR);
  307. ADD_MODE("ScaleFiltering.Bicubic", OBS_SCALE_BICUBIC);
  308. ADD_MODE("ScaleFiltering.Lanczos", OBS_SCALE_LANCZOS);
  309. ADD_MODE("ScaleFiltering.Area", OBS_SCALE_AREA);
  310. #undef ADD_MODE
  311. return menu;
  312. }
  313. void OBSBasic::SetBlendingMethod()
  314. {
  315. QAction *action = reinterpret_cast<QAction *>(sender());
  316. obs_blending_method method = (obs_blending_method)action->property("method").toInt();
  317. OBSSceneItem sceneItem = GetCurrentSceneItem();
  318. obs_blending_method oldMethod = obs_sceneitem_get_blending_method(sceneItem);
  319. obs_sceneitem_set_blending_method(sceneItem, method);
  320. auto undo_redo = [](const std::string &uuid, int64_t id, obs_blending_method val) {
  321. OBSSourceAutoRelease s = obs_get_source_by_uuid(uuid.c_str());
  322. obs_scene_t *sc = obs_group_or_scene_from_source(s);
  323. obs_sceneitem_t *si = obs_scene_find_sceneitem_by_id(sc, id);
  324. if (si)
  325. obs_sceneitem_set_blending_method(si, val);
  326. };
  327. OBSSource source = obs_sceneitem_get_source(sceneItem);
  328. OBSSource sceneSource = obs_scene_get_source(obs_sceneitem_get_scene(sceneItem));
  329. int64_t id = obs_sceneitem_get_id(sceneItem);
  330. const char *name = obs_source_get_name(sceneSource);
  331. const char *uuid = obs_source_get_uuid(sceneSource);
  332. if (uuid && *uuid) {
  333. QString actionString = QTStr("Undo.BlendingMethod").arg(obs_source_get_name(source), name);
  334. auto undoFunction = std::bind(undo_redo, std::placeholders::_1, id, oldMethod);
  335. auto redoFunction = std::bind(undo_redo, std::placeholders::_1, id, method);
  336. undo_s.add_action(actionString, undoFunction, redoFunction, uuid, uuid);
  337. }
  338. }
  339. QMenu *OBSBasic::AddBlendingMethodMenu(QMenu *menu, obs_sceneitem_t *item)
  340. {
  341. obs_blending_method blendingMethod = obs_sceneitem_get_blending_method(item);
  342. QAction *action;
  343. #define ADD_MODE(name, method) \
  344. action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMethod); \
  345. action->setProperty("method", (int)method); \
  346. action->setCheckable(true); \
  347. action->setChecked(blendingMethod == method);
  348. ADD_MODE("BlendingMethod.Default", OBS_BLEND_METHOD_DEFAULT);
  349. ADD_MODE("BlendingMethod.SrgbOff", OBS_BLEND_METHOD_SRGB_OFF);
  350. #undef ADD_MODE
  351. return menu;
  352. }
  353. void OBSBasic::SetBlendingMode()
  354. {
  355. QAction *action = reinterpret_cast<QAction *>(sender());
  356. obs_blending_type mode = (obs_blending_type)action->property("mode").toInt();
  357. OBSSceneItem sceneItem = GetCurrentSceneItem();
  358. obs_blending_type oldMode = obs_sceneitem_get_blending_mode(sceneItem);
  359. obs_sceneitem_set_blending_mode(sceneItem, mode);
  360. auto undo_redo = [](const std::string &uuid, int64_t id, obs_blending_type val) {
  361. OBSSourceAutoRelease s = obs_get_source_by_uuid(uuid.c_str());
  362. obs_scene_t *sc = obs_group_or_scene_from_source(s);
  363. obs_sceneitem_t *si = obs_scene_find_sceneitem_by_id(sc, id);
  364. if (si)
  365. obs_sceneitem_set_blending_mode(si, val);
  366. };
  367. OBSSource source = obs_sceneitem_get_source(sceneItem);
  368. OBSSource sceneSource = obs_scene_get_source(obs_sceneitem_get_scene(sceneItem));
  369. int64_t id = obs_sceneitem_get_id(sceneItem);
  370. const char *name = obs_source_get_name(sceneSource);
  371. const char *uuid = obs_source_get_uuid(sceneSource);
  372. if (uuid && *uuid) {
  373. QString actionString = QTStr("Undo.BlendingMode").arg(obs_source_get_name(source), name);
  374. auto undoFunction = std::bind(undo_redo, std::placeholders::_1, id, oldMode);
  375. auto redoFunction = std::bind(undo_redo, std::placeholders::_1, id, mode);
  376. undo_s.add_action(actionString, undoFunction, redoFunction, uuid, uuid);
  377. }
  378. }
  379. QMenu *OBSBasic::AddBlendingModeMenu(QMenu *menu, obs_sceneitem_t *item)
  380. {
  381. obs_blending_type blendingMode = obs_sceneitem_get_blending_mode(item);
  382. QAction *action;
  383. #define ADD_MODE(name, mode) \
  384. action = menu->addAction(QTStr("" name), this, &OBSBasic::SetBlendingMode); \
  385. action->setProperty("mode", (int)mode); \
  386. action->setCheckable(true); \
  387. action->setChecked(blendingMode == mode);
  388. ADD_MODE("BlendingMode.Normal", OBS_BLEND_NORMAL);
  389. ADD_MODE("BlendingMode.Additive", OBS_BLEND_ADDITIVE);
  390. ADD_MODE("BlendingMode.Subtract", OBS_BLEND_SUBTRACT);
  391. ADD_MODE("BlendingMode.Screen", OBS_BLEND_SCREEN);
  392. ADD_MODE("BlendingMode.Multiply", OBS_BLEND_MULTIPLY);
  393. ADD_MODE("BlendingMode.Lighten", OBS_BLEND_LIGHTEN);
  394. ADD_MODE("BlendingMode.Darken", OBS_BLEND_DARKEN);
  395. #undef ADD_MODE
  396. return menu;
  397. }
  398. QMenu *OBSBasic::AddBackgroundColorMenu(QMenu *menu, QWidgetAction *widgetAction, ColorSelect *select,
  399. obs_sceneitem_t *item)
  400. {
  401. QAction *action;
  402. menu->setStyleSheet(QString("*[bgColor=\"1\"]{background-color:rgba(255,68,68,33%);}"
  403. "*[bgColor=\"2\"]{background-color:rgba(255,255,68,33%);}"
  404. "*[bgColor=\"3\"]{background-color:rgba(68,255,68,33%);}"
  405. "*[bgColor=\"4\"]{background-color:rgba(68,255,255,33%);}"
  406. "*[bgColor=\"5\"]{background-color:rgba(68,68,255,33%);}"
  407. "*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}"
  408. "*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}"
  409. "*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}"));
  410. obs_data_t *privData = obs_sceneitem_get_private_settings(item);
  411. obs_data_release(privData);
  412. obs_data_set_default_int(privData, "color-preset", 0);
  413. int preset = obs_data_get_int(privData, "color-preset");
  414. action = menu->addAction(QTStr("Clear"), this, &OBSBasic::ColorChange);
  415. action->setCheckable(true);
  416. action->setProperty("bgColor", 0);
  417. action->setChecked(preset == 0);
  418. action = menu->addAction(QTStr("CustomColor"), this, &OBSBasic::ColorChange);
  419. action->setCheckable(true);
  420. action->setProperty("bgColor", 1);
  421. action->setChecked(preset == 1);
  422. menu->addSeparator();
  423. widgetAction->setDefaultWidget(select);
  424. for (int i = 1; i < 9; i++) {
  425. stringstream button;
  426. button << "preset" << i;
  427. QPushButton *colorButton = select->findChild<QPushButton *>(button.str().c_str());
  428. if (preset == i + 1)
  429. colorButton->setStyleSheet("border: 2px solid black");
  430. colorButton->setProperty("bgColor", i);
  431. connect(colorButton, &QPushButton::released, this, &OBSBasic::ColorChange);
  432. }
  433. menu->addAction(widgetAction);
  434. return menu;
  435. }
  436. void OBSBasic::CreateSourcePopupMenu(int idx, bool preview)
  437. {
  438. QMenu popup(this);
  439. delete previewProjectorSource;
  440. delete sourceProjector;
  441. delete scaleFilteringMenu;
  442. delete blendingMethodMenu;
  443. delete blendingModeMenu;
  444. delete colorMenu;
  445. delete colorWidgetAction;
  446. delete colorSelect;
  447. delete deinterlaceMenu;
  448. OBSSceneItem sceneItem;
  449. obs_source_t *source;
  450. uint32_t flags;
  451. bool isAsyncVideo = false;
  452. bool hasAudio = false;
  453. bool hasVideo = false;
  454. bool sourceSelected = idx != -1;
  455. if (sourceSelected) {
  456. sceneItem = ui->sources->Get(idx);
  457. source = obs_sceneitem_get_source(sceneItem);
  458. flags = obs_source_get_output_flags(source);
  459. isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO;
  460. hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO;
  461. hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO;
  462. }
  463. // Add new source
  464. QAction *addSource = popup.addAction(QTStr("AddSource"), this, SLOT(AddSourceDialog()));
  465. popup.addAction(addSource);
  466. popup.addSeparator();
  467. if (!preview && !sourceSelected) {
  468. QAction *addGroup = new QAction(QTStr("Basic.Main.NewGroup"), this);
  469. connect(addGroup, &QAction::triggered, ui->sources, &SourceTree::AddGroup);
  470. popup.addAction(addGroup);
  471. }
  472. // Preview menu entries
  473. if (preview) {
  474. QAction *action =
  475. popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview);
  476. action->setCheckable(true);
  477. action->setChecked(obs_display_enabled(ui->preview->GetDisplay()));
  478. if (IsPreviewProgramMode())
  479. action->setEnabled(false);
  480. popup.addAction(ui->actionLockPreview);
  481. popup.addMenu(ui->scalingMenu);
  482. popup.addSeparator();
  483. }
  484. // Projector menu entries
  485. if (preview) {
  486. previewProjectorSource = new QMenu(QTStr("Projector.Open.Preview"));
  487. AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector);
  488. previewProjectorSource->addSeparator();
  489. previewProjectorSource->addAction(QTStr("Projector.Window"), this, &OBSBasic::OpenPreviewWindow);
  490. popup.addMenu(previewProjectorSource);
  491. }
  492. if (hasVideo) {
  493. sourceProjector = new QMenu(QTStr("Projector.Open.Source"));
  494. AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector);
  495. sourceProjector->addSeparator();
  496. sourceProjector->addAction(QTStr("Projector.Window"), this, &OBSBasic::OpenSourceWindow);
  497. popup.addMenu(sourceProjector);
  498. }
  499. popup.addSeparator();
  500. // Screenshot menu entries
  501. if (preview) {
  502. popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene);
  503. }
  504. if (hasVideo) {
  505. popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource);
  506. }
  507. popup.addSeparator();
  508. if (sourceSelected) {
  509. // Sources list menu entries
  510. if (!preview) {
  511. colorMenu = new QMenu(QTStr("ChangeBG"));
  512. colorWidgetAction = new QWidgetAction(colorMenu);
  513. colorSelect = new ColorSelect(colorMenu);
  514. popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem));
  515. if (hasAudio) {
  516. bool isHidden = isHiddenInMixer(source);
  517. QAction *actionHideMixer =
  518. popup.addAction(QTStr("HideMixer"), this, [source, isHidden]() {
  519. setHiddenInMixer(source, !isHidden);
  520. OBSBasic *main = OBSBasic::Get();
  521. emit main->mixerStatusChanged(obs_source_get_uuid(source));
  522. });
  523. actionHideMixer->setCheckable(true);
  524. actionHideMixer->setChecked(isHidden);
  525. }
  526. popup.addSeparator();
  527. }
  528. // Scene item menu entries
  529. if (hasVideo && source) {
  530. scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering"));
  531. popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem));
  532. blendingModeMenu = new QMenu(QTStr("BlendingMode"));
  533. popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem));
  534. blendingMethodMenu = new QMenu(QTStr("BlendingMethod"));
  535. popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem));
  536. if (isAsyncVideo) {
  537. deinterlaceMenu = new QMenu(QTStr("Deinterlacing"));
  538. popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source));
  539. }
  540. popup.addMenu(CreateVisibilityTransitionMenu(true));
  541. popup.addMenu(CreateVisibilityTransitionMenu(false));
  542. popup.addSeparator();
  543. QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this,
  544. &OBSBasic::ResizeOutputSizeOfSource);
  545. int width = obs_source_get_width(source);
  546. int height = obs_source_get_height(source);
  547. resizeOutput->setEnabled(!obs_video_active());
  548. if (width < 32 || height < 32)
  549. resizeOutput->setEnabled(false);
  550. }
  551. popup.addSeparator();
  552. popup.addMenu(ui->orderMenu);
  553. if (hasVideo) {
  554. popup.addMenu(ui->transformMenu);
  555. }
  556. popup.addSeparator();
  557. // Source grouping
  558. if (ui->sources->MultipleBaseSelected()) {
  559. popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems);
  560. popup.addSeparator();
  561. } else if (ui->sources->GroupsSelected()) {
  562. popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups);
  563. popup.addSeparator();
  564. }
  565. popup.addAction(ui->actionCopySource);
  566. popup.addAction(ui->actionPasteRef);
  567. popup.addAction(ui->actionPasteDup);
  568. popup.addSeparator();
  569. if (hasVideo || hasAudio) {
  570. popup.addAction(ui->actionCopyFilters);
  571. popup.addAction(ui->actionPasteFilters);
  572. popup.addSeparator();
  573. }
  574. popup.addAction(ui->actionRemoveSource);
  575. popup.addAction(renameSource);
  576. popup.addSeparator();
  577. if (flags && flags & OBS_SOURCE_INTERACTION)
  578. popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered);
  579. popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); });
  580. QAction *action =
  581. popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered);
  582. action->setEnabled(obs_source_configurable(source));
  583. } else {
  584. popup.addAction(ui->actionPasteRef);
  585. popup.addAction(ui->actionPasteDup);
  586. }
  587. popup.exec(QCursor::pos());
  588. }
  589. void OBSBasic::actionOpenSourceFilters()
  590. {
  591. QAction *action = reinterpret_cast<QAction *>(sender());
  592. if (!action->property("source").isValid()) {
  593. return;
  594. }
  595. obs_source_t *source = action->property("source").value<OBSSource>();
  596. CreateFiltersWindow(source);
  597. }
  598. void OBSBasic::actionOpenSourceProperties()
  599. {
  600. QAction *action = reinterpret_cast<QAction *>(sender());
  601. if (!action->property("source").isValid()) {
  602. return;
  603. }
  604. obs_source_t *source = action->property("source").value<OBSSource>();
  605. CreatePropertiesWindow(source);
  606. }
  607. void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
  608. {
  609. if (ui->scenes->count()) {
  610. QModelIndex idx = ui->sources->indexAt(pos);
  611. CreateSourcePopupMenu(idx.row(), false);
  612. }
  613. }
  614. static inline bool should_show_properties(obs_source_t *source, const char *id)
  615. {
  616. if (!source)
  617. return false;
  618. if (strcmp(id, "group") == 0)
  619. return false;
  620. if (!obs_source_configurable(source))
  621. return false;
  622. uint32_t caps = obs_source_get_output_flags(source);
  623. if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0)
  624. return false;
  625. return true;
  626. }
  627. void OBSBasic::AddSourceDialog()
  628. {
  629. QAction *action = qobject_cast<QAction *>(sender());
  630. if (!action) {
  631. return;
  632. }
  633. if (addWindow) {
  634. addWindow->close();
  635. }
  636. addWindow = new OBSBasicSourceSelect(this, undo_s);
  637. addWindow->show();
  638. addWindow->setAttribute(Qt::WA_DeleteOnClose, true);
  639. connect(this, &OBSBasic::sourceUuidDropped, addWindow, &OBSBasicSourceSelect::sourceDropped);
  640. }
  641. void OBSBasic::on_actionAddSource_triggered()
  642. {
  643. AddSourceDialog();
  644. }
  645. static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param)
  646. {
  647. vector<OBSSceneItem> &items = *static_cast<vector<OBSSceneItem> *>(param);
  648. if (obs_sceneitem_selected(item)) {
  649. items.emplace_back(item);
  650. } else if (obs_sceneitem_is_group(item)) {
  651. obs_sceneitem_group_enum_items(item, remove_items, &items);
  652. }
  653. return true;
  654. };
  655. void OBSBasic::on_actionRemoveSource_triggered()
  656. {
  657. vector<OBSSceneItem> items;
  658. OBSScene scene = GetCurrentScene();
  659. obs_source_t *scene_source = obs_scene_get_source(scene);
  660. obs_scene_enum_items(scene, remove_items, &items);
  661. if (!items.size())
  662. return;
  663. /* ------------------------------------- */
  664. /* confirm action with user */
  665. bool confirmed = false;
  666. if (items.size() > 1) {
  667. QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size()));
  668. QMessageBox remove_items(this);
  669. remove_items.setText(text);
  670. QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole);
  671. remove_items.setDefaultButton(Yes);
  672. remove_items.addButton(QTStr("No"), QMessageBox::NoRole);
  673. remove_items.setIcon(QMessageBox::Question);
  674. remove_items.setWindowTitle(QTStr("ConfirmRemove.Title"));
  675. remove_items.exec();
  676. confirmed = Yes == remove_items.clickedButton();
  677. } else {
  678. OBSSceneItem &item = items[0];
  679. obs_source_t *source = obs_sceneitem_get_source(item);
  680. if (source && QueryRemoveSource(source))
  681. confirmed = true;
  682. }
  683. if (!confirmed)
  684. return;
  685. /* ----------------------------------------------- */
  686. /* save undo data */
  687. OBSData undo_data = BackupScene(scene_source);
  688. /* ----------------------------------------------- */
  689. /* remove items */
  690. for (auto &item : items)
  691. obs_sceneitem_remove(item);
  692. /* ----------------------------------------------- */
  693. /* save redo data */
  694. OBSData redo_data = BackupScene(scene_source);
  695. /* ----------------------------------------------- */
  696. /* add undo/redo action */
  697. QString action_name;
  698. if (items.size() > 1) {
  699. action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size()));
  700. } else {
  701. QString str = QTStr("Undo.Delete");
  702. action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0])));
  703. }
  704. CreateSceneUndoRedoAction(action_name, undo_data, redo_data);
  705. }
  706. void OBSBasic::on_actionInteract_triggered()
  707. {
  708. OBSSceneItem item = GetCurrentSceneItem();
  709. OBSSource source = obs_sceneitem_get_source(item);
  710. if (source)
  711. CreateInteractionWindow(source);
  712. }
  713. void OBSBasic::on_actionSourceProperties_triggered()
  714. {
  715. OBSSceneItem item = GetCurrentSceneItem();
  716. OBSSource source = obs_sceneitem_get_source(item);
  717. if (source)
  718. CreatePropertiesWindow(source);
  719. }
  720. void OBSBasic::on_actionSourceUp_triggered()
  721. {
  722. MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp"));
  723. }
  724. void OBSBasic::on_actionSourceDown_triggered()
  725. {
  726. MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown"));
  727. }
  728. void OBSBasic::on_actionMoveUp_triggered()
  729. {
  730. MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp"));
  731. }
  732. void OBSBasic::on_actionMoveDown_triggered()
  733. {
  734. MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown"));
  735. }
  736. void OBSBasic::on_actionMoveToTop_triggered()
  737. {
  738. MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop"));
  739. }
  740. void OBSBasic::on_actionMoveToBottom_triggered()
  741. {
  742. MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom"));
  743. }
  744. void OBSBasic::OpenFilters(OBSSource source)
  745. {
  746. if (source == nullptr) {
  747. OBSSceneItem item = GetCurrentSceneItem();
  748. source = obs_sceneitem_get_source(item);
  749. }
  750. CreateFiltersWindow(source);
  751. }
  752. void OBSBasic::OpenProperties(OBSSource source)
  753. {
  754. if (source == nullptr) {
  755. OBSSceneItem item = GetCurrentSceneItem();
  756. source = obs_sceneitem_get_source(item);
  757. }
  758. CreatePropertiesWindow(source);
  759. }
  760. void OBSBasic::OpenInteraction(OBSSource source)
  761. {
  762. if (source == nullptr) {
  763. OBSSceneItem item = GetCurrentSceneItem();
  764. source = obs_sceneitem_get_source(item);
  765. }
  766. CreateInteractionWindow(source);
  767. }
  768. void OBSBasic::OpenEditTransform(OBSSceneItem item)
  769. {
  770. if (!item)
  771. item = GetCurrentSceneItem();
  772. if (!item)
  773. return;
  774. CreateEditTransformWindow(item);
  775. }
  776. int OBSBasic::GetTopSelectedSourceItem()
  777. {
  778. QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes();
  779. return selectedItems.count() ? selectedItems[0].row() : -1;
  780. }
  781. QModelIndexList OBSBasic::GetAllSelectedSourceItems()
  782. {
  783. return ui->sources->selectionModel()->selectedIndexes();
  784. }
  785. void OBSBasic::on_actionEditTransform_triggered()
  786. {
  787. const auto item = GetCurrentSceneItem();
  788. if (!item)
  789. return;
  790. CreateEditTransformWindow(item);
  791. }
  792. void undo_redo(const std::string &data)
  793. {
  794. OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str());
  795. OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid"));
  796. OBSBasic::Get()->SetCurrentScene(source.Get(), true);
  797. obs_scene_load_transform_states(data.c_str());
  798. }
  799. static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
  800. {
  801. matrix4 boxTransform;
  802. obs_sceneitem_get_box_transform(item, &boxTransform);
  803. vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f);
  804. vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f);
  805. auto GetMinPos = [&](float x, float y) {
  806. vec3 pos;
  807. vec3_set(&pos, x, y, 0.0f);
  808. vec3_transform(&pos, &pos, &boxTransform);
  809. vec3_min(&tl, &tl, &pos);
  810. vec3_max(&br, &br, &pos);
  811. };
  812. GetMinPos(0.0f, 0.0f);
  813. GetMinPos(1.0f, 0.0f);
  814. GetMinPos(0.0f, 1.0f);
  815. GetMinPos(1.0f, 1.0f);
  816. }
  817. static vec3 GetItemTL(obs_sceneitem_t *item)
  818. {
  819. vec3 tl, br;
  820. GetItemBox(item, tl, br);
  821. return tl;
  822. }
  823. static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl)
  824. {
  825. vec3 newTL;
  826. vec2 pos;
  827. obs_sceneitem_get_pos(item, &pos);
  828. newTL = GetItemTL(item);
  829. pos.x += tl.x - newTL.x;
  830. pos.y += tl.y - newTL.y;
  831. obs_sceneitem_set_pos(item, &pos);
  832. }
  833. static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  834. {
  835. if (obs_sceneitem_is_group(item))
  836. obs_sceneitem_group_enum_items(item, RotateSelectedSources, param);
  837. if (!obs_sceneitem_selected(item))
  838. return true;
  839. if (obs_sceneitem_locked(item))
  840. return true;
  841. float rot = *static_cast<float *>(param);
  842. vec3 tl = GetItemTL(item);
  843. rot += obs_sceneitem_get_rot(item);
  844. if (rot >= 360.0f)
  845. rot -= 360.0f;
  846. else if (rot <= -360.0f)
  847. rot += 360.0f;
  848. obs_sceneitem_set_rot(item, rot);
  849. obs_sceneitem_force_update_transform(item);
  850. SetItemTL(item, tl);
  851. return true;
  852. };
  853. void OBSBasic::on_actionRotate90CW_triggered()
  854. {
  855. float f90CW = 90.0f;
  856. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  857. obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW);
  858. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  859. std::string undo_data(obs_data_get_json(wrapper));
  860. std::string redo_data(obs_data_get_json(rwrapper));
  861. undo_s.add_action(
  862. QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  863. undo_redo, undo_redo, undo_data, redo_data);
  864. }
  865. void OBSBasic::on_actionRotate90CCW_triggered()
  866. {
  867. float f90CCW = -90.0f;
  868. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  869. obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW);
  870. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  871. std::string undo_data(obs_data_get_json(wrapper));
  872. std::string redo_data(obs_data_get_json(rwrapper));
  873. undo_s.add_action(
  874. QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  875. undo_redo, undo_redo, undo_data, redo_data);
  876. }
  877. void OBSBasic::on_actionRotate180_triggered()
  878. {
  879. float f180 = 180.0f;
  880. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  881. obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180);
  882. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  883. std::string undo_data(obs_data_get_json(wrapper));
  884. std::string redo_data(obs_data_get_json(rwrapper));
  885. undo_s.add_action(
  886. QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  887. undo_redo, undo_redo, undo_data, redo_data);
  888. }
  889. static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  890. {
  891. vec2 &mul = *static_cast<vec2 *>(param);
  892. if (obs_sceneitem_is_group(item))
  893. obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param);
  894. if (!obs_sceneitem_selected(item))
  895. return true;
  896. if (obs_sceneitem_locked(item))
  897. return true;
  898. vec3 tl = GetItemTL(item);
  899. vec2 scale;
  900. obs_sceneitem_get_scale(item, &scale);
  901. vec2_mul(&scale, &scale, &mul);
  902. obs_sceneitem_set_scale(item, &scale);
  903. obs_sceneitem_force_update_transform(item);
  904. SetItemTL(item, tl);
  905. return true;
  906. }
  907. void OBSBasic::on_actionFlipHorizontal_triggered()
  908. {
  909. vec2 scale;
  910. vec2_set(&scale, -1.0f, 1.0f);
  911. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  912. obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale);
  913. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  914. std::string undo_data(obs_data_get_json(wrapper));
  915. std::string redo_data(obs_data_get_json(rwrapper));
  916. undo_s.add_action(
  917. QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  918. undo_redo, undo_redo, undo_data, redo_data);
  919. }
  920. void OBSBasic::on_actionFlipVertical_triggered()
  921. {
  922. vec2 scale;
  923. vec2_set(&scale, 1.0f, -1.0f);
  924. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  925. obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale);
  926. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  927. std::string undo_data(obs_data_get_json(wrapper));
  928. std::string redo_data(obs_data_get_json(rwrapper));
  929. undo_s.add_action(
  930. QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  931. undo_redo, undo_redo, undo_data, redo_data);
  932. }
  933. static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  934. {
  935. obs_bounds_type boundsType = *static_cast<obs_bounds_type *>(param);
  936. if (obs_sceneitem_is_group(item))
  937. obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param);
  938. if (!obs_sceneitem_selected(item))
  939. return true;
  940. if (obs_sceneitem_locked(item))
  941. return true;
  942. obs_video_info ovi;
  943. obs_get_video_info(&ovi);
  944. obs_transform_info itemInfo;
  945. vec2_set(&itemInfo.pos, 0.0f, 0.0f);
  946. vec2_set(&itemInfo.scale, 1.0f, 1.0f);
  947. itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP;
  948. itemInfo.rot = 0.0f;
  949. vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height));
  950. itemInfo.bounds_type = boundsType;
  951. itemInfo.bounds_alignment = OBS_ALIGN_CENTER;
  952. itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item);
  953. obs_sceneitem_set_info2(item, &itemInfo);
  954. return true;
  955. }
  956. void OBSBasic::on_actionFitToScreen_triggered()
  957. {
  958. obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER;
  959. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  960. obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType);
  961. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  962. std::string undo_data(obs_data_get_json(wrapper));
  963. std::string redo_data(obs_data_get_json(rwrapper));
  964. undo_s.add_action(
  965. QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  966. undo_redo, undo_redo, undo_data, redo_data);
  967. }
  968. void OBSBasic::on_actionStretchToScreen_triggered()
  969. {
  970. obs_bounds_type boundsType = OBS_BOUNDS_STRETCH;
  971. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  972. obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType);
  973. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  974. std::string undo_data(obs_data_get_json(wrapper));
  975. std::string redo_data(obs_data_get_json(rwrapper));
  976. undo_s.add_action(QTStr("Undo.Transform.StretchToScreen")
  977. .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  978. undo_redo, undo_redo, undo_data, redo_data);
  979. }
  980. void OBSBasic::CenterSelectedSceneItems(const CenterType &centerType)
  981. {
  982. QModelIndexList selectedItems = GetAllSelectedSourceItems();
  983. if (!selectedItems.count())
  984. return;
  985. vector<OBSSceneItem> items;
  986. // Filter out items that have no size
  987. for (int x = 0; x < selectedItems.count(); x++) {
  988. OBSSceneItem item = ui->sources->Get(selectedItems[x].row());
  989. obs_transform_info oti;
  990. obs_sceneitem_get_info2(item, &oti);
  991. obs_source_t *source = obs_sceneitem_get_source(item);
  992. float width = float(obs_source_get_width(source)) * oti.scale.x;
  993. float height = float(obs_source_get_height(source)) * oti.scale.y;
  994. if (width == 0.0f || height == 0.0f)
  995. continue;
  996. items.emplace_back(item);
  997. }
  998. if (!items.size())
  999. return;
  1000. // Get center x, y coordinates of items
  1001. vec3 center;
  1002. float top = M_INFINITE;
  1003. float left = M_INFINITE;
  1004. float right = 0.0f;
  1005. float bottom = 0.0f;
  1006. for (auto &item : items) {
  1007. vec3 tl, br;
  1008. GetItemBox(item, tl, br);
  1009. left = std::min(tl.x, left);
  1010. top = std::min(tl.y, top);
  1011. right = std::max(br.x, right);
  1012. bottom = std::max(br.y, bottom);
  1013. }
  1014. center.x = (right + left) / 2.0f;
  1015. center.y = (top + bottom) / 2.0f;
  1016. center.z = 0.0f;
  1017. // Get coordinates of screen center
  1018. obs_video_info ovi;
  1019. obs_get_video_info(&ovi);
  1020. vec3 screenCenter;
  1021. vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f);
  1022. vec3_mulf(&screenCenter, &screenCenter, 0.5f);
  1023. // Calculate difference between screen center and item center
  1024. vec3 offset;
  1025. vec3_sub(&offset, &screenCenter, &center);
  1026. // Shift items by offset
  1027. for (auto &item : items) {
  1028. vec3 tl, br;
  1029. GetItemBox(item, tl, br);
  1030. vec3_add(&tl, &tl, &offset);
  1031. vec3 itemTL = GetItemTL(item);
  1032. if (centerType == CenterType::Vertical)
  1033. tl.x = itemTL.x;
  1034. else if (centerType == CenterType::Horizontal)
  1035. tl.y = itemTL.y;
  1036. SetItemTL(item, tl);
  1037. }
  1038. }
  1039. void OBSBasic::on_actionCenterToScreen_triggered()
  1040. {
  1041. CenterType centerType = CenterType::Scene;
  1042. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1043. CenterSelectedSceneItems(centerType);
  1044. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1045. std::string undo_data(obs_data_get_json(wrapper));
  1046. std::string redo_data(obs_data_get_json(rwrapper));
  1047. undo_s.add_action(
  1048. QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  1049. undo_redo, undo_redo, undo_data, redo_data);
  1050. }
  1051. void OBSBasic::on_actionVerticalCenter_triggered()
  1052. {
  1053. CenterType centerType = CenterType::Vertical;
  1054. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1055. CenterSelectedSceneItems(centerType);
  1056. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1057. std::string undo_data(obs_data_get_json(wrapper));
  1058. std::string redo_data(obs_data_get_json(rwrapper));
  1059. undo_s.add_action(
  1060. QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  1061. undo_redo, undo_redo, undo_data, redo_data);
  1062. }
  1063. void OBSBasic::on_actionHorizontalCenter_triggered()
  1064. {
  1065. CenterType centerType = CenterType::Horizontal;
  1066. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1067. CenterSelectedSceneItems(centerType);
  1068. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1069. std::string undo_data(obs_data_get_json(wrapper));
  1070. std::string redo_data(obs_data_get_json(rwrapper));
  1071. undo_s.add_action(
  1072. QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  1073. undo_redo, undo_redo, undo_data, redo_data);
  1074. }
  1075. void OBSBasic::on_toggleSourceIcons_toggled(bool visible)
  1076. {
  1077. ui->sources->SetIconsVisible(visible);
  1078. if (advAudioWindow != nullptr)
  1079. advAudioWindow->SetIconsVisible(visible);
  1080. config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible);
  1081. }
  1082. void OBSBasic::on_sourcePropertiesButton_clicked()
  1083. {
  1084. on_actionSourceProperties_triggered();
  1085. }
  1086. void OBSBasic::on_sourceFiltersButton_clicked()
  1087. {
  1088. OpenFilters();
  1089. }
  1090. void OBSBasic::on_sourceInteractButton_clicked()
  1091. {
  1092. on_actionInteract_triggered();
  1093. }