OBSBasic_SceneItems.cpp 45 KB

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