OBSBasicTransform.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. #include "OBSBasicTransform.hpp"
  2. #include <widgets/OBSBasic.hpp>
  3. #include "moc_OBSBasicTransform.cpp"
  4. namespace {
  5. static bool find_sel(obs_scene_t *, obs_sceneitem_t *item, void *param)
  6. {
  7. OBSSceneItem &dst = *static_cast<OBSSceneItem *>(param);
  8. if (obs_sceneitem_selected(item)) {
  9. dst = item;
  10. return false;
  11. }
  12. if (obs_sceneitem_is_group(item)) {
  13. obs_sceneitem_group_enum_items(item, find_sel, param);
  14. if (!!dst) {
  15. return false;
  16. }
  17. }
  18. return true;
  19. };
  20. static OBSSceneItem FindASelectedItem(obs_scene_t *scene)
  21. {
  22. OBSSceneItem item;
  23. obs_scene_enum_items(scene, find_sel, &item);
  24. return item;
  25. }
  26. static vec2 getAlignmentConversion(uint32_t alignment)
  27. {
  28. vec2 ratio = {0.5f, 0.5f};
  29. if (alignment & OBS_ALIGN_RIGHT) {
  30. ratio.x = 1.0f;
  31. }
  32. if (alignment & OBS_ALIGN_LEFT) {
  33. ratio.x = 0.0f;
  34. }
  35. if (alignment & OBS_ALIGN_BOTTOM) {
  36. ratio.y = 1.0f;
  37. }
  38. if (alignment & OBS_ALIGN_TOP) {
  39. ratio.y = 0.0f;
  40. }
  41. return ratio;
  42. }
  43. } // namespace
  44. #define COMBO_CHANGED &QComboBox::currentIndexChanged
  45. #define ALIGN_CHANGED &AlignmentSelector::currentIndexChanged
  46. #define ISCROLL_CHANGED &QSpinBox::valueChanged
  47. #define DSCROLL_CHANGED &QDoubleSpinBox::valueChanged
  48. OBSBasicTransform::OBSBasicTransform(OBSSceneItem item, OBSBasic *parent)
  49. : QDialog(parent),
  50. ui(new Ui::OBSBasicTransform),
  51. main(parent)
  52. {
  53. setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
  54. ui->setupUi(this);
  55. positionAlignment = new AlignmentSelector(this);
  56. positionAlignment->setAccessibleName(QTStr("Basic.TransformWindow.Alignment"));
  57. ui->alignmentLayout->addWidget(positionAlignment);
  58. positionAlignment->setAlignment(Qt::AlignTop | Qt::AlignLeft);
  59. boundsAlignment = new AlignmentSelector(this);
  60. boundsAlignment->setAccessibleName(QTStr("Basic.TransformWindow.BoundsAlignment"));
  61. boundsAlignment->setEnabled(false);
  62. ui->boundsAlignmentLayout->addWidget(boundsAlignment);
  63. boundsAlignment->setAlignment(Qt::AlignTop | Qt::AlignLeft);
  64. setTabOrder(ui->rotation, positionAlignment);
  65. setTabOrder(ui->boundsType, boundsAlignment);
  66. hookWidget(ui->positionX, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
  67. hookWidget(ui->positionY, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
  68. hookWidget(ui->rotation, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
  69. hookWidget(ui->sizeX, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
  70. hookWidget(ui->sizeY, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
  71. hookWidget(positionAlignment.get(), ALIGN_CHANGED, &OBSBasicTransform::onAlignChanged);
  72. hookWidget(ui->boundsType, COMBO_CHANGED, &OBSBasicTransform::onBoundsType);
  73. hookWidget(boundsAlignment.get(), ALIGN_CHANGED, &OBSBasicTransform::onControlChanged);
  74. hookWidget(ui->boundsWidth, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
  75. hookWidget(ui->boundsHeight, DSCROLL_CHANGED, &OBSBasicTransform::onControlChanged);
  76. hookWidget(ui->cropLeft, ISCROLL_CHANGED, &OBSBasicTransform::onCropChanged);
  77. hookWidget(ui->cropRight, ISCROLL_CHANGED, &OBSBasicTransform::onCropChanged);
  78. hookWidget(ui->cropTop, ISCROLL_CHANGED, &OBSBasicTransform::onCropChanged);
  79. hookWidget(ui->cropBottom, ISCROLL_CHANGED, &OBSBasicTransform::onCropChanged);
  80. #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
  81. hookWidget(ui->cropToBounds, &QCheckBox::checkStateChanged, &OBSBasicTransform::onControlChanged);
  82. #else
  83. hookWidget(ui->cropToBounds, &QCheckBox::stateChanged, &OBSBasicTransform::onControlChanged);
  84. #endif
  85. ui->buttonBox->button(QDialogButtonBox::Close)->setDefault(true);
  86. connect(ui->buttonBox->button(QDialogButtonBox::Reset), &QPushButton::clicked, main,
  87. &OBSBasic::on_actionResetTransform_triggered);
  88. installEventFilter(CreateShortcutFilter());
  89. OBSScene scene = obs_sceneitem_get_scene(item);
  90. setScene(scene);
  91. setItem(item);
  92. std::string name = obs_source_get_name(obs_sceneitem_get_source(item));
  93. setWindowTitle(QTStr("Basic.TransformWindow.Title").arg(name.c_str()));
  94. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(main->GetCurrentScene(), false);
  95. undo_data = std::string(obs_data_get_json(wrapper));
  96. adjustSize();
  97. setMinimumSize(size());
  98. setMaximumSize(size());
  99. }
  100. OBSBasicTransform::~OBSBasicTransform()
  101. {
  102. OBSDataAutoRelease wrapper = obs_scene_save_transform_states(main->GetCurrentScene(), false);
  103. auto undo_redo = [](const std::string &data) {
  104. OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str());
  105. OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid"));
  106. OBSBasic::Get()->SetCurrentScene(source.Get(), true);
  107. obs_scene_load_transform_states(data.c_str());
  108. };
  109. std::string redo_data(obs_data_get_json(wrapper));
  110. if (undo_data.compare(redo_data) != 0)
  111. main->undo_s.add_action(
  112. QTStr("Undo.Transform").arg(obs_source_get_name(obs_scene_get_source(main->GetCurrentScene()))),
  113. undo_redo, undo_redo, undo_data, redo_data);
  114. }
  115. void OBSBasicTransform::setScene(OBSScene scene)
  116. {
  117. sigs.clear();
  118. if (scene) {
  119. OBSSource source = obs_scene_get_source(scene);
  120. signal_handler_t *signal = obs_source_get_signal_handler(source);
  121. sigs.emplace_back(signal, "item_transform", OBSSceneItemTransform, this);
  122. sigs.emplace_back(signal, "item_remove", OBSSceneItemRemoved, this);
  123. sigs.emplace_back(signal, "item_select", OBSSceneItemSelect, this);
  124. sigs.emplace_back(signal, "item_deselect", OBSSceneItemDeselect, this);
  125. sigs.emplace_back(signal, "item_locked", OBSSceneItemLocked, this);
  126. }
  127. }
  128. void OBSBasicTransform::setItem(OBSSceneItem newItem)
  129. {
  130. QMetaObject::invokeMethod(this, "setItemQt", Q_ARG(OBSSceneItem, OBSSceneItem(newItem)));
  131. }
  132. void OBSBasicTransform::setEnabled(bool enable)
  133. {
  134. ui->transformSettings->setEnabled(enable);
  135. ui->boundsSettings->setEnabled(enable);
  136. ui->cropSettings->setEnabled(enable);
  137. ui->buttonBox->button(QDialogButtonBox::Reset)->setEnabled(enable);
  138. }
  139. void OBSBasicTransform::setItemQt(OBSSceneItem newItem)
  140. {
  141. item = newItem;
  142. if (item)
  143. refreshControls();
  144. bool enable = !!item && !obs_sceneitem_locked(item);
  145. setEnabled(enable);
  146. }
  147. void OBSBasicTransform::OBSSceneItemTransform(void *param, calldata_t *data)
  148. {
  149. OBSBasicTransform *window = static_cast<OBSBasicTransform *>(param);
  150. OBSSceneItem item = (obs_sceneitem_t *)calldata_ptr(data, "item");
  151. if (item == window->item && !window->ignoreTransformSignal)
  152. QMetaObject::invokeMethod(window, "refreshControls");
  153. }
  154. void OBSBasicTransform::OBSSceneItemRemoved(void *param, calldata_t *data)
  155. {
  156. OBSBasicTransform *window = static_cast<OBSBasicTransform *>(param);
  157. obs_scene_t *scene = (obs_scene_t *)calldata_ptr(data, "scene");
  158. obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(data, "item");
  159. if (item == window->item)
  160. window->setItem(FindASelectedItem(scene));
  161. }
  162. void OBSBasicTransform::OBSSceneItemSelect(void *param, calldata_t *data)
  163. {
  164. OBSBasicTransform *window = static_cast<OBSBasicTransform *>(param);
  165. OBSSceneItem item = (obs_sceneitem_t *)calldata_ptr(data, "item");
  166. if (item != window->item)
  167. window->setItem(item);
  168. }
  169. void OBSBasicTransform::OBSSceneItemDeselect(void *param, calldata_t *data)
  170. {
  171. OBSBasicTransform *window = static_cast<OBSBasicTransform *>(param);
  172. obs_scene_t *scene = (obs_scene_t *)calldata_ptr(data, "scene");
  173. obs_sceneitem_t *item = (obs_sceneitem_t *)calldata_ptr(data, "item");
  174. if (item == window->item) {
  175. window->setWindowTitle(QTStr("Basic.TransformWindow.NoSelectedSource"));
  176. window->setItem(FindASelectedItem(scene));
  177. }
  178. }
  179. void OBSBasicTransform::OBSSceneItemLocked(void *param, calldata_t *data)
  180. {
  181. OBSBasicTransform *window = static_cast<OBSBasicTransform *>(param);
  182. bool locked = calldata_bool(data, "locked");
  183. QMetaObject::invokeMethod(window, "setEnabled", Q_ARG(bool, !locked));
  184. }
  185. static const uint32_t indexToAlign[] = {OBS_ALIGN_TOP | OBS_ALIGN_LEFT,
  186. OBS_ALIGN_TOP,
  187. OBS_ALIGN_TOP | OBS_ALIGN_RIGHT,
  188. OBS_ALIGN_LEFT,
  189. OBS_ALIGN_CENTER,
  190. OBS_ALIGN_RIGHT,
  191. OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT,
  192. OBS_ALIGN_BOTTOM,
  193. OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT};
  194. static int alignToIndex(uint32_t align)
  195. {
  196. int index = 0;
  197. for (uint32_t curAlign : indexToAlign) {
  198. if (curAlign == align)
  199. return index;
  200. index++;
  201. }
  202. return 0;
  203. }
  204. void OBSBasicTransform::refreshControls()
  205. {
  206. if (!item)
  207. return;
  208. obs_transform_info oti;
  209. obs_sceneitem_crop crop;
  210. obs_sceneitem_get_info2(item, &oti);
  211. obs_sceneitem_get_crop(item, &crop);
  212. obs_source_t *source = obs_sceneitem_get_source(item);
  213. uint32_t source_cx = obs_source_get_width(source);
  214. uint32_t source_cy = obs_source_get_height(source);
  215. float width = float(source_cx);
  216. float height = float(source_cy);
  217. int alignIndex = alignToIndex(oti.alignment);
  218. int boundsAlignIndex = alignToIndex(oti.bounds_alignment);
  219. ignoreItemChange = true;
  220. ui->positionX->setValue(oti.pos.x);
  221. ui->positionY->setValue(oti.pos.y);
  222. ui->rotation->setValue(oti.rot);
  223. ui->sizeX->setValue(oti.scale.x * width);
  224. ui->sizeY->setValue(oti.scale.y * height);
  225. positionAlignment->setCurrentIndex(alignIndex);
  226. bool valid_size = source_cx != 0 && source_cy != 0;
  227. ui->sizeX->setEnabled(valid_size);
  228. ui->sizeY->setEnabled(valid_size);
  229. ui->boundsType->setCurrentIndex(int(oti.bounds_type));
  230. boundsAlignment->setCurrentIndex(boundsAlignIndex);
  231. ui->boundsWidth->setValue(oti.bounds.x);
  232. ui->boundsHeight->setValue(oti.bounds.y);
  233. ui->cropToBounds->setChecked(oti.crop_to_bounds);
  234. ui->cropLeft->setValue(int(crop.left));
  235. ui->cropRight->setValue(int(crop.right));
  236. ui->cropTop->setValue(int(crop.top));
  237. ui->cropBottom->setValue(int(crop.bottom));
  238. ignoreItemChange = false;
  239. std::string name = obs_source_get_name(source);
  240. setWindowTitle(QTStr("Basic.TransformWindow.Title").arg(name.c_str()));
  241. }
  242. void OBSBasicTransform::onAlignChanged(int index)
  243. {
  244. uint32_t alignment = indexToAlign[index];
  245. vec2 flipRatio = getAlignmentConversion(alignment);
  246. obs_transform_info oti;
  247. obs_sceneitem_crop crop;
  248. obs_sceneitem_get_info2(item, &oti);
  249. obs_sceneitem_get_crop(item, &crop);
  250. obs_source_t *source = obs_sceneitem_get_source(item);
  251. uint32_t sourceWidth = obs_source_get_width(source);
  252. uint32_t sourceHeight = obs_source_get_height(source);
  253. uint32_t widthForFlip = sourceWidth - crop.left - crop.right;
  254. uint32_t heightForFlip = sourceHeight - crop.top - crop.bottom;
  255. if (oti.bounds_type != OBS_BOUNDS_NONE) {
  256. widthForFlip = oti.bounds.x;
  257. heightForFlip = oti.bounds.y;
  258. }
  259. vec2 currentRatio = getAlignmentConversion(oti.alignment);
  260. float shiftX = (currentRatio.x - flipRatio.x) * widthForFlip * oti.scale.x;
  261. float shiftY = (currentRatio.y - flipRatio.y) * heightForFlip * oti.scale.y;
  262. bool previousIgnoreState = ignoreItemChange;
  263. ignoreItemChange = true;
  264. ui->positionX->setValue(oti.pos.x - shiftX);
  265. ui->positionY->setValue(oti.pos.y - shiftY);
  266. ignoreItemChange = previousIgnoreState;
  267. onControlChanged();
  268. }
  269. void OBSBasicTransform::onBoundsType(int index)
  270. {
  271. if (index == -1)
  272. return;
  273. obs_bounds_type type = (obs_bounds_type)index;
  274. bool enable = (type != OBS_BOUNDS_NONE);
  275. boundsAlignment->setEnabled(enable && type != OBS_BOUNDS_STRETCH);
  276. ui->boundsWidth->setEnabled(enable);
  277. ui->boundsHeight->setEnabled(enable);
  278. bool isCoverBounds = type == OBS_BOUNDS_SCALE_OUTER || type == OBS_BOUNDS_SCALE_TO_WIDTH ||
  279. type == OBS_BOUNDS_SCALE_TO_HEIGHT;
  280. ui->cropToBounds->setEnabled(isCoverBounds);
  281. if (!ignoreItemChange) {
  282. obs_bounds_type lastType = obs_sceneitem_get_bounds_type(item);
  283. if (lastType == OBS_BOUNDS_NONE) {
  284. OBSSource source = obs_sceneitem_get_source(item);
  285. int width = (int)obs_source_get_width(source);
  286. int height = (int)obs_source_get_height(source);
  287. vec2 scale;
  288. obs_sceneitem_get_scale(item, &scale);
  289. obs_sceneitem_crop crop;
  290. obs_sceneitem_get_crop(item, &crop);
  291. ui->sizeX->setValue(width);
  292. ui->sizeY->setValue(height);
  293. ui->boundsWidth->setValue((width - crop.left - crop.right) * scale.x);
  294. ui->boundsHeight->setValue((height - crop.top - crop.bottom) * scale.y);
  295. } else if (type == OBS_BOUNDS_NONE) {
  296. OBSSource source = obs_sceneitem_get_source(item);
  297. int width = (int)obs_source_get_width(source);
  298. int height = (int)obs_source_get_height(source);
  299. matrix4 draw;
  300. obs_sceneitem_get_draw_transform(item, &draw);
  301. ui->sizeX->setValue(width * draw.x.x);
  302. ui->sizeY->setValue(height * draw.y.y);
  303. obs_transform_info oti;
  304. obs_sceneitem_get_info2(item, &oti);
  305. // We use the draw transform values here which is always a top left coordinate origin.
  306. vec2 currentRatio = getAlignmentConversion(OBS_ALIGN_TOP | OBS_ALIGN_LEFT);
  307. vec2 flipRatio = getAlignmentConversion(oti.alignment);
  308. float drawX = draw.t.x;
  309. float drawY = draw.t.y;
  310. obs_sceneitem_crop crop;
  311. obs_sceneitem_get_crop(item, &crop);
  312. uint32_t widthForFlip = width - crop.left - crop.right;
  313. uint32_t heightForFlip = height - crop.top - crop.bottom;
  314. float shiftX = (currentRatio.x - flipRatio.x) * (widthForFlip * draw.x.x);
  315. float shiftY = (currentRatio.y - flipRatio.y) * (heightForFlip * draw.y.y);
  316. ui->positionX->setValue(oti.pos.x - (oti.pos.x - drawX) - shiftX);
  317. ui->positionY->setValue(oti.pos.y - (oti.pos.y - drawY) - shiftY);
  318. }
  319. }
  320. onControlChanged();
  321. }
  322. void OBSBasicTransform::onControlChanged()
  323. {
  324. if (ignoreItemChange)
  325. return;
  326. obs_source_t *source = obs_sceneitem_get_source(item);
  327. uint32_t source_cx = obs_source_get_width(source);
  328. uint32_t source_cy = obs_source_get_height(source);
  329. double width = double(source_cx);
  330. double height = double(source_cy);
  331. obs_transform_info oti;
  332. obs_sceneitem_get_info2(item, &oti);
  333. /* do not scale a source if it has 0 width/height */
  334. if (source_cx != 0 && source_cy != 0) {
  335. oti.scale.x = float(ui->sizeX->value() / width);
  336. oti.scale.y = float(ui->sizeY->value() / height);
  337. }
  338. oti.pos.x = float(ui->positionX->value());
  339. oti.pos.y = float(ui->positionY->value());
  340. oti.rot = float(ui->rotation->value());
  341. oti.alignment = indexToAlign[positionAlignment->currentIndex()];
  342. oti.bounds_type = (obs_bounds_type)ui->boundsType->currentIndex();
  343. oti.bounds_alignment = indexToAlign[boundsAlignment->currentIndex()];
  344. oti.bounds.x = float(ui->boundsWidth->value());
  345. oti.bounds.y = float(ui->boundsHeight->value());
  346. oti.crop_to_bounds = ui->cropToBounds->isChecked();
  347. ignoreTransformSignal = true;
  348. obs_sceneitem_set_info2(item, &oti);
  349. ignoreTransformSignal = false;
  350. }
  351. void OBSBasicTransform::onCropChanged()
  352. {
  353. if (ignoreItemChange)
  354. return;
  355. obs_sceneitem_crop crop;
  356. crop.left = uint32_t(ui->cropLeft->value());
  357. crop.right = uint32_t(ui->cropRight->value());
  358. crop.top = uint32_t(ui->cropTop->value());
  359. crop.bottom = uint32_t(ui->cropBottom->value());
  360. ignoreTransformSignal = true;
  361. obs_sceneitem_set_crop(item, &crop);
  362. ignoreTransformSignal = false;
  363. }
  364. void OBSBasicTransform::onSceneChanged(QListWidgetItem *current, QListWidgetItem *)
  365. {
  366. if (!current)
  367. return;
  368. OBSScene scene = GetOBSRef<OBSScene>(current);
  369. this->setScene(scene);
  370. }