OBSBasic_SceneItems.cpp 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443
  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. OBSSceneItem sceneItem;
  455. obs_source_t *source;
  456. uint32_t flags;
  457. bool isAsyncVideo = false;
  458. bool hasAudio = false;
  459. bool hasVideo = false;
  460. bool sourceSelected = idx != -1;
  461. if (sourceSelected) {
  462. sceneItem = ui->sources->Get(idx);
  463. source = obs_sceneitem_get_source(sceneItem);
  464. flags = obs_source_get_output_flags(source);
  465. isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) == OBS_SOURCE_ASYNC_VIDEO;
  466. hasAudio = (flags & OBS_SOURCE_AUDIO) == OBS_SOURCE_AUDIO;
  467. hasVideo = (flags & OBS_SOURCE_VIDEO) == OBS_SOURCE_VIDEO;
  468. }
  469. // Add new source
  470. QPointer<QMenu> addSourceMenu = CreateAddSourcePopupMenu();
  471. if (addSourceMenu) {
  472. popup.addMenu(addSourceMenu);
  473. popup.addSeparator();
  474. }
  475. // Preview menu entries
  476. if (preview) {
  477. QAction *action =
  478. popup.addAction(QTStr("Basic.Main.PreviewConextMenu.Enable"), this, &OBSBasic::TogglePreview);
  479. action->setCheckable(true);
  480. action->setChecked(obs_display_enabled(ui->preview->GetDisplay()));
  481. if (IsPreviewProgramMode())
  482. action->setEnabled(false);
  483. popup.addAction(ui->actionLockPreview);
  484. popup.addMenu(ui->scalingMenu);
  485. popup.addSeparator();
  486. }
  487. // Projector menu entries
  488. if (preview) {
  489. previewProjectorSource = new QMenu(QTStr("Projector.Open.Preview"));
  490. AddProjectorMenuMonitors(previewProjectorSource, this, &OBSBasic::OpenPreviewProjector);
  491. previewProjectorSource->addSeparator();
  492. previewProjectorSource->addAction(QTStr("Projector.Window"), this, &OBSBasic::OpenPreviewWindow);
  493. popup.addMenu(previewProjectorSource);
  494. }
  495. if (hasVideo) {
  496. sourceProjector = new QMenu(QTStr("Projector.Open.Source"));
  497. AddProjectorMenuMonitors(sourceProjector, this, &OBSBasic::OpenSourceProjector);
  498. sourceProjector->addSeparator();
  499. sourceProjector->addAction(QTStr("Projector.Window"), this, &OBSBasic::OpenSourceWindow);
  500. popup.addMenu(sourceProjector);
  501. }
  502. popup.addSeparator();
  503. // Screenshot menu entries
  504. if (preview) {
  505. popup.addAction(QTStr("Screenshot.Preview"), this, &OBSBasic::ScreenshotScene);
  506. }
  507. if (hasVideo) {
  508. popup.addAction(QTStr("Screenshot.Source"), this, &OBSBasic::ScreenshotSelectedSource);
  509. }
  510. popup.addSeparator();
  511. if (sourceSelected) {
  512. // Sources list menu entries
  513. if (!preview) {
  514. colorMenu = new QMenu(QTStr("ChangeBG"));
  515. colorWidgetAction = new QWidgetAction(colorMenu);
  516. colorSelect = new ColorSelect(colorMenu);
  517. popup.addMenu(AddBackgroundColorMenu(colorMenu, colorWidgetAction, colorSelect, sceneItem));
  518. if (hasAudio) {
  519. QAction *actionHideMixer =
  520. popup.addAction(QTStr("HideMixer"), this, &OBSBasic::ToggleHideMixer);
  521. actionHideMixer->setCheckable(true);
  522. actionHideMixer->setChecked(SourceMixerHidden(source));
  523. }
  524. popup.addSeparator();
  525. }
  526. // Scene item menu entries
  527. if (hasVideo && source) {
  528. scaleFilteringMenu = new QMenu(QTStr("ScaleFiltering"));
  529. popup.addMenu(AddScaleFilteringMenu(scaleFilteringMenu, sceneItem));
  530. blendingModeMenu = new QMenu(QTStr("BlendingMode"));
  531. popup.addMenu(AddBlendingModeMenu(blendingModeMenu, sceneItem));
  532. blendingMethodMenu = new QMenu(QTStr("BlendingMethod"));
  533. popup.addMenu(AddBlendingMethodMenu(blendingMethodMenu, sceneItem));
  534. if (isAsyncVideo) {
  535. deinterlaceMenu = new QMenu(QTStr("Deinterlacing"));
  536. popup.addMenu(AddDeinterlacingMenu(deinterlaceMenu, source));
  537. }
  538. popup.addMenu(CreateVisibilityTransitionMenu(true));
  539. popup.addMenu(CreateVisibilityTransitionMenu(false));
  540. popup.addSeparator();
  541. QAction *resizeOutput = popup.addAction(QTStr("ResizeOutputSizeOfSource"), this,
  542. &OBSBasic::ResizeOutputSizeOfSource);
  543. int width = obs_source_get_width(source);
  544. int height = obs_source_get_height(source);
  545. resizeOutput->setEnabled(!obs_video_active());
  546. if (width < 32 || height < 32)
  547. resizeOutput->setEnabled(false);
  548. }
  549. popup.addSeparator();
  550. popup.addMenu(ui->orderMenu);
  551. if (hasVideo) {
  552. popup.addMenu(ui->transformMenu);
  553. }
  554. popup.addSeparator();
  555. // Source grouping
  556. if (ui->sources->MultipleBaseSelected()) {
  557. popup.addSeparator();
  558. popup.addAction(QTStr("Basic.Main.GroupItems"), ui->sources, &SourceTree::GroupSelectedItems);
  559. } else if (ui->sources->GroupsSelected()) {
  560. popup.addSeparator();
  561. popup.addAction(QTStr("Basic.Main.Ungroup"), ui->sources, &SourceTree::UngroupSelectedGroups);
  562. }
  563. popup.addSeparator();
  564. popup.addAction(ui->actionCopySource);
  565. popup.addAction(ui->actionPasteRef);
  566. popup.addAction(ui->actionPasteDup);
  567. popup.addSeparator();
  568. if (hasVideo || hasAudio) {
  569. popup.addAction(ui->actionCopyFilters);
  570. popup.addAction(ui->actionPasteFilters);
  571. popup.addSeparator();
  572. }
  573. popup.addAction(ui->actionRemoveSource);
  574. popup.addAction(renameSource);
  575. popup.addSeparator();
  576. if (flags && flags & OBS_SOURCE_INTERACTION)
  577. popup.addAction(QTStr("Interact"), this, &OBSBasic::on_actionInteract_triggered);
  578. popup.addAction(QTStr("Filters"), this, [&]() { OpenFilters(); });
  579. QAction *action =
  580. popup.addAction(QTStr("Properties"), this, &OBSBasic::on_actionSourceProperties_triggered);
  581. action->setEnabled(obs_source_configurable(source));
  582. } else {
  583. popup.addAction(ui->actionPasteRef);
  584. popup.addAction(ui->actionPasteDup);
  585. }
  586. popup.exec(QCursor::pos());
  587. }
  588. void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
  589. {
  590. if (ui->scenes->count()) {
  591. QModelIndex idx = ui->sources->indexAt(pos);
  592. CreateSourcePopupMenu(idx.row(), false);
  593. }
  594. }
  595. static inline bool should_show_properties(obs_source_t *source, const char *id)
  596. {
  597. if (!source)
  598. return false;
  599. if (strcmp(id, "group") == 0)
  600. return false;
  601. if (!obs_source_configurable(source))
  602. return false;
  603. uint32_t caps = obs_source_get_output_flags(source);
  604. if ((caps & OBS_SOURCE_CAP_DONT_SHOW_PROPERTIES) != 0)
  605. return false;
  606. return true;
  607. }
  608. void OBSBasic::AddSource(const char *id)
  609. {
  610. if (id && *id) {
  611. OBSBasicSourceSelect sourceSelect(this, id, undo_s);
  612. sourceSelect.exec();
  613. if (should_show_properties(sourceSelect.newSource, id)) {
  614. CreatePropertiesWindow(sourceSelect.newSource);
  615. }
  616. }
  617. }
  618. QMenu *OBSBasic::CreateAddSourcePopupMenu()
  619. {
  620. const char *unversioned_type;
  621. const char *type;
  622. bool foundValues = false;
  623. bool foundDeprecated = false;
  624. size_t idx = 0;
  625. QMenu *popup = new QMenu(QTStr("AddSource"), this);
  626. QMenu *deprecated = new QMenu(QTStr("Deprecated"), popup);
  627. auto getActionAfter = [](QMenu *menu, const QString &name) {
  628. QList<QAction *> actions = menu->actions();
  629. for (QAction *menuAction : actions) {
  630. if (menuAction->text().compare(name, Qt::CaseInsensitive) >= 0)
  631. return menuAction;
  632. }
  633. return (QAction *)nullptr;
  634. };
  635. auto addSource = [this, getActionAfter](QMenu *popup, const char *type, const char *name) {
  636. QString qname = QT_UTF8(name);
  637. QAction *popupItem = new QAction(qname, this);
  638. connect(popupItem, &QAction::triggered, [this, type]() { AddSource(type); });
  639. QIcon icon;
  640. if (strcmp(type, "scene") == 0)
  641. icon = GetSceneIcon();
  642. else
  643. icon = GetSourceIcon(type);
  644. popupItem->setIcon(icon);
  645. QAction *after = getActionAfter(popup, qname);
  646. popup->insertAction(after, popupItem);
  647. };
  648. while (obs_enum_input_types2(idx++, &type, &unversioned_type)) {
  649. const char *name = obs_source_get_display_name(type);
  650. uint32_t caps = obs_get_source_output_flags(type);
  651. if ((caps & OBS_SOURCE_CAP_DISABLED) != 0)
  652. continue;
  653. if ((caps & OBS_SOURCE_DEPRECATED) == 0) {
  654. addSource(popup, unversioned_type, name);
  655. } else {
  656. addSource(deprecated, unversioned_type, name);
  657. foundDeprecated = true;
  658. }
  659. foundValues = true;
  660. }
  661. addSource(popup, "scene", Str("Basic.Scene"));
  662. popup->addSeparator();
  663. QAction *addGroup = new QAction(QTStr("Group"), this);
  664. addGroup->setIcon(GetGroupIcon());
  665. connect(addGroup, &QAction::triggered, [this]() { AddSource("group"); });
  666. popup->addAction(addGroup);
  667. if (!foundDeprecated) {
  668. delete deprecated;
  669. deprecated = nullptr;
  670. }
  671. if (!foundValues) {
  672. delete popup;
  673. popup = nullptr;
  674. } else if (foundDeprecated) {
  675. popup->addSeparator();
  676. popup->addMenu(deprecated);
  677. }
  678. return popup;
  679. }
  680. void OBSBasic::AddSourcePopupMenu(const QPoint &pos)
  681. {
  682. if (!GetCurrentScene()) {
  683. // Tell the user he needs a scene first (help beginners).
  684. OBSMessageBox::information(this, QTStr("Basic.Main.AddSourceHelp.Title"),
  685. QTStr("Basic.Main.AddSourceHelp.Text"));
  686. return;
  687. }
  688. QScopedPointer<QMenu> popup(CreateAddSourcePopupMenu());
  689. if (popup)
  690. popup->exec(pos);
  691. }
  692. void OBSBasic::on_actionAddSource_triggered()
  693. {
  694. AddSourcePopupMenu(QCursor::pos());
  695. }
  696. static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param)
  697. {
  698. vector<OBSSceneItem> &items = *static_cast<vector<OBSSceneItem> *>(param);
  699. if (obs_sceneitem_selected(item)) {
  700. items.emplace_back(item);
  701. } else if (obs_sceneitem_is_group(item)) {
  702. obs_sceneitem_group_enum_items(item, remove_items, &items);
  703. }
  704. return true;
  705. };
  706. void OBSBasic::on_actionRemoveSource_triggered()
  707. {
  708. vector<OBSSceneItem> items;
  709. OBSScene scene = GetCurrentScene();
  710. obs_source_t *scene_source = obs_scene_get_source(scene);
  711. obs_scene_enum_items(scene, remove_items, &items);
  712. if (!items.size())
  713. return;
  714. /* ------------------------------------- */
  715. /* confirm action with user */
  716. bool confirmed = false;
  717. if (items.size() > 1) {
  718. QString text = QTStr("ConfirmRemove.TextMultiple").arg(QString::number(items.size()));
  719. QMessageBox remove_items(this);
  720. remove_items.setText(text);
  721. QPushButton *Yes = remove_items.addButton(QTStr("Yes"), QMessageBox::YesRole);
  722. remove_items.setDefaultButton(Yes);
  723. remove_items.addButton(QTStr("No"), QMessageBox::NoRole);
  724. remove_items.setIcon(QMessageBox::Question);
  725. remove_items.setWindowTitle(QTStr("ConfirmRemove.Title"));
  726. remove_items.exec();
  727. confirmed = Yes == remove_items.clickedButton();
  728. } else {
  729. OBSSceneItem &item = items[0];
  730. obs_source_t *source = obs_sceneitem_get_source(item);
  731. if (source && QueryRemoveSource(source))
  732. confirmed = true;
  733. }
  734. if (!confirmed)
  735. return;
  736. /* ----------------------------------------------- */
  737. /* save undo data */
  738. OBSData undo_data = BackupScene(scene_source);
  739. /* ----------------------------------------------- */
  740. /* remove items */
  741. for (auto &item : items)
  742. obs_sceneitem_remove(item);
  743. /* ----------------------------------------------- */
  744. /* save redo data */
  745. OBSData redo_data = BackupScene(scene_source);
  746. /* ----------------------------------------------- */
  747. /* add undo/redo action */
  748. QString action_name;
  749. if (items.size() > 1) {
  750. action_name = QTStr("Undo.Sources.Multi").arg(QString::number(items.size()));
  751. } else {
  752. QString str = QTStr("Undo.Delete");
  753. action_name = str.arg(obs_source_get_name(obs_sceneitem_get_source(items[0])));
  754. }
  755. CreateSceneUndoRedoAction(action_name, undo_data, redo_data);
  756. }
  757. void OBSBasic::on_actionInteract_triggered()
  758. {
  759. OBSSceneItem item = GetCurrentSceneItem();
  760. OBSSource source = obs_sceneitem_get_source(item);
  761. if (source)
  762. CreateInteractionWindow(source);
  763. }
  764. void OBSBasic::on_actionSourceProperties_triggered()
  765. {
  766. OBSSceneItem item = GetCurrentSceneItem();
  767. OBSSource source = obs_sceneitem_get_source(item);
  768. if (source)
  769. CreatePropertiesWindow(source);
  770. }
  771. void OBSBasic::on_actionSourceUp_triggered()
  772. {
  773. MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp"));
  774. }
  775. void OBSBasic::on_actionSourceDown_triggered()
  776. {
  777. MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown"));
  778. }
  779. void OBSBasic::on_actionMoveUp_triggered()
  780. {
  781. MoveSceneItem(OBS_ORDER_MOVE_UP, QTStr("Undo.MoveUp"));
  782. }
  783. void OBSBasic::on_actionMoveDown_triggered()
  784. {
  785. MoveSceneItem(OBS_ORDER_MOVE_DOWN, QTStr("Undo.MoveDown"));
  786. }
  787. void OBSBasic::on_actionMoveToTop_triggered()
  788. {
  789. MoveSceneItem(OBS_ORDER_MOVE_TOP, QTStr("Undo.MoveToTop"));
  790. }
  791. void OBSBasic::on_actionMoveToBottom_triggered()
  792. {
  793. MoveSceneItem(OBS_ORDER_MOVE_BOTTOM, QTStr("Undo.MoveToBottom"));
  794. }
  795. void OBSBasic::OpenFilters(OBSSource source)
  796. {
  797. if (source == nullptr) {
  798. OBSSceneItem item = GetCurrentSceneItem();
  799. source = obs_sceneitem_get_source(item);
  800. }
  801. CreateFiltersWindow(source);
  802. }
  803. void OBSBasic::OpenProperties(OBSSource source)
  804. {
  805. if (source == nullptr) {
  806. OBSSceneItem item = GetCurrentSceneItem();
  807. source = obs_sceneitem_get_source(item);
  808. }
  809. CreatePropertiesWindow(source);
  810. }
  811. void OBSBasic::OpenInteraction(OBSSource source)
  812. {
  813. if (source == nullptr) {
  814. OBSSceneItem item = GetCurrentSceneItem();
  815. source = obs_sceneitem_get_source(item);
  816. }
  817. CreateInteractionWindow(source);
  818. }
  819. void OBSBasic::OpenEditTransform(OBSSceneItem item)
  820. {
  821. if (!item)
  822. item = GetCurrentSceneItem();
  823. if (!item)
  824. return;
  825. CreateEditTransformWindow(item);
  826. }
  827. int OBSBasic::GetTopSelectedSourceItem()
  828. {
  829. QModelIndexList selectedItems = ui->sources->selectionModel()->selectedIndexes();
  830. return selectedItems.count() ? selectedItems[0].row() : -1;
  831. }
  832. QModelIndexList OBSBasic::GetAllSelectedSourceItems()
  833. {
  834. return ui->sources->selectionModel()->selectedIndexes();
  835. }
  836. void OBSBasic::on_actionEditTransform_triggered()
  837. {
  838. const auto item = GetCurrentSceneItem();
  839. if (!item)
  840. return;
  841. CreateEditTransformWindow(item);
  842. }
  843. void undo_redo(const std::string &data)
  844. {
  845. OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str());
  846. OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid"));
  847. OBSBasic::Get()->SetCurrentScene(source.Get(), true);
  848. obs_scene_load_transform_states(data.c_str());
  849. }
  850. static void GetItemBox(obs_sceneitem_t *item, vec3 &tl, vec3 &br)
  851. {
  852. matrix4 boxTransform;
  853. obs_sceneitem_get_box_transform(item, &boxTransform);
  854. vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f);
  855. vec3_set(&br, -M_INFINITE, -M_INFINITE, 0.0f);
  856. auto GetMinPos = [&](float x, float y) {
  857. vec3 pos;
  858. vec3_set(&pos, x, y, 0.0f);
  859. vec3_transform(&pos, &pos, &boxTransform);
  860. vec3_min(&tl, &tl, &pos);
  861. vec3_max(&br, &br, &pos);
  862. };
  863. GetMinPos(0.0f, 0.0f);
  864. GetMinPos(1.0f, 0.0f);
  865. GetMinPos(0.0f, 1.0f);
  866. GetMinPos(1.0f, 1.0f);
  867. }
  868. static vec3 GetItemTL(obs_sceneitem_t *item)
  869. {
  870. vec3 tl, br;
  871. GetItemBox(item, tl, br);
  872. return tl;
  873. }
  874. static void SetItemTL(obs_sceneitem_t *item, const vec3 &tl)
  875. {
  876. vec3 newTL;
  877. vec2 pos;
  878. obs_sceneitem_get_pos(item, &pos);
  879. newTL = GetItemTL(item);
  880. pos.x += tl.x - newTL.x;
  881. pos.y += tl.y - newTL.y;
  882. obs_sceneitem_set_pos(item, &pos);
  883. }
  884. static bool RotateSelectedSources(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  885. {
  886. if (obs_sceneitem_is_group(item))
  887. obs_sceneitem_group_enum_items(item, RotateSelectedSources, param);
  888. if (!obs_sceneitem_selected(item))
  889. return true;
  890. if (obs_sceneitem_locked(item))
  891. return true;
  892. float rot = *static_cast<float *>(param);
  893. vec3 tl = GetItemTL(item);
  894. rot += obs_sceneitem_get_rot(item);
  895. if (rot >= 360.0f)
  896. rot -= 360.0f;
  897. else if (rot <= -360.0f)
  898. rot += 360.0f;
  899. obs_sceneitem_set_rot(item, rot);
  900. obs_sceneitem_force_update_transform(item);
  901. SetItemTL(item, tl);
  902. return true;
  903. };
  904. void OBSBasic::on_actionRotate90CW_triggered()
  905. {
  906. float f90CW = 90.0f;
  907. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  908. obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW);
  909. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  910. std::string undo_data(obs_data_get_json(wrapper));
  911. std::string redo_data(obs_data_get_json(rwrapper));
  912. undo_s.add_action(
  913. QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  914. undo_redo, undo_redo, undo_data, redo_data);
  915. }
  916. void OBSBasic::on_actionRotate90CCW_triggered()
  917. {
  918. float f90CCW = -90.0f;
  919. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  920. obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW);
  921. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  922. std::string undo_data(obs_data_get_json(wrapper));
  923. std::string redo_data(obs_data_get_json(rwrapper));
  924. undo_s.add_action(
  925. QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  926. undo_redo, undo_redo, undo_data, redo_data);
  927. }
  928. void OBSBasic::on_actionRotate180_triggered()
  929. {
  930. float f180 = 180.0f;
  931. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  932. obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180);
  933. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  934. std::string undo_data(obs_data_get_json(wrapper));
  935. std::string redo_data(obs_data_get_json(rwrapper));
  936. undo_s.add_action(
  937. QTStr("Undo.Transform.Rotate").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  938. undo_redo, undo_redo, undo_data, redo_data);
  939. }
  940. static bool MultiplySelectedItemScale(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  941. {
  942. vec2 &mul = *static_cast<vec2 *>(param);
  943. if (obs_sceneitem_is_group(item))
  944. obs_sceneitem_group_enum_items(item, MultiplySelectedItemScale, param);
  945. if (!obs_sceneitem_selected(item))
  946. return true;
  947. if (obs_sceneitem_locked(item))
  948. return true;
  949. vec3 tl = GetItemTL(item);
  950. vec2 scale;
  951. obs_sceneitem_get_scale(item, &scale);
  952. vec2_mul(&scale, &scale, &mul);
  953. obs_sceneitem_set_scale(item, &scale);
  954. obs_sceneitem_force_update_transform(item);
  955. SetItemTL(item, tl);
  956. return true;
  957. }
  958. void OBSBasic::on_actionFlipHorizontal_triggered()
  959. {
  960. vec2 scale;
  961. vec2_set(&scale, -1.0f, 1.0f);
  962. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  963. obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale);
  964. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  965. std::string undo_data(obs_data_get_json(wrapper));
  966. std::string redo_data(obs_data_get_json(rwrapper));
  967. undo_s.add_action(
  968. QTStr("Undo.Transform.HFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  969. undo_redo, undo_redo, undo_data, redo_data);
  970. }
  971. void OBSBasic::on_actionFlipVertical_triggered()
  972. {
  973. vec2 scale;
  974. vec2_set(&scale, 1.0f, -1.0f);
  975. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  976. obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, &scale);
  977. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  978. std::string undo_data(obs_data_get_json(wrapper));
  979. std::string redo_data(obs_data_get_json(rwrapper));
  980. undo_s.add_action(
  981. QTStr("Undo.Transform.VFlip").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  982. undo_redo, undo_redo, undo_data, redo_data);
  983. }
  984. static bool CenterAlignSelectedItems(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  985. {
  986. obs_bounds_type boundsType = *static_cast<obs_bounds_type *>(param);
  987. if (obs_sceneitem_is_group(item))
  988. obs_sceneitem_group_enum_items(item, CenterAlignSelectedItems, param);
  989. if (!obs_sceneitem_selected(item))
  990. return true;
  991. if (obs_sceneitem_locked(item))
  992. return true;
  993. obs_video_info ovi;
  994. obs_get_video_info(&ovi);
  995. obs_transform_info itemInfo;
  996. vec2_set(&itemInfo.pos, 0.0f, 0.0f);
  997. vec2_set(&itemInfo.scale, 1.0f, 1.0f);
  998. itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP;
  999. itemInfo.rot = 0.0f;
  1000. vec2_set(&itemInfo.bounds, float(ovi.base_width), float(ovi.base_height));
  1001. itemInfo.bounds_type = boundsType;
  1002. itemInfo.bounds_alignment = OBS_ALIGN_CENTER;
  1003. itemInfo.crop_to_bounds = obs_sceneitem_get_bounds_crop(item);
  1004. obs_sceneitem_set_info2(item, &itemInfo);
  1005. return true;
  1006. }
  1007. void OBSBasic::on_actionFitToScreen_triggered()
  1008. {
  1009. obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER;
  1010. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1011. obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType);
  1012. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1013. std::string undo_data(obs_data_get_json(wrapper));
  1014. std::string redo_data(obs_data_get_json(rwrapper));
  1015. undo_s.add_action(
  1016. QTStr("Undo.Transform.FitToScreen").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  1017. undo_redo, undo_redo, undo_data, redo_data);
  1018. }
  1019. void OBSBasic::on_actionStretchToScreen_triggered()
  1020. {
  1021. obs_bounds_type boundsType = OBS_BOUNDS_STRETCH;
  1022. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1023. obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, &boundsType);
  1024. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1025. std::string undo_data(obs_data_get_json(wrapper));
  1026. std::string redo_data(obs_data_get_json(rwrapper));
  1027. undo_s.add_action(QTStr("Undo.Transform.StretchToScreen")
  1028. .arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  1029. undo_redo, undo_redo, undo_data, redo_data);
  1030. }
  1031. void OBSBasic::CenterSelectedSceneItems(const CenterType &centerType)
  1032. {
  1033. QModelIndexList selectedItems = GetAllSelectedSourceItems();
  1034. if (!selectedItems.count())
  1035. return;
  1036. vector<OBSSceneItem> items;
  1037. // Filter out items that have no size
  1038. for (int x = 0; x < selectedItems.count(); x++) {
  1039. OBSSceneItem item = ui->sources->Get(selectedItems[x].row());
  1040. obs_transform_info oti;
  1041. obs_sceneitem_get_info2(item, &oti);
  1042. obs_source_t *source = obs_sceneitem_get_source(item);
  1043. float width = float(obs_source_get_width(source)) * oti.scale.x;
  1044. float height = float(obs_source_get_height(source)) * oti.scale.y;
  1045. if (width == 0.0f || height == 0.0f)
  1046. continue;
  1047. items.emplace_back(item);
  1048. }
  1049. if (!items.size())
  1050. return;
  1051. // Get center x, y coordinates of items
  1052. vec3 center;
  1053. float top = M_INFINITE;
  1054. float left = M_INFINITE;
  1055. float right = 0.0f;
  1056. float bottom = 0.0f;
  1057. for (auto &item : items) {
  1058. vec3 tl, br;
  1059. GetItemBox(item, tl, br);
  1060. left = std::min(tl.x, left);
  1061. top = std::min(tl.y, top);
  1062. right = std::max(br.x, right);
  1063. bottom = std::max(br.y, bottom);
  1064. }
  1065. center.x = (right + left) / 2.0f;
  1066. center.y = (top + bottom) / 2.0f;
  1067. center.z = 0.0f;
  1068. // Get coordinates of screen center
  1069. obs_video_info ovi;
  1070. obs_get_video_info(&ovi);
  1071. vec3 screenCenter;
  1072. vec3_set(&screenCenter, float(ovi.base_width), float(ovi.base_height), 0.0f);
  1073. vec3_mulf(&screenCenter, &screenCenter, 0.5f);
  1074. // Calculate difference between screen center and item center
  1075. vec3 offset;
  1076. vec3_sub(&offset, &screenCenter, &center);
  1077. // Shift items by offset
  1078. for (auto &item : items) {
  1079. vec3 tl, br;
  1080. GetItemBox(item, tl, br);
  1081. vec3_add(&tl, &tl, &offset);
  1082. vec3 itemTL = GetItemTL(item);
  1083. if (centerType == CenterType::Vertical)
  1084. tl.x = itemTL.x;
  1085. else if (centerType == CenterType::Horizontal)
  1086. tl.y = itemTL.y;
  1087. SetItemTL(item, tl);
  1088. }
  1089. }
  1090. void OBSBasic::on_actionCenterToScreen_triggered()
  1091. {
  1092. CenterType centerType = CenterType::Scene;
  1093. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1094. CenterSelectedSceneItems(centerType);
  1095. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1096. std::string undo_data(obs_data_get_json(wrapper));
  1097. std::string redo_data(obs_data_get_json(rwrapper));
  1098. undo_s.add_action(
  1099. QTStr("Undo.Transform.Center").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  1100. undo_redo, undo_redo, undo_data, redo_data);
  1101. }
  1102. void OBSBasic::on_actionVerticalCenter_triggered()
  1103. {
  1104. CenterType centerType = CenterType::Vertical;
  1105. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1106. CenterSelectedSceneItems(centerType);
  1107. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1108. std::string undo_data(obs_data_get_json(wrapper));
  1109. std::string redo_data(obs_data_get_json(rwrapper));
  1110. undo_s.add_action(
  1111. QTStr("Undo.Transform.VCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  1112. undo_redo, undo_redo, undo_data, redo_data);
  1113. }
  1114. void OBSBasic::on_actionHorizontalCenter_triggered()
  1115. {
  1116. CenterType centerType = CenterType::Horizontal;
  1117. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1118. CenterSelectedSceneItems(centerType);
  1119. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(GetCurrentScene(), false);
  1120. std::string undo_data(obs_data_get_json(wrapper));
  1121. std::string redo_data(obs_data_get_json(rwrapper));
  1122. undo_s.add_action(
  1123. QTStr("Undo.Transform.HCenter").arg(obs_source_get_name(obs_scene_get_source(GetCurrentScene()))),
  1124. undo_redo, undo_redo, undo_data, redo_data);
  1125. }
  1126. void OBSBasic::on_toggleSourceIcons_toggled(bool visible)
  1127. {
  1128. ui->sources->SetIconsVisible(visible);
  1129. if (advAudioWindow != nullptr)
  1130. advAudioWindow->SetIconsVisible(visible);
  1131. config_set_bool(App()->GetUserConfig(), "BasicWindow", "ShowSourceIcons", visible);
  1132. }
  1133. void OBSBasic::on_sourcePropertiesButton_clicked()
  1134. {
  1135. on_actionSourceProperties_triggered();
  1136. }
  1137. void OBSBasic::on_sourceFiltersButton_clicked()
  1138. {
  1139. OpenFilters();
  1140. }
  1141. void OBSBasic::on_sourceInteractButton_clicked()
  1142. {
  1143. on_actionInteract_triggered();
  1144. }