window-basic-preview.cpp 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608
  1. #include <QGuiApplication>
  2. #include <QMouseEvent>
  3. #include <algorithm>
  4. #include <cmath>
  5. #include <string>
  6. #include <graphics/vec4.h>
  7. #include <graphics/matrix4.h>
  8. #include "window-basic-preview.hpp"
  9. #include "window-basic-main.hpp"
  10. #include "obs-app.hpp"
  11. #include "platform.hpp"
  12. #define HANDLE_RADIUS 4.0f
  13. #define HANDLE_SEL_RADIUS (HANDLE_RADIUS * 1.5f)
  14. #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
  15. #define SUPPORTS_FRACTIONAL_SCALING
  16. #endif
  17. /* TODO: make C++ math classes and clean up code here later */
  18. OBSBasicPreview::OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags)
  19. : OBSQTDisplay(parent, flags)
  20. {
  21. ResetScrollingOffset();
  22. setMouseTracking(true);
  23. }
  24. OBSBasicPreview::~OBSBasicPreview()
  25. {
  26. if (overflow) {
  27. obs_enter_graphics();
  28. gs_texture_destroy(overflow);
  29. obs_leave_graphics();
  30. }
  31. }
  32. vec2 OBSBasicPreview::GetMouseEventPos(QMouseEvent *event)
  33. {
  34. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  35. #ifdef SUPPORTS_FRACTIONAL_SCALING
  36. float pixelRatio = main->devicePixelRatioF();
  37. #else
  38. float pixelRatio = main->devicePixelRatio();
  39. #endif
  40. float scale = pixelRatio / main->previewScale;
  41. vec2 pos;
  42. vec2_set(&pos,
  43. (float(event->x()) - main->previewX / pixelRatio) * scale,
  44. (float(event->y()) - main->previewY / pixelRatio) * scale);
  45. return pos;
  46. }
  47. struct SceneFindData {
  48. const vec2 &pos;
  49. OBSSceneItem item;
  50. bool selectBelow;
  51. obs_sceneitem_t *group = nullptr;
  52. SceneFindData(const SceneFindData &) = delete;
  53. SceneFindData(SceneFindData &&) = delete;
  54. SceneFindData& operator=(const SceneFindData &) = delete;
  55. SceneFindData& operator=(SceneFindData &&) = delete;
  56. inline SceneFindData(const vec2 &pos_, bool selectBelow_)
  57. : pos (pos_),
  58. selectBelow (selectBelow_)
  59. {}
  60. };
  61. static bool SceneItemHasVideo(obs_sceneitem_t *item)
  62. {
  63. obs_source_t *source = obs_sceneitem_get_source(item);
  64. uint32_t flags = obs_source_get_output_flags(source);
  65. return (flags & OBS_SOURCE_VIDEO) != 0;
  66. }
  67. static bool CloseFloat(float a, float b, float epsilon=0.01)
  68. {
  69. using std::abs;
  70. return abs(a-b) <= epsilon;
  71. }
  72. static bool FindItemAtPos(obs_scene_t *scene, obs_sceneitem_t *item,
  73. void *param)
  74. {
  75. SceneFindData *data = reinterpret_cast<SceneFindData*>(param);
  76. matrix4 transform;
  77. matrix4 invTransform;
  78. vec3 transformedPos;
  79. vec3 pos3;
  80. vec3 pos3_;
  81. if (!SceneItemHasVideo(item))
  82. return true;
  83. if (obs_sceneitem_locked(item))
  84. return true;
  85. vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f);
  86. obs_sceneitem_get_box_transform(item, &transform);
  87. matrix4_inv(&invTransform, &transform);
  88. vec3_transform(&transformedPos, &pos3, &invTransform);
  89. vec3_transform(&pos3_, &transformedPos, &transform);
  90. if (CloseFloat(pos3.x, pos3_.x) && CloseFloat(pos3.y, pos3_.y) &&
  91. transformedPos.x >= 0.0f && transformedPos.x <= 1.0f &&
  92. transformedPos.y >= 0.0f && transformedPos.y <= 1.0f) {
  93. if (data->selectBelow && obs_sceneitem_selected(item)) {
  94. if (data->item)
  95. return false;
  96. else
  97. data->selectBelow = false;
  98. }
  99. data->item = item;
  100. }
  101. UNUSED_PARAMETER(scene);
  102. return true;
  103. }
  104. static vec3 GetTransformedPos(float x, float y, const matrix4 &mat)
  105. {
  106. vec3 result;
  107. vec3_set(&result, x, y, 0.0f);
  108. vec3_transform(&result, &result, &mat);
  109. return result;
  110. }
  111. static inline vec2 GetOBSScreenSize()
  112. {
  113. obs_video_info ovi;
  114. vec2 size;
  115. vec2_zero(&size);
  116. if (obs_get_video_info(&ovi)) {
  117. size.x = float(ovi.base_width);
  118. size.y = float(ovi.base_height);
  119. }
  120. return size;
  121. }
  122. vec3 OBSBasicPreview::GetSnapOffset(const vec3 &tl, const vec3 &br)
  123. {
  124. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  125. vec2 screenSize = GetOBSScreenSize();
  126. vec3 clampOffset;
  127. vec3_zero(&clampOffset);
  128. const bool snap = config_get_bool(GetGlobalConfig(),
  129. "BasicWindow", "SnappingEnabled");
  130. if (snap == false)
  131. return clampOffset;
  132. const bool screenSnap = config_get_bool(GetGlobalConfig(),
  133. "BasicWindow", "ScreenSnapping");
  134. const bool centerSnap = config_get_bool(GetGlobalConfig(),
  135. "BasicWindow", "CenterSnapping");
  136. const float clampDist = config_get_double(GetGlobalConfig(),
  137. "BasicWindow", "SnapDistance") / main->previewScale;
  138. const float centerX = br.x - (br.x - tl.x) / 2.0f;
  139. const float centerY = br.y - (br.y - tl.y) / 2.0f;
  140. // Left screen edge.
  141. if (screenSnap &&
  142. fabsf(tl.x) < clampDist)
  143. clampOffset.x = -tl.x;
  144. // Right screen edge.
  145. if (screenSnap &&
  146. fabsf(clampOffset.x) < EPSILON &&
  147. fabsf(screenSize.x - br.x) < clampDist)
  148. clampOffset.x = screenSize.x - br.x;
  149. // Horizontal center.
  150. if (centerSnap &&
  151. fabsf(screenSize.x - (br.x - tl.x)) > clampDist &&
  152. fabsf(screenSize.x / 2.0f - centerX) < clampDist)
  153. clampOffset.x = screenSize.x / 2.0f - centerX;
  154. // Top screen edge.
  155. if (screenSnap &&
  156. fabsf(tl.y) < clampDist)
  157. clampOffset.y = -tl.y;
  158. // Bottom screen edge.
  159. if (screenSnap &&
  160. fabsf(clampOffset.y) < EPSILON &&
  161. fabsf(screenSize.y - br.y) < clampDist)
  162. clampOffset.y = screenSize.y - br.y;
  163. // Vertical center.
  164. if (centerSnap &&
  165. fabsf(screenSize.y - (br.y - tl.y)) > clampDist &&
  166. fabsf(screenSize.y / 2.0f - centerY) < clampDist)
  167. clampOffset.y = screenSize.y / 2.0f - centerY;
  168. return clampOffset;
  169. }
  170. OBSSceneItem OBSBasicPreview::GetItemAtPos(const vec2 &pos, bool selectBelow)
  171. {
  172. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  173. OBSScene scene = main->GetCurrentScene();
  174. if (!scene)
  175. return OBSSceneItem();
  176. SceneFindData data(pos, selectBelow);
  177. obs_scene_enum_items(scene, FindItemAtPos, &data);
  178. return data.item;
  179. }
  180. static bool CheckItemSelected(obs_scene_t *scene, obs_sceneitem_t *item,
  181. void *param)
  182. {
  183. SceneFindData *data = reinterpret_cast<SceneFindData*>(param);
  184. matrix4 transform;
  185. vec3 transformedPos;
  186. vec3 pos3;
  187. if (!SceneItemHasVideo(item))
  188. return true;
  189. if (obs_sceneitem_is_group(item)) {
  190. data->group = item;
  191. obs_sceneitem_group_enum_items(item, CheckItemSelected, param);
  192. data->group = nullptr;
  193. if (data->item) {
  194. return false;
  195. }
  196. }
  197. vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f);
  198. obs_sceneitem_get_box_transform(item, &transform);
  199. if (data->group) {
  200. matrix4 parent_transform;
  201. obs_sceneitem_get_draw_transform(data->group, &parent_transform);
  202. matrix4_mul(&transform, &transform, &parent_transform);
  203. }
  204. matrix4_inv(&transform, &transform);
  205. vec3_transform(&transformedPos, &pos3, &transform);
  206. if (transformedPos.x >= 0.0f && transformedPos.x <= 1.0f &&
  207. transformedPos.y >= 0.0f && transformedPos.y <= 1.0f) {
  208. if (obs_sceneitem_selected(item)) {
  209. data->item = item;
  210. return false;
  211. }
  212. }
  213. UNUSED_PARAMETER(scene);
  214. return true;
  215. }
  216. bool OBSBasicPreview::SelectedAtPos(const vec2 &pos)
  217. {
  218. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  219. OBSScene scene = main->GetCurrentScene();
  220. if (!scene)
  221. return false;
  222. SceneFindData data(pos, false);
  223. obs_scene_enum_items(scene, CheckItemSelected, &data);
  224. return !!data.item;
  225. }
  226. struct HandleFindData {
  227. const vec2 &pos;
  228. const float radius;
  229. matrix4 parent_xform;
  230. OBSSceneItem item;
  231. ItemHandle handle = ItemHandle::None;
  232. HandleFindData(const HandleFindData &) = delete;
  233. HandleFindData(HandleFindData &&) = delete;
  234. HandleFindData& operator=(const HandleFindData &) = delete;
  235. HandleFindData& operator=(HandleFindData &&) = delete;
  236. inline HandleFindData(const vec2 &pos_, float scale)
  237. : pos (pos_),
  238. radius (HANDLE_SEL_RADIUS / scale)
  239. {
  240. matrix4_identity(&parent_xform);
  241. }
  242. inline HandleFindData(const HandleFindData &hfd,
  243. obs_sceneitem_t *parent)
  244. : pos (hfd.pos),
  245. radius (hfd.radius),
  246. item (hfd.item),
  247. handle (hfd.handle)
  248. {
  249. obs_sceneitem_get_draw_transform(parent, &parent_xform);
  250. }
  251. };
  252. static bool FindHandleAtPos(obs_scene_t *scene, obs_sceneitem_t *item,
  253. void *param)
  254. {
  255. HandleFindData &data = *reinterpret_cast<HandleFindData*>(param);
  256. if (!obs_sceneitem_selected(item)) {
  257. if (obs_sceneitem_is_group(item)) {
  258. HandleFindData newData(data, item);
  259. obs_sceneitem_group_enum_items(item, FindHandleAtPos,
  260. &newData);
  261. data.item = newData.item;
  262. data.handle = newData.handle;
  263. }
  264. return true;
  265. }
  266. matrix4 transform;
  267. vec3 pos3;
  268. float closestHandle = data.radius;
  269. vec3_set(&pos3, data.pos.x, data.pos.y, 0.0f);
  270. obs_sceneitem_get_box_transform(item, &transform);
  271. auto TestHandle = [&] (float x, float y, ItemHandle handle)
  272. {
  273. vec3 handlePos = GetTransformedPos(x, y, transform);
  274. vec3_transform(&handlePos, &handlePos, &data.parent_xform);
  275. float dist = vec3_dist(&handlePos, &pos3);
  276. if (dist < data.radius) {
  277. if (dist < closestHandle) {
  278. closestHandle = dist;
  279. data.handle = handle;
  280. data.item = item;
  281. }
  282. }
  283. };
  284. TestHandle(0.0f, 0.0f, ItemHandle::TopLeft);
  285. TestHandle(0.5f, 0.0f, ItemHandle::TopCenter);
  286. TestHandle(1.0f, 0.0f, ItemHandle::TopRight);
  287. TestHandle(0.0f, 0.5f, ItemHandle::CenterLeft);
  288. TestHandle(1.0f, 0.5f, ItemHandle::CenterRight);
  289. TestHandle(0.0f, 1.0f, ItemHandle::BottomLeft);
  290. TestHandle(0.5f, 1.0f, ItemHandle::BottomCenter);
  291. TestHandle(1.0f, 1.0f, ItemHandle::BottomRight);
  292. UNUSED_PARAMETER(scene);
  293. return true;
  294. }
  295. static vec2 GetItemSize(obs_sceneitem_t *item)
  296. {
  297. obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(item);
  298. vec2 size;
  299. if (boundsType != OBS_BOUNDS_NONE) {
  300. obs_sceneitem_get_bounds(item, &size);
  301. } else {
  302. obs_source_t *source = obs_sceneitem_get_source(item);
  303. obs_sceneitem_crop crop;
  304. vec2 scale;
  305. obs_sceneitem_get_scale(item, &scale);
  306. obs_sceneitem_get_crop(item, &crop);
  307. size.x = float(obs_source_get_width(source) -
  308. crop.left - crop.right) * scale.x;
  309. size.y = float(obs_source_get_height(source) -
  310. crop.top - crop.bottom) * scale.y;
  311. }
  312. return size;
  313. }
  314. void OBSBasicPreview::GetStretchHandleData(const vec2 &pos)
  315. {
  316. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  317. OBSScene scene = main->GetCurrentScene();
  318. if (!scene)
  319. return;
  320. #ifdef SUPPORTS_FRACTIONAL_SCALING
  321. float scale = main->previewScale / main->devicePixelRatioF();
  322. #else
  323. float scale = main->previewScale / main->devicePixelRatio();
  324. #endif
  325. vec2 scaled_pos = pos;
  326. vec2_divf(&scaled_pos, &scaled_pos, scale);
  327. HandleFindData data(scaled_pos, scale);
  328. obs_scene_enum_items(scene, FindHandleAtPos, &data);
  329. stretchItem = std::move(data.item);
  330. stretchHandle = data.handle;
  331. if (stretchHandle != ItemHandle::None) {
  332. matrix4 boxTransform;
  333. vec3 itemUL;
  334. float itemRot;
  335. stretchItemSize = GetItemSize(stretchItem);
  336. obs_sceneitem_get_box_transform(stretchItem, &boxTransform);
  337. itemRot = obs_sceneitem_get_rot(stretchItem);
  338. vec3_from_vec4(&itemUL, &boxTransform.t);
  339. /* build the item space conversion matrices */
  340. matrix4_identity(&itemToScreen);
  341. matrix4_rotate_aa4f(&itemToScreen, &itemToScreen,
  342. 0.0f, 0.0f, 1.0f, RAD(itemRot));
  343. matrix4_translate3f(&itemToScreen, &itemToScreen,
  344. itemUL.x, itemUL.y, 0.0f);
  345. matrix4_identity(&screenToItem);
  346. matrix4_translate3f(&screenToItem, &screenToItem,
  347. -itemUL.x, -itemUL.y, 0.0f);
  348. matrix4_rotate_aa4f(&screenToItem, &screenToItem,
  349. 0.0f, 0.0f, 1.0f, RAD(-itemRot));
  350. obs_sceneitem_get_crop(stretchItem, &startCrop);
  351. obs_sceneitem_get_pos(stretchItem, &startItemPos);
  352. obs_source_t *source = obs_sceneitem_get_source(stretchItem);
  353. cropSize.x = float(obs_source_get_width(source) -
  354. startCrop.left - startCrop.right);
  355. cropSize.y = float(obs_source_get_height(source) -
  356. startCrop.top - startCrop.bottom);
  357. stretchGroup = obs_sceneitem_get_group(scene, stretchItem);
  358. if (stretchGroup) {
  359. obs_sceneitem_get_draw_transform(stretchGroup,
  360. &invGroupTransform);
  361. matrix4_inv(&invGroupTransform,
  362. &invGroupTransform);
  363. obs_sceneitem_defer_group_resize_begin(stretchGroup);
  364. }
  365. }
  366. }
  367. void OBSBasicPreview::keyPressEvent(QKeyEvent *event)
  368. {
  369. if (!IsFixedScaling() || event->isAutoRepeat()) {
  370. OBSQTDisplay::keyPressEvent(event);
  371. return;
  372. }
  373. switch (event->key()) {
  374. case Qt::Key_Space:
  375. setCursor(Qt::OpenHandCursor);
  376. scrollMode = true;
  377. break;
  378. }
  379. OBSQTDisplay::keyPressEvent(event);
  380. }
  381. void OBSBasicPreview::keyReleaseEvent(QKeyEvent *event)
  382. {
  383. if (event->isAutoRepeat()) {
  384. OBSQTDisplay::keyReleaseEvent(event);
  385. return;
  386. }
  387. switch (event->key()) {
  388. case Qt::Key_Space:
  389. scrollMode = false;
  390. setCursor(Qt::ArrowCursor);
  391. break;
  392. }
  393. OBSQTDisplay::keyReleaseEvent(event);
  394. }
  395. void OBSBasicPreview::wheelEvent(QWheelEvent *event)
  396. {
  397. if (scrollMode && IsFixedScaling()
  398. && event->orientation() == Qt::Vertical) {
  399. if (event->delta() > 0)
  400. SetScalingLevel(scalingLevel + 1);
  401. else if (event->delta() < 0)
  402. SetScalingLevel(scalingLevel - 1);
  403. emit DisplayResized();
  404. }
  405. OBSQTDisplay::wheelEvent(event);
  406. }
  407. void OBSBasicPreview::mousePressEvent(QMouseEvent *event)
  408. {
  409. if (scrollMode && IsFixedScaling() &&
  410. event->button() == Qt::LeftButton) {
  411. setCursor(Qt::ClosedHandCursor);
  412. scrollingFrom.x = event->x();
  413. scrollingFrom.y = event->y();
  414. return;
  415. }
  416. if (event->button() == Qt::RightButton) {
  417. scrollMode = false;
  418. setCursor(Qt::ArrowCursor);
  419. }
  420. if (locked) {
  421. OBSQTDisplay::mousePressEvent(event);
  422. return;
  423. }
  424. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  425. #ifdef SUPPORTS_FRACTIONAL_SCALING
  426. float pixelRatio = main->devicePixelRatioF();
  427. #else
  428. float pixelRatio = main->devicePixelRatio();
  429. #endif
  430. float x = float(event->x()) - main->previewX / pixelRatio;
  431. float y = float(event->y()) - main->previewY / pixelRatio;
  432. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  433. bool altDown = (modifiers & Qt::AltModifier);
  434. OBSQTDisplay::mousePressEvent(event);
  435. if (event->button() != Qt::LeftButton &&
  436. event->button() != Qt::RightButton)
  437. return;
  438. if (event->button() == Qt::LeftButton)
  439. mouseDown = true;
  440. if (altDown)
  441. cropping = true;
  442. vec2_set(&startPos, x, y);
  443. GetStretchHandleData(startPos);
  444. vec2_divf(&startPos, &startPos, main->previewScale / pixelRatio);
  445. startPos.x = std::round(startPos.x);
  446. startPos.y = std::round(startPos.y);
  447. mouseOverItems = SelectedAtPos(startPos);
  448. vec2_zero(&lastMoveOffset);
  449. }
  450. static bool select_one(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
  451. {
  452. obs_sceneitem_t *selectedItem =
  453. reinterpret_cast<obs_sceneitem_t*>(param);
  454. if (obs_sceneitem_is_group(item))
  455. obs_sceneitem_group_enum_items(item, select_one, param);
  456. obs_sceneitem_select(item, (selectedItem == item));
  457. UNUSED_PARAMETER(scene);
  458. return true;
  459. }
  460. void OBSBasicPreview::DoSelect(const vec2 &pos)
  461. {
  462. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  463. OBSScene scene = main->GetCurrentScene();
  464. OBSSceneItem item = GetItemAtPos(pos, true);
  465. obs_scene_enum_items(scene, select_one, (obs_sceneitem_t*)item);
  466. }
  467. void OBSBasicPreview::DoCtrlSelect(const vec2 &pos)
  468. {
  469. OBSSceneItem item = GetItemAtPos(pos, false);
  470. if (!item)
  471. return;
  472. bool selected = obs_sceneitem_selected(item);
  473. obs_sceneitem_select(item, !selected);
  474. }
  475. void OBSBasicPreview::ProcessClick(const vec2 &pos)
  476. {
  477. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  478. if (modifiers & Qt::ControlModifier)
  479. DoCtrlSelect(pos);
  480. else
  481. DoSelect(pos);
  482. }
  483. void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event)
  484. {
  485. if (scrollMode)
  486. setCursor(Qt::OpenHandCursor);
  487. if (locked) {
  488. OBSQTDisplay::mouseReleaseEvent(event);
  489. return;
  490. }
  491. if (mouseDown) {
  492. vec2 pos = GetMouseEventPos(event);
  493. if (!mouseMoved)
  494. ProcessClick(pos);
  495. if (stretchGroup) {
  496. obs_sceneitem_defer_group_resize_end(stretchGroup);
  497. }
  498. stretchItem = nullptr;
  499. stretchGroup = nullptr;
  500. mouseDown = false;
  501. mouseMoved = false;
  502. cropping = false;
  503. OBSSceneItem item = GetItemAtPos(pos, true);
  504. hoveredPreviewItem = item;
  505. }
  506. }
  507. struct SelectedItemBounds {
  508. bool first = true;
  509. vec3 tl, br;
  510. };
  511. static bool AddItemBounds(obs_scene_t *scene, obs_sceneitem_t *item,
  512. void *param)
  513. {
  514. SelectedItemBounds *data = reinterpret_cast<SelectedItemBounds*>(param);
  515. vec3 t[4];
  516. auto add_bounds = [data, &t] ()
  517. {
  518. for (const vec3 &v : t) {
  519. if (data->first) {
  520. vec3_copy(&data->tl, &v);
  521. vec3_copy(&data->br, &v);
  522. data->first = false;
  523. } else {
  524. vec3_min(&data->tl, &data->tl, &v);
  525. vec3_max(&data->br, &data->br, &v);
  526. }
  527. }
  528. };
  529. if (obs_sceneitem_is_group(item)) {
  530. SelectedItemBounds sib;
  531. obs_sceneitem_group_enum_items(item, AddItemBounds, &sib);
  532. if (!sib.first) {
  533. matrix4 xform;
  534. obs_sceneitem_get_draw_transform(item, &xform);
  535. vec3_set(&t[0], sib.tl.x, sib.tl.y, 0.0f);
  536. vec3_set(&t[1], sib.tl.x, sib.br.y, 0.0f);
  537. vec3_set(&t[2], sib.br.x, sib.tl.y, 0.0f);
  538. vec3_set(&t[3], sib.br.x, sib.br.y, 0.0f);
  539. vec3_transform(&t[0], &t[0], &xform);
  540. vec3_transform(&t[1], &t[1], &xform);
  541. vec3_transform(&t[2], &t[2], &xform);
  542. vec3_transform(&t[3], &t[3], &xform);
  543. add_bounds();
  544. }
  545. }
  546. if (!obs_sceneitem_selected(item))
  547. return true;
  548. matrix4 boxTransform;
  549. obs_sceneitem_get_box_transform(item, &boxTransform);
  550. t[0] = GetTransformedPos(0.0f, 0.0f, boxTransform);
  551. t[1] = GetTransformedPos(1.0f, 0.0f, boxTransform);
  552. t[2] = GetTransformedPos(0.0f, 1.0f, boxTransform);
  553. t[3] = GetTransformedPos(1.0f, 1.0f, boxTransform);
  554. add_bounds();
  555. UNUSED_PARAMETER(scene);
  556. return true;
  557. }
  558. struct OffsetData {
  559. float clampDist;
  560. vec3 tl, br, offset;
  561. };
  562. static bool GetSourceSnapOffset(obs_scene_t *scene, obs_sceneitem_t *item,
  563. void *param)
  564. {
  565. OffsetData *data = reinterpret_cast<OffsetData*>(param);
  566. if (obs_sceneitem_selected(item))
  567. return true;
  568. matrix4 boxTransform;
  569. obs_sceneitem_get_box_transform(item, &boxTransform);
  570. vec3 t[4] = {
  571. GetTransformedPos(0.0f, 0.0f, boxTransform),
  572. GetTransformedPos(1.0f, 0.0f, boxTransform),
  573. GetTransformedPos(0.0f, 1.0f, boxTransform),
  574. GetTransformedPos(1.0f, 1.0f, boxTransform)
  575. };
  576. bool first = true;
  577. vec3 tl, br;
  578. vec3_zero(&tl);
  579. vec3_zero(&br);
  580. for (const vec3 &v : t) {
  581. if (first) {
  582. vec3_copy(&tl, &v);
  583. vec3_copy(&br, &v);
  584. first = false;
  585. } else {
  586. vec3_min(&tl, &tl, &v);
  587. vec3_max(&br, &br, &v);
  588. }
  589. }
  590. // Snap to other source edges
  591. #define EDGE_SNAP(l, r, x, y) \
  592. do { \
  593. double dist = fabsf(l.x - data->r.x); \
  594. if (dist < data->clampDist && \
  595. fabsf(data->offset.x) < EPSILON && \
  596. data->tl.y < br.y && \
  597. data->br.y > tl.y && \
  598. (fabsf(data->offset.x) > dist || data->offset.x < EPSILON)) \
  599. data->offset.x = l.x - data->r.x; \
  600. } while (false)
  601. EDGE_SNAP(tl, br, x, y);
  602. EDGE_SNAP(tl, br, y, x);
  603. EDGE_SNAP(br, tl, x, y);
  604. EDGE_SNAP(br, tl, y, x);
  605. #undef EDGE_SNAP
  606. UNUSED_PARAMETER(scene);
  607. return true;
  608. }
  609. void OBSBasicPreview::SnapItemMovement(vec2 &offset)
  610. {
  611. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  612. OBSScene scene = main->GetCurrentScene();
  613. SelectedItemBounds data;
  614. obs_scene_enum_items(scene, AddItemBounds, &data);
  615. data.tl.x += offset.x;
  616. data.tl.y += offset.y;
  617. data.br.x += offset.x;
  618. data.br.y += offset.y;
  619. vec3 snapOffset = GetSnapOffset(data.tl, data.br);
  620. const bool snap = config_get_bool(GetGlobalConfig(),
  621. "BasicWindow", "SnappingEnabled");
  622. const bool sourcesSnap = config_get_bool(GetGlobalConfig(),
  623. "BasicWindow", "SourceSnapping");
  624. if (snap == false)
  625. return;
  626. if (sourcesSnap == false) {
  627. offset.x += snapOffset.x;
  628. offset.y += snapOffset.y;
  629. return;
  630. }
  631. const float clampDist = config_get_double(GetGlobalConfig(),
  632. "BasicWindow", "SnapDistance") / main->previewScale;
  633. OffsetData offsetData;
  634. offsetData.clampDist = clampDist;
  635. offsetData.tl = data.tl;
  636. offsetData.br = data.br;
  637. vec3_copy(&offsetData.offset, &snapOffset);
  638. obs_scene_enum_items(scene, GetSourceSnapOffset, &offsetData);
  639. if (fabsf(offsetData.offset.x) > EPSILON ||
  640. fabsf(offsetData.offset.y) > EPSILON) {
  641. offset.x += offsetData.offset.x;
  642. offset.y += offsetData.offset.y;
  643. } else {
  644. offset.x += snapOffset.x;
  645. offset.y += snapOffset.y;
  646. }
  647. }
  648. static bool move_items(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
  649. {
  650. if (obs_sceneitem_locked(item))
  651. return true;
  652. bool selected = obs_sceneitem_selected(item);
  653. vec2 *offset = reinterpret_cast<vec2*>(param);
  654. if (obs_sceneitem_is_group(item) && !selected) {
  655. matrix4 transform;
  656. vec3 new_offset;
  657. vec3_set(&new_offset, offset->x, offset->y, 0.0f);
  658. obs_sceneitem_get_draw_transform(item, &transform);
  659. vec4_set(&transform.t, 0.0f, 0.0f, 0.0f, 1.0f);
  660. matrix4_inv(&transform, &transform);
  661. vec3_transform(&new_offset, &new_offset, &transform);
  662. obs_sceneitem_group_enum_items(item, move_items, &new_offset);
  663. }
  664. if (selected) {
  665. vec2 pos;
  666. obs_sceneitem_get_pos(item, &pos);
  667. vec2_add(&pos, &pos, offset);
  668. obs_sceneitem_set_pos(item, &pos);
  669. }
  670. UNUSED_PARAMETER(scene);
  671. return true;
  672. }
  673. void OBSBasicPreview::MoveItems(const vec2 &pos)
  674. {
  675. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  676. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  677. OBSScene scene = main->GetCurrentScene();
  678. vec2 offset, moveOffset;
  679. vec2_sub(&offset, &pos, &startPos);
  680. vec2_sub(&moveOffset, &offset, &lastMoveOffset);
  681. if (!(modifiers & Qt::ControlModifier))
  682. SnapItemMovement(moveOffset);
  683. vec2_add(&lastMoveOffset, &lastMoveOffset, &moveOffset);
  684. obs_scene_enum_items(scene, move_items, &moveOffset);
  685. }
  686. vec3 OBSBasicPreview::CalculateStretchPos(const vec3 &tl, const vec3 &br)
  687. {
  688. uint32_t alignment = obs_sceneitem_get_alignment(stretchItem);
  689. vec3 pos;
  690. vec3_zero(&pos);
  691. if (alignment & OBS_ALIGN_LEFT)
  692. pos.x = tl.x;
  693. else if (alignment & OBS_ALIGN_RIGHT)
  694. pos.x = br.x;
  695. else
  696. pos.x = (br.x - tl.x) * 0.5f + tl.x;
  697. if (alignment & OBS_ALIGN_TOP)
  698. pos.y = tl.y;
  699. else if (alignment & OBS_ALIGN_BOTTOM)
  700. pos.y = br.y;
  701. else
  702. pos.y = (br.y - tl.y) * 0.5f + tl.y;
  703. return pos;
  704. }
  705. void OBSBasicPreview::ClampAspect(vec3 &tl, vec3 &br, vec2 &size,
  706. const vec2 &baseSize)
  707. {
  708. float baseAspect = baseSize.x / baseSize.y;
  709. float aspect = size.x / size.y;
  710. uint32_t stretchFlags = (uint32_t)stretchHandle;
  711. if (stretchHandle == ItemHandle::TopLeft ||
  712. stretchHandle == ItemHandle::TopRight ||
  713. stretchHandle == ItemHandle::BottomLeft ||
  714. stretchHandle == ItemHandle::BottomRight) {
  715. if (aspect < baseAspect) {
  716. if ((size.y >= 0.0f && size.x >= 0.0f) ||
  717. (size.y <= 0.0f && size.x <= 0.0f))
  718. size.x = size.y * baseAspect;
  719. else
  720. size.x = size.y * baseAspect * -1.0f;
  721. } else {
  722. if ((size.y >= 0.0f && size.x >= 0.0f) ||
  723. (size.y <= 0.0f && size.x <= 0.0f))
  724. size.y = size.x / baseAspect;
  725. else
  726. size.y = size.x / baseAspect * -1.0f;
  727. }
  728. } else if (stretchHandle == ItemHandle::TopCenter ||
  729. stretchHandle == ItemHandle::BottomCenter) {
  730. if ((size.y >= 0.0f && size.x >= 0.0f) ||
  731. (size.y <= 0.0f && size.x <= 0.0f))
  732. size.x = size.y * baseAspect;
  733. else
  734. size.x = size.y * baseAspect * -1.0f;
  735. } else if (stretchHandle == ItemHandle::CenterLeft ||
  736. stretchHandle == ItemHandle::CenterRight) {
  737. if ((size.y >= 0.0f && size.x >= 0.0f) ||
  738. (size.y <= 0.0f && size.x <= 0.0f))
  739. size.y = size.x / baseAspect;
  740. else
  741. size.y = size.x / baseAspect * -1.0f;
  742. }
  743. size.x = std::round(size.x);
  744. size.y = std::round(size.y);
  745. if (stretchFlags & ITEM_LEFT)
  746. tl.x = br.x - size.x;
  747. else if (stretchFlags & ITEM_RIGHT)
  748. br.x = tl.x + size.x;
  749. if (stretchFlags & ITEM_TOP)
  750. tl.y = br.y - size.y;
  751. else if (stretchFlags & ITEM_BOTTOM)
  752. br.y = tl.y + size.y;
  753. }
  754. void OBSBasicPreview::SnapStretchingToScreen(vec3 &tl, vec3 &br)
  755. {
  756. uint32_t stretchFlags = (uint32_t)stretchHandle;
  757. vec3 newTL = GetTransformedPos(tl.x, tl.y, itemToScreen);
  758. vec3 newTR = GetTransformedPos(br.x, tl.y, itemToScreen);
  759. vec3 newBL = GetTransformedPos(tl.x, br.y, itemToScreen);
  760. vec3 newBR = GetTransformedPos(br.x, br.y, itemToScreen);
  761. vec3 boundingTL;
  762. vec3 boundingBR;
  763. vec3_copy(&boundingTL, &newTL);
  764. vec3_min(&boundingTL, &boundingTL, &newTR);
  765. vec3_min(&boundingTL, &boundingTL, &newBL);
  766. vec3_min(&boundingTL, &boundingTL, &newBR);
  767. vec3_copy(&boundingBR, &newTL);
  768. vec3_max(&boundingBR, &boundingBR, &newTR);
  769. vec3_max(&boundingBR, &boundingBR, &newBL);
  770. vec3_max(&boundingBR, &boundingBR, &newBR);
  771. vec3 offset = GetSnapOffset(boundingTL, boundingBR);
  772. vec3_add(&offset, &offset, &newTL);
  773. vec3_transform(&offset, &offset, &screenToItem);
  774. vec3_sub(&offset, &offset, &tl);
  775. if (stretchFlags & ITEM_LEFT)
  776. tl.x += offset.x;
  777. else if (stretchFlags & ITEM_RIGHT)
  778. br.x += offset.x;
  779. if (stretchFlags & ITEM_TOP)
  780. tl.y += offset.y;
  781. else if (stretchFlags & ITEM_BOTTOM)
  782. br.y += offset.y;
  783. }
  784. static float maxfunc(float x, float y)
  785. {
  786. return x > y ? x : y;
  787. }
  788. static float minfunc(float x, float y)
  789. {
  790. return x < y ? x : y;
  791. }
  792. void OBSBasicPreview::CropItem(const vec2 &pos)
  793. {
  794. obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(stretchItem);
  795. uint32_t stretchFlags = (uint32_t)stretchHandle;
  796. uint32_t align = obs_sceneitem_get_alignment(stretchItem);
  797. vec3 tl, br, pos3;
  798. vec3_zero(&tl);
  799. vec3_set(&br, stretchItemSize.x, stretchItemSize.y, 0.0f);
  800. vec3_set(&pos3, pos.x, pos.y, 0.0f);
  801. vec3_transform(&pos3, &pos3, &screenToItem);
  802. obs_sceneitem_crop crop = startCrop;
  803. vec2 scale;
  804. obs_sceneitem_get_scale(stretchItem, &scale);
  805. vec2 max_tl;
  806. vec2 max_br;
  807. vec2_set(&max_tl,
  808. float(-crop.left) * scale.x,
  809. float(-crop.top) * scale.y);
  810. vec2_set(&max_br,
  811. stretchItemSize.x + crop.right * scale.x,
  812. stretchItemSize.y + crop.bottom * scale.y);
  813. typedef std::function<float (float, float)> minmax_func_t;
  814. minmax_func_t min_x = scale.x < 0.0f ? maxfunc : minfunc;
  815. minmax_func_t min_y = scale.y < 0.0f ? maxfunc : minfunc;
  816. minmax_func_t max_x = scale.x < 0.0f ? minfunc : maxfunc;
  817. minmax_func_t max_y = scale.y < 0.0f ? minfunc : maxfunc;
  818. pos3.x = min_x(pos3.x, max_br.x);
  819. pos3.x = max_x(pos3.x, max_tl.x);
  820. pos3.y = min_y(pos3.y, max_br.y);
  821. pos3.y = max_y(pos3.y, max_tl.y);
  822. if (stretchFlags & ITEM_LEFT) {
  823. float maxX = stretchItemSize.x - (2.0 * scale.x);
  824. pos3.x = tl.x = min_x(pos3.x, maxX);
  825. } else if (stretchFlags & ITEM_RIGHT) {
  826. float minX = (2.0 * scale.x);
  827. pos3.x = br.x = max_x(pos3.x, minX);
  828. }
  829. if (stretchFlags & ITEM_TOP) {
  830. float maxY = stretchItemSize.y - (2.0 * scale.y);
  831. pos3.y = tl.y = min_y(pos3.y, maxY);
  832. } else if (stretchFlags & ITEM_BOTTOM) {
  833. float minY = (2.0 * scale.y);
  834. pos3.y = br.y = max_y(pos3.y, minY);
  835. }
  836. #define ALIGN_X (ITEM_LEFT|ITEM_RIGHT)
  837. #define ALIGN_Y (ITEM_TOP|ITEM_BOTTOM)
  838. vec3 newPos;
  839. vec3_zero(&newPos);
  840. uint32_t align_x = (align & ALIGN_X);
  841. uint32_t align_y = (align & ALIGN_Y);
  842. if (align_x == (stretchFlags & ALIGN_X) && align_x != 0)
  843. newPos.x = pos3.x;
  844. else if (align & ITEM_RIGHT)
  845. newPos.x = stretchItemSize.x;
  846. else if (!(align & ITEM_LEFT))
  847. newPos.x = stretchItemSize.x * 0.5f;
  848. if (align_y == (stretchFlags & ALIGN_Y) && align_y != 0)
  849. newPos.y = pos3.y;
  850. else if (align & ITEM_BOTTOM)
  851. newPos.y = stretchItemSize.y;
  852. else if (!(align & ITEM_TOP))
  853. newPos.y = stretchItemSize.y * 0.5f;
  854. #undef ALIGN_X
  855. #undef ALIGN_Y
  856. crop = startCrop;
  857. if (stretchFlags & ITEM_LEFT)
  858. crop.left += int(std::round(tl.x / scale.x));
  859. else if (stretchFlags & ITEM_RIGHT)
  860. crop.right += int(std::round((stretchItemSize.x - br.x) / scale.x));
  861. if (stretchFlags & ITEM_TOP)
  862. crop.top += int(std::round(tl.y / scale.y));
  863. else if (stretchFlags & ITEM_BOTTOM)
  864. crop.bottom += int(std::round((stretchItemSize.y - br.y) / scale.y));
  865. vec3_transform(&newPos, &newPos, &itemToScreen);
  866. newPos.x = std::round(newPos.x);
  867. newPos.y = std::round(newPos.y);
  868. #if 0
  869. vec3 curPos;
  870. vec3_zero(&curPos);
  871. obs_sceneitem_get_pos(stretchItem, (vec2*)&curPos);
  872. blog(LOG_DEBUG, "curPos {%d, %d} - newPos {%d, %d}",
  873. int(curPos.x), int(curPos.y),
  874. int(newPos.x), int(newPos.y));
  875. blog(LOG_DEBUG, "crop {%d, %d, %d, %d}",
  876. crop.left, crop.top,
  877. crop.right, crop.bottom);
  878. #endif
  879. obs_sceneitem_defer_update_begin(stretchItem);
  880. obs_sceneitem_set_crop(stretchItem, &crop);
  881. if (boundsType == OBS_BOUNDS_NONE)
  882. obs_sceneitem_set_pos(stretchItem, (vec2*)&newPos);
  883. obs_sceneitem_defer_update_end(stretchItem);
  884. }
  885. void OBSBasicPreview::StretchItem(const vec2 &pos)
  886. {
  887. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  888. obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(stretchItem);
  889. uint32_t stretchFlags = (uint32_t)stretchHandle;
  890. bool shiftDown = (modifiers & Qt::ShiftModifier);
  891. vec3 tl, br, pos3;
  892. vec3_zero(&tl);
  893. vec3_set(&br, stretchItemSize.x, stretchItemSize.y, 0.0f);
  894. vec3_set(&pos3, pos.x, pos.y, 0.0f);
  895. vec3_transform(&pos3, &pos3, &screenToItem);
  896. if (stretchFlags & ITEM_LEFT)
  897. tl.x = pos3.x;
  898. else if (stretchFlags & ITEM_RIGHT)
  899. br.x = pos3.x;
  900. if (stretchFlags & ITEM_TOP)
  901. tl.y = pos3.y;
  902. else if (stretchFlags & ITEM_BOTTOM)
  903. br.y = pos3.y;
  904. if (!(modifiers & Qt::ControlModifier))
  905. SnapStretchingToScreen(tl, br);
  906. obs_source_t *source = obs_sceneitem_get_source(stretchItem);
  907. vec2 baseSize;
  908. vec2_set(&baseSize,
  909. float(obs_source_get_width(source)),
  910. float(obs_source_get_height(source)));
  911. vec2 size;
  912. vec2_set(&size,br. x - tl.x, br.y - tl.y);
  913. if (boundsType != OBS_BOUNDS_NONE) {
  914. if (shiftDown)
  915. ClampAspect(tl, br, size, baseSize);
  916. if (tl.x > br.x) std::swap(tl.x, br.x);
  917. if (tl.y > br.y) std::swap(tl.y, br.y);
  918. vec2_abs(&size, &size);
  919. obs_sceneitem_set_bounds(stretchItem, &size);
  920. } else {
  921. obs_sceneitem_crop crop;
  922. obs_sceneitem_get_crop(stretchItem, &crop);
  923. baseSize.x -= float(crop.left + crop.right);
  924. baseSize.y -= float(crop.top + crop.bottom);
  925. if (!shiftDown)
  926. ClampAspect(tl, br, size, baseSize);
  927. vec2_div(&size, &size, &baseSize);
  928. obs_sceneitem_set_scale(stretchItem, &size);
  929. }
  930. pos3 = CalculateStretchPos(tl, br);
  931. vec3_transform(&pos3, &pos3, &itemToScreen);
  932. vec2 newPos;
  933. vec2_set(&newPos, std::round(pos3.x), std::round(pos3.y));
  934. obs_sceneitem_set_pos(stretchItem, &newPos);
  935. }
  936. void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event)
  937. {
  938. if (scrollMode && event->buttons() == Qt::LeftButton) {
  939. scrollingOffset.x += event->x() - scrollingFrom.x;
  940. scrollingOffset.y += event->y() - scrollingFrom.y;
  941. scrollingFrom.x = event->x();
  942. scrollingFrom.y = event->y();
  943. emit DisplayResized();
  944. return;
  945. }
  946. if (locked)
  947. return;
  948. if (mouseDown) {
  949. hoveredPreviewItem = nullptr;
  950. vec2 pos = GetMouseEventPos(event);
  951. if (!mouseMoved && !mouseOverItems &&
  952. stretchHandle == ItemHandle::None) {
  953. ProcessClick(startPos);
  954. mouseOverItems = SelectedAtPos(startPos);
  955. }
  956. pos.x = std::round(pos.x);
  957. pos.y = std::round(pos.y);
  958. if (stretchHandle != ItemHandle::None) {
  959. OBSBasic *main = reinterpret_cast<OBSBasic*>(
  960. App()->GetMainWindow());
  961. OBSScene scene = main->GetCurrentScene();
  962. obs_sceneitem_t *group = obs_sceneitem_get_group(
  963. scene, stretchItem);
  964. if (group) {
  965. vec3 group_pos;
  966. vec3_set(&group_pos, pos.x, pos.y, 0.0f);
  967. vec3_transform(&group_pos, &group_pos,
  968. &invGroupTransform);
  969. pos.x = group_pos.x;
  970. pos.y = group_pos.y;
  971. }
  972. if (cropping)
  973. CropItem(pos);
  974. else
  975. StretchItem(pos);
  976. } else if (mouseOverItems) {
  977. MoveItems(pos);
  978. }
  979. mouseMoved = true;
  980. } else {
  981. vec2 pos = GetMouseEventPos(event);
  982. OBSSceneItem item = GetItemAtPos(pos, true);
  983. hoveredPreviewItem = item;
  984. }
  985. }
  986. void OBSBasicPreview::leaveEvent(QEvent *event)
  987. {
  988. hoveredPreviewItem = nullptr;
  989. UNUSED_PARAMETER(event);
  990. }
  991. static void DrawSquareAtPos(float x, float y)
  992. {
  993. struct vec3 pos;
  994. vec3_set(&pos, x, y, 0.0f);
  995. struct matrix4 matrix;
  996. gs_matrix_get(&matrix);
  997. vec3_transform(&pos, &pos, &matrix);
  998. gs_matrix_push();
  999. gs_matrix_identity();
  1000. gs_matrix_translate(&pos);
  1001. gs_matrix_translate3f(-HANDLE_RADIUS, -HANDLE_RADIUS, 0.0f);
  1002. gs_matrix_scale3f(HANDLE_RADIUS*2, HANDLE_RADIUS*2, 1.0f);
  1003. gs_draw(GS_TRISTRIP, 0, 0);
  1004. gs_matrix_pop();
  1005. }
  1006. static void DrawLine(float x1, float y1, float x2, float y2, float thickness)
  1007. {
  1008. struct matrix4 matrix;
  1009. gs_matrix_get(&matrix);
  1010. float ySide = (y1 == y2) ? (y1 < 0.5f ? 1.0f : -1.0f) : 0.0f;
  1011. float xSide = (x1 == x2) ? (x1 < 0.5f ? 1.0f : -1.0f) : 0.0f;
  1012. gs_render_start(true);
  1013. gs_vertex2f(x1, y1);
  1014. gs_vertex2f(x1 + (xSide * (thickness / matrix.x.x)),
  1015. y1 + (ySide * (thickness / matrix.y.y)));
  1016. gs_vertex2f(x2 + (xSide * (thickness / matrix.x.x)),
  1017. y2 + (ySide * (thickness / matrix.y.y)));
  1018. gs_vertex2f(x2, y2);
  1019. gs_vertex2f(x1, y1);
  1020. gs_vertbuffer_t *line = gs_render_save();
  1021. gs_load_vertexbuffer(line);
  1022. gs_draw(GS_TRISTRIP, 0, 0);
  1023. gs_vertexbuffer_destroy(line);
  1024. }
  1025. static void DrawRect(float thickness)
  1026. {
  1027. struct matrix4 matrix;
  1028. gs_matrix_get(&matrix);
  1029. gs_render_start(true);
  1030. gs_vertex2f(0.0f, 0.0f);
  1031. gs_vertex2f(0.0f + (thickness / matrix.x.x), 0.0f);
  1032. gs_vertex2f(0.0f + (thickness / matrix.x.x), 1.0f);
  1033. gs_vertex2f(0.0f, 1.0f);
  1034. gs_vertex2f(0.0f, 0.0f);
  1035. gs_vertex2f(0.0f, 1.0f);
  1036. gs_vertex2f(0.0f, 1.0f - (thickness / matrix.y.y));
  1037. gs_vertex2f(1.0f, 1.0f - (thickness / matrix.y.y));
  1038. gs_vertex2f(1.0f, 1.0f);
  1039. gs_vertex2f(0.0f, 1.0f);
  1040. gs_vertex2f(1.0f, 1.0f);
  1041. gs_vertex2f(1.0f - (thickness / matrix.x.x), 1.0f);
  1042. gs_vertex2f(1.0f - (thickness / matrix.x.x), 0.0f);
  1043. gs_vertex2f(1.0f, 0.0f);
  1044. gs_vertex2f(1.0f, 1.0f);
  1045. gs_vertex2f(1.0f, 0.0f);
  1046. gs_vertex2f(1.0f, 0.0f + (thickness / matrix.y.y));
  1047. gs_vertex2f(0.0f, 0.0f + (thickness / matrix.y.y));
  1048. gs_vertex2f(0.0f, 0.0f);
  1049. gs_vertex2f(1.0f, 0.0f);
  1050. gs_vertbuffer_t *rect = gs_render_save();
  1051. gs_load_vertexbuffer(rect);
  1052. gs_draw(GS_TRISTRIP, 0, 0);
  1053. gs_vertexbuffer_destroy(rect);
  1054. }
  1055. static inline bool crop_enabled(const obs_sceneitem_crop *crop)
  1056. {
  1057. return crop->left > 0 ||
  1058. crop->top > 0 ||
  1059. crop->right > 0 ||
  1060. crop->bottom > 0;
  1061. }
  1062. bool OBSBasicPreview::DrawSelectedOverflow(obs_scene_t *scene,
  1063. obs_sceneitem_t *item, void *param)
  1064. {
  1065. if (obs_sceneitem_locked(item))
  1066. return true;
  1067. if (!SceneItemHasVideo(item))
  1068. return true;
  1069. bool select = config_get_bool(GetGlobalConfig(), "BasicWindow",
  1070. "OverflowSelectionHidden");
  1071. if (!select && !obs_sceneitem_visible(item))
  1072. return true;
  1073. if (obs_sceneitem_is_group(item)) {
  1074. matrix4 mat;
  1075. obs_sceneitem_get_draw_transform(item, &mat);
  1076. gs_matrix_push();
  1077. gs_matrix_mul(&mat);
  1078. obs_sceneitem_group_enum_items(item, DrawSelectedOverflow, param);
  1079. gs_matrix_pop();
  1080. }
  1081. bool always = config_get_bool(GetGlobalConfig(), "BasicWindow",
  1082. "OverflowAlwaysVisible");
  1083. if (!always && !obs_sceneitem_selected(item))
  1084. return true;
  1085. OBSBasicPreview *prev = reinterpret_cast<OBSBasicPreview*>(param);
  1086. matrix4 boxTransform;
  1087. matrix4 invBoxTransform;
  1088. obs_sceneitem_get_box_transform(item, &boxTransform);
  1089. matrix4_inv(&invBoxTransform, &boxTransform);
  1090. vec3 bounds[] = {
  1091. {{{0.f, 0.f, 0.f}}},
  1092. {{{1.f, 0.f, 0.f}}},
  1093. {{{0.f, 1.f, 0.f}}},
  1094. {{{1.f, 1.f, 0.f}}},
  1095. };
  1096. bool visible = std::all_of(std::begin(bounds), std::end(bounds),
  1097. [&](const vec3 &b)
  1098. {
  1099. vec3 pos;
  1100. vec3_transform(&pos, &b, &boxTransform);
  1101. vec3_transform(&pos, &pos, &invBoxTransform);
  1102. return CloseFloat(pos.x, b.x) && CloseFloat(pos.y, b.y);
  1103. });
  1104. if (!visible)
  1105. return true;
  1106. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawSelectedOverflow");
  1107. obs_transform_info info;
  1108. obs_sceneitem_get_info(item, &info);
  1109. gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_REPEAT);
  1110. gs_eparam_t *image = gs_effect_get_param_by_name(solid, "image");
  1111. gs_eparam_t *scale = gs_effect_get_param_by_name(solid, "scale");
  1112. vec2 s;
  1113. vec2_set(&s, boxTransform.x.x / 96, boxTransform.y.y / 96);
  1114. gs_effect_set_vec2(scale, &s);
  1115. gs_effect_set_texture(image, prev->overflow);
  1116. gs_matrix_push();
  1117. gs_matrix_mul(&boxTransform);
  1118. obs_sceneitem_crop crop;
  1119. obs_sceneitem_get_crop(item, &crop);
  1120. while (gs_effect_loop(solid, "Draw")) {
  1121. gs_draw_sprite(prev->overflow, 0, 1, 1);
  1122. }
  1123. gs_matrix_pop();
  1124. GS_DEBUG_MARKER_END();
  1125. UNUSED_PARAMETER(scene);
  1126. return true;
  1127. }
  1128. bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *scene,
  1129. obs_sceneitem_t *item, void *param)
  1130. {
  1131. if (obs_sceneitem_locked(item))
  1132. return true;
  1133. if (!SceneItemHasVideo(item))
  1134. return true;
  1135. if (obs_sceneitem_is_group(item)) {
  1136. matrix4 mat;
  1137. obs_sceneitem_get_draw_transform(item, &mat);
  1138. gs_matrix_push();
  1139. gs_matrix_mul(&mat);
  1140. obs_sceneitem_group_enum_items(item, DrawSelectedItem, param);
  1141. gs_matrix_pop();
  1142. }
  1143. OBSBasicPreview *prev = reinterpret_cast<OBSBasicPreview*>(param);
  1144. OBSBasic *main = OBSBasic::Get();
  1145. bool hovered = prev->hoveredPreviewItem == item ||
  1146. prev->hoveredListItem == item;
  1147. bool selected = obs_sceneitem_selected(item);
  1148. if (!selected && !hovered)
  1149. return true;
  1150. matrix4 boxTransform;
  1151. matrix4 invBoxTransform;
  1152. obs_sceneitem_get_box_transform(item, &boxTransform);
  1153. matrix4_inv(&invBoxTransform, &boxTransform);
  1154. vec3 bounds[] = {
  1155. {{{0.f, 0.f, 0.f}}},
  1156. {{{1.f, 0.f, 0.f}}},
  1157. {{{0.f, 1.f, 0.f}}},
  1158. {{{1.f, 1.f, 0.f}}},
  1159. };
  1160. vec4 red;
  1161. vec4 green;
  1162. vec4 blue;
  1163. vec4_set(&red, 1.0f, 0.0f, 0.0f, 1.0f);
  1164. vec4_set(&green, 0.0f, 1.0f, 0.0f, 1.0f);
  1165. vec4_set(&blue, 0.0f, 0.5f, 1.0f, 1.0f);
  1166. bool visible = std::all_of(std::begin(bounds), std::end(bounds),
  1167. [&](const vec3 &b)
  1168. {
  1169. vec3 pos;
  1170. vec3_transform(&pos, &b, &boxTransform);
  1171. vec3_transform(&pos, &pos, &invBoxTransform);
  1172. return CloseFloat(pos.x, b.x) && CloseFloat(pos.y, b.y);
  1173. });
  1174. if (!visible)
  1175. return true;
  1176. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawSelectedItem");
  1177. obs_transform_info info;
  1178. obs_sceneitem_get_info(item, &info);
  1179. gs_matrix_push();
  1180. gs_matrix_mul(&boxTransform);
  1181. obs_sceneitem_crop crop;
  1182. obs_sceneitem_get_crop(item, &crop);
  1183. gs_effect_t *eff = gs_get_effect();
  1184. gs_eparam_t *colParam = gs_effect_get_param_by_name(eff, "color");
  1185. if (info.bounds_type == OBS_BOUNDS_NONE && crop_enabled(&crop)) {
  1186. #define DRAW_SIDE(side, x1, y1, x2, y2) \
  1187. if (hovered && !selected) \
  1188. gs_effect_set_vec4(colParam, &blue); \
  1189. else if (crop.side > 0) \
  1190. gs_effect_set_vec4(colParam, &green); \
  1191. DrawLine(x1, y1, x2, y2, HANDLE_RADIUS / 2); \
  1192. gs_effect_set_vec4(colParam, &red);
  1193. DRAW_SIDE(left, 0.0f, 0.0f, 0.0f, 1.0f);
  1194. DRAW_SIDE(top, 0.0f, 0.0f, 1.0f, 0.0f);
  1195. DRAW_SIDE(right, 1.0f, 0.0f, 1.0f, 1.0f);
  1196. DRAW_SIDE(bottom, 0.0f, 1.0f, 1.0f, 1.0f);
  1197. #undef DRAW_SIDE
  1198. } else {
  1199. if (!selected) {
  1200. gs_effect_set_vec4(colParam, &blue);
  1201. DrawRect(HANDLE_RADIUS / 2);
  1202. } else {
  1203. DrawRect(HANDLE_RADIUS / 2);
  1204. }
  1205. }
  1206. gs_load_vertexbuffer(main->box);
  1207. gs_effect_set_vec4(colParam, &red);
  1208. if (selected) {
  1209. DrawSquareAtPos(0.0f, 0.0f);
  1210. DrawSquareAtPos(0.0f, 1.0f);
  1211. DrawSquareAtPos(1.0f, 0.0f);
  1212. DrawSquareAtPos(1.0f, 1.0f);
  1213. DrawSquareAtPos(0.5f, 0.0f);
  1214. DrawSquareAtPos(0.0f, 0.5f);
  1215. DrawSquareAtPos(0.5f, 1.0f);
  1216. DrawSquareAtPos(1.0f, 0.5f);
  1217. }
  1218. gs_matrix_pop();
  1219. GS_DEBUG_MARKER_END();
  1220. UNUSED_PARAMETER(scene);
  1221. UNUSED_PARAMETER(param);
  1222. return true;
  1223. }
  1224. void OBSBasicPreview::DrawOverflow()
  1225. {
  1226. if (locked)
  1227. return;
  1228. bool hidden = config_get_bool(GetGlobalConfig(), "BasicWindow",
  1229. "OverflowHidden");
  1230. if (hidden)
  1231. return;
  1232. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawOverflow");
  1233. if (!overflow) {
  1234. std::string path;
  1235. GetDataFilePath("images/overflow.png", path);
  1236. overflow = gs_texture_create_from_file(path.c_str());
  1237. }
  1238. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  1239. OBSScene scene = main->GetCurrentScene();
  1240. if (scene) {
  1241. gs_matrix_push();
  1242. gs_matrix_scale3f(main->previewScale, main->previewScale, 1.0f);
  1243. obs_scene_enum_items(scene, DrawSelectedOverflow, this);
  1244. gs_matrix_pop();
  1245. }
  1246. gs_load_vertexbuffer(nullptr);
  1247. GS_DEBUG_MARKER_END();
  1248. }
  1249. void OBSBasicPreview::DrawSceneEditing()
  1250. {
  1251. if (locked)
  1252. return;
  1253. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawSceneEditing");
  1254. OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
  1255. gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
  1256. gs_technique_t *tech = gs_effect_get_technique(solid, "Solid");
  1257. vec4 color;
  1258. vec4_set(&color, 1.0f, 0.0f, 0.0f, 1.0f);
  1259. gs_effect_set_vec4(gs_effect_get_param_by_name(solid, "color"), &color);
  1260. gs_technique_begin(tech);
  1261. gs_technique_begin_pass(tech, 0);
  1262. OBSScene scene = main->GetCurrentScene();
  1263. if (scene) {
  1264. gs_matrix_push();
  1265. gs_matrix_scale3f(main->previewScale, main->previewScale, 1.0f);
  1266. obs_scene_enum_items(scene, DrawSelectedItem, this);
  1267. gs_matrix_pop();
  1268. }
  1269. gs_load_vertexbuffer(nullptr);
  1270. gs_technique_end_pass(tech);
  1271. gs_technique_end(tech);
  1272. GS_DEBUG_MARKER_END();
  1273. }
  1274. void OBSBasicPreview::ResetScrollingOffset()
  1275. {
  1276. vec2_zero(&scrollingOffset);
  1277. }
  1278. void OBSBasicPreview::SetScalingLevel(int32_t newScalingLevelVal) {
  1279. float newScalingAmountVal = pow(ZOOM_SENSITIVITY, float(newScalingLevelVal));
  1280. scalingLevel = newScalingLevelVal;
  1281. SetScalingAmount(newScalingAmountVal);
  1282. }
  1283. void OBSBasicPreview::SetScalingAmount(float newScalingAmountVal) {
  1284. scrollingOffset.x *= newScalingAmountVal / scalingAmount;
  1285. scrollingOffset.y *= newScalingAmountVal / scalingAmount;
  1286. scalingAmount = newScalingAmountVal;
  1287. }
  1288. OBSBasicPreview *OBSBasicPreview::Get()
  1289. {
  1290. return OBSBasic::Get()->ui->preview;
  1291. }