OBSBasic_SceneItems.cpp 42 KB

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