window-basic-preview.cpp 70 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595
  1. #include <QGuiApplication>
  2. #include <QMouseEvent>
  3. #include <cmath>
  4. #include <string>
  5. #include <graphics/vec4.h>
  6. #include <graphics/matrix4.h>
  7. #include <util/dstr.hpp>
  8. #include "moc_window-basic-preview.cpp"
  9. #include "window-basic-main.hpp"
  10. #include "obs-app.hpp"
  11. #include "platform.hpp"
  12. #include "display-helpers.hpp"
  13. #define HANDLE_RADIUS 4.0f
  14. #define HANDLE_SEL_RADIUS (HANDLE_RADIUS * 1.5f)
  15. #define HELPER_ROT_BREAKPOINT 45.0f
  16. /* TODO: make C++ math classes and clean up code here later */
  17. OBSBasicPreview::OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags) : OBSQTDisplay(parent, flags)
  18. {
  19. ResetScrollingOffset();
  20. setMouseTracking(true);
  21. }
  22. OBSBasicPreview::~OBSBasicPreview()
  23. {
  24. obs_enter_graphics();
  25. if (overflow)
  26. gs_texture_destroy(overflow);
  27. if (rectFill)
  28. gs_vertexbuffer_destroy(rectFill);
  29. if (circleFill)
  30. gs_vertexbuffer_destroy(circleFill);
  31. obs_leave_graphics();
  32. }
  33. void OBSBasicPreview::Init()
  34. {
  35. OBSBasic *main = OBSBasic::Get();
  36. connect(main, &OBSBasic::PreviewXScrollBarMoved, this, &OBSBasicPreview::XScrollBarMoved);
  37. connect(main, &OBSBasic::PreviewYScrollBarMoved, this, &OBSBasicPreview::YScrollBarMoved);
  38. }
  39. vec2 OBSBasicPreview::GetMouseEventPos(QMouseEvent *event)
  40. {
  41. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  42. float pixelRatio = main->GetDevicePixelRatio();
  43. float scale = pixelRatio / main->previewScale;
  44. QPoint qtPos = event->pos();
  45. vec2 pos;
  46. vec2_set(&pos, (qtPos.x() - main->previewX / pixelRatio) * scale,
  47. (qtPos.y() - main->previewY / pixelRatio) * scale);
  48. return pos;
  49. }
  50. static void RotatePos(vec2 *pos, float rot)
  51. {
  52. float cosR = cos(rot);
  53. float sinR = sin(rot);
  54. vec2 newPos;
  55. newPos.x = cosR * pos->x - sinR * pos->y;
  56. newPos.y = sinR * pos->x + cosR * pos->y;
  57. vec2_copy(pos, &newPos);
  58. }
  59. struct SceneFindData {
  60. const vec2 &pos;
  61. OBSSceneItem item;
  62. bool selectBelow;
  63. obs_sceneitem_t *group = nullptr;
  64. SceneFindData(const SceneFindData &) = delete;
  65. SceneFindData(SceneFindData &&) = delete;
  66. SceneFindData &operator=(const SceneFindData &) = delete;
  67. SceneFindData &operator=(SceneFindData &&) = delete;
  68. inline SceneFindData(const vec2 &pos_, bool selectBelow_) : pos(pos_), selectBelow(selectBelow_) {}
  69. };
  70. struct SceneFindBoxData {
  71. const vec2 &startPos;
  72. const vec2 &pos;
  73. std::vector<obs_sceneitem_t *> sceneItems;
  74. SceneFindBoxData(const SceneFindData &) = delete;
  75. SceneFindBoxData(SceneFindData &&) = delete;
  76. SceneFindBoxData &operator=(const SceneFindData &) = delete;
  77. SceneFindBoxData &operator=(SceneFindData &&) = delete;
  78. inline SceneFindBoxData(const vec2 &startPos_, const vec2 &pos_) : startPos(startPos_), pos(pos_) {}
  79. };
  80. static bool SceneItemHasVideo(obs_sceneitem_t *item)
  81. {
  82. obs_source_t *source = obs_sceneitem_get_source(item);
  83. uint32_t flags = obs_source_get_output_flags(source);
  84. return (flags & OBS_SOURCE_VIDEO) != 0;
  85. }
  86. static bool CloseFloat(float a, float b, float epsilon = 0.01)
  87. {
  88. using std::abs;
  89. return abs(a - b) <= epsilon;
  90. }
  91. static bool FindItemAtPos(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  92. {
  93. SceneFindData *data = reinterpret_cast<SceneFindData *>(param);
  94. matrix4 transform;
  95. matrix4 invTransform;
  96. vec3 transformedPos;
  97. vec3 pos3;
  98. vec3 pos3_;
  99. if (!SceneItemHasVideo(item))
  100. return true;
  101. if (obs_sceneitem_locked(item))
  102. return true;
  103. vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f);
  104. obs_sceneitem_get_box_transform(item, &transform);
  105. matrix4_inv(&invTransform, &transform);
  106. vec3_transform(&transformedPos, &pos3, &invTransform);
  107. vec3_transform(&pos3_, &transformedPos, &transform);
  108. if (CloseFloat(pos3.x, pos3_.x) && CloseFloat(pos3.y, pos3_.y) && transformedPos.x >= 0.0f &&
  109. transformedPos.x <= 1.0f && transformedPos.y >= 0.0f && transformedPos.y <= 1.0f) {
  110. if (data->selectBelow && obs_sceneitem_selected(item)) {
  111. if (data->item)
  112. return false;
  113. else
  114. data->selectBelow = false;
  115. }
  116. data->item = item;
  117. }
  118. return true;
  119. }
  120. static vec3 GetTransformedPos(float x, float y, const matrix4 &mat)
  121. {
  122. vec3 result;
  123. vec3_set(&result, x, y, 0.0f);
  124. vec3_transform(&result, &result, &mat);
  125. return result;
  126. }
  127. static inline vec2 GetOBSScreenSize()
  128. {
  129. obs_video_info ovi;
  130. vec2 size;
  131. vec2_zero(&size);
  132. if (obs_get_video_info(&ovi)) {
  133. size.x = float(ovi.base_width);
  134. size.y = float(ovi.base_height);
  135. }
  136. return size;
  137. }
  138. vec3 OBSBasicPreview::GetSnapOffset(const vec3 &tl, const vec3 &br)
  139. {
  140. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  141. vec2 screenSize = GetOBSScreenSize();
  142. vec3 clampOffset;
  143. vec3_zero(&clampOffset);
  144. const bool snap = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SnappingEnabled");
  145. if (snap == false)
  146. return clampOffset;
  147. const bool screenSnap = config_get_bool(App()->GetUserConfig(), "BasicWindow", "ScreenSnapping");
  148. const bool centerSnap = config_get_bool(App()->GetUserConfig(), "BasicWindow", "CenterSnapping");
  149. const float clampDist =
  150. config_get_double(App()->GetUserConfig(), "BasicWindow", "SnapDistance") / main->previewScale;
  151. const float centerX = br.x - (br.x - tl.x) / 2.0f;
  152. const float centerY = br.y - (br.y - tl.y) / 2.0f;
  153. // Left screen edge.
  154. if (screenSnap && fabsf(tl.x) < clampDist)
  155. clampOffset.x = -tl.x;
  156. // Right screen edge.
  157. if (screenSnap && fabsf(clampOffset.x) < EPSILON && fabsf(screenSize.x - br.x) < clampDist)
  158. clampOffset.x = screenSize.x - br.x;
  159. // Horizontal center.
  160. if (centerSnap && fabsf(screenSize.x - (br.x - tl.x)) > clampDist &&
  161. fabsf(screenSize.x / 2.0f - centerX) < clampDist)
  162. clampOffset.x = screenSize.x / 2.0f - centerX;
  163. // Top screen edge.
  164. if (screenSnap && fabsf(tl.y) < clampDist)
  165. clampOffset.y = -tl.y;
  166. // Bottom screen edge.
  167. if (screenSnap && fabsf(clampOffset.y) < EPSILON && fabsf(screenSize.y - br.y) < clampDist)
  168. clampOffset.y = screenSize.y - br.y;
  169. // Vertical center.
  170. if (centerSnap && fabsf(screenSize.y - (br.y - tl.y)) > clampDist &&
  171. fabsf(screenSize.y / 2.0f - centerY) < clampDist)
  172. clampOffset.y = screenSize.y / 2.0f - centerY;
  173. return clampOffset;
  174. }
  175. OBSSceneItem OBSBasicPreview::GetItemAtPos(const vec2 &pos, bool selectBelow)
  176. {
  177. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  178. OBSScene scene = main->GetCurrentScene();
  179. if (!scene)
  180. return OBSSceneItem();
  181. SceneFindData data(pos, selectBelow);
  182. obs_scene_enum_items(scene, FindItemAtPos, &data);
  183. return data.item;
  184. }
  185. static bool CheckItemSelected(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  186. {
  187. SceneFindData *data = reinterpret_cast<SceneFindData *>(param);
  188. matrix4 transform;
  189. vec3 transformedPos;
  190. vec3 pos3;
  191. if (!SceneItemHasVideo(item))
  192. return true;
  193. if (obs_sceneitem_is_group(item)) {
  194. data->group = item;
  195. obs_sceneitem_group_enum_items(item, CheckItemSelected, param);
  196. data->group = nullptr;
  197. if (data->item) {
  198. return false;
  199. }
  200. }
  201. vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f);
  202. obs_sceneitem_get_box_transform(item, &transform);
  203. if (data->group) {
  204. matrix4 parent_transform;
  205. obs_sceneitem_get_draw_transform(data->group, &parent_transform);
  206. matrix4_mul(&transform, &transform, &parent_transform);
  207. }
  208. matrix4_inv(&transform, &transform);
  209. vec3_transform(&transformedPos, &pos3, &transform);
  210. if (transformedPos.x >= 0.0f && transformedPos.x <= 1.0f && transformedPos.y >= 0.0f &&
  211. transformedPos.y <= 1.0f) {
  212. if (obs_sceneitem_selected(item)) {
  213. data->item = item;
  214. return false;
  215. }
  216. }
  217. return true;
  218. }
  219. bool OBSBasicPreview::SelectedAtPos(const vec2 &pos)
  220. {
  221. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  222. OBSScene scene = main->GetCurrentScene();
  223. if (!scene)
  224. return false;
  225. SceneFindData data(pos, false);
  226. obs_scene_enum_items(scene, CheckItemSelected, &data);
  227. return !!data.item;
  228. }
  229. struct HandleFindData {
  230. const vec2 &pos;
  231. const float radius;
  232. matrix4 parent_xform;
  233. OBSSceneItem item;
  234. ItemHandle handle = ItemHandle::None;
  235. float angle = 0.0f;
  236. vec2 rotatePoint;
  237. vec2 offsetPoint;
  238. float angleOffset = 0.0f;
  239. HandleFindData(const HandleFindData &) = delete;
  240. HandleFindData(HandleFindData &&) = delete;
  241. HandleFindData &operator=(const HandleFindData &) = delete;
  242. HandleFindData &operator=(HandleFindData &&) = delete;
  243. inline HandleFindData(const vec2 &pos_, float scale) : pos(pos_), radius(HANDLE_SEL_RADIUS / scale)
  244. {
  245. matrix4_identity(&parent_xform);
  246. }
  247. inline HandleFindData(const HandleFindData &hfd, obs_sceneitem_t *parent)
  248. : pos(hfd.pos),
  249. radius(hfd.radius),
  250. item(hfd.item),
  251. handle(hfd.handle),
  252. angle(hfd.angle),
  253. rotatePoint(hfd.rotatePoint),
  254. offsetPoint(hfd.offsetPoint)
  255. {
  256. obs_sceneitem_get_draw_transform(parent, &parent_xform);
  257. }
  258. };
  259. static bool FindHandleAtPos(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  260. {
  261. HandleFindData &data = *reinterpret_cast<HandleFindData *>(param);
  262. if (!obs_sceneitem_selected(item)) {
  263. if (obs_sceneitem_is_group(item)) {
  264. HandleFindData newData(data, item);
  265. newData.angleOffset = obs_sceneitem_get_rot(item);
  266. obs_sceneitem_group_enum_items(item, FindHandleAtPos, &newData);
  267. data.item = newData.item;
  268. data.handle = newData.handle;
  269. data.angle = newData.angle;
  270. data.rotatePoint = newData.rotatePoint;
  271. data.offsetPoint = newData.offsetPoint;
  272. }
  273. return true;
  274. }
  275. matrix4 transform;
  276. vec3 pos3;
  277. float closestHandle = data.radius;
  278. vec3_set(&pos3, data.pos.x, data.pos.y, 0.0f);
  279. obs_sceneitem_get_box_transform(item, &transform);
  280. auto TestHandle = [&](float x, float y, ItemHandle handle) {
  281. vec3 handlePos = GetTransformedPos(x, y, transform);
  282. vec3_transform(&handlePos, &handlePos, &data.parent_xform);
  283. float dist = vec3_dist(&handlePos, &pos3);
  284. if (dist < data.radius) {
  285. if (dist < closestHandle) {
  286. closestHandle = dist;
  287. data.handle = handle;
  288. data.item = item;
  289. }
  290. }
  291. };
  292. TestHandle(0.0f, 0.0f, ItemHandle::TopLeft);
  293. TestHandle(0.5f, 0.0f, ItemHandle::TopCenter);
  294. TestHandle(1.0f, 0.0f, ItemHandle::TopRight);
  295. TestHandle(0.0f, 0.5f, ItemHandle::CenterLeft);
  296. TestHandle(1.0f, 0.5f, ItemHandle::CenterRight);
  297. TestHandle(0.0f, 1.0f, ItemHandle::BottomLeft);
  298. TestHandle(0.5f, 1.0f, ItemHandle::BottomCenter);
  299. TestHandle(1.0f, 1.0f, ItemHandle::BottomRight);
  300. vec2 scale;
  301. obs_sceneitem_get_scale(item, &scale);
  302. obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(item);
  303. vec2 rotHandleOffset;
  304. vec2_set(&rotHandleOffset, 0.0f, HANDLE_RADIUS * data.radius * 1.5 - data.radius);
  305. bool invertx = scale.x < 0.0f && boundsType == OBS_BOUNDS_NONE;
  306. float angle =
  307. atan2(invertx ? transform.x.y * -1.0f : transform.x.y, invertx ? transform.x.x * -1.0f : transform.x.x);
  308. RotatePos(&rotHandleOffset, angle);
  309. RotatePos(&rotHandleOffset, RAD(data.angleOffset));
  310. bool inverty = scale.y < 0.0f && boundsType == OBS_BOUNDS_NONE;
  311. vec3 handlePos = GetTransformedPos(0.5f, inverty ? 1.0f : 0.0f, transform);
  312. vec3_transform(&handlePos, &handlePos, &data.parent_xform);
  313. handlePos.x -= rotHandleOffset.x;
  314. handlePos.y -= rotHandleOffset.y;
  315. float dist = vec3_dist(&handlePos, &pos3);
  316. if (dist < data.radius) {
  317. if (dist < closestHandle) {
  318. closestHandle = dist;
  319. data.item = item;
  320. data.angle = obs_sceneitem_get_rot(item);
  321. data.handle = ItemHandle::Rot;
  322. vec2_set(&data.rotatePoint, transform.t.x + transform.x.x / 2 + transform.y.x / 2,
  323. transform.t.y + transform.x.y / 2 + transform.y.y / 2);
  324. obs_sceneitem_get_pos(item, &data.offsetPoint);
  325. data.offsetPoint.x -= data.rotatePoint.x;
  326. data.offsetPoint.y -= data.rotatePoint.y;
  327. RotatePos(&data.offsetPoint, -RAD(obs_sceneitem_get_rot(item)));
  328. }
  329. }
  330. return true;
  331. }
  332. static vec2 GetItemSize(obs_sceneitem_t *item)
  333. {
  334. obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(item);
  335. vec2 size;
  336. if (boundsType != OBS_BOUNDS_NONE) {
  337. obs_sceneitem_get_bounds(item, &size);
  338. } else {
  339. obs_source_t *source = obs_sceneitem_get_source(item);
  340. obs_sceneitem_crop crop;
  341. vec2 scale;
  342. obs_sceneitem_get_scale(item, &scale);
  343. obs_sceneitem_get_crop(item, &crop);
  344. size.x = float(obs_source_get_width(source) - crop.left - crop.right) * scale.x;
  345. size.y = float(obs_source_get_height(source) - crop.top - crop.bottom) * scale.y;
  346. }
  347. return size;
  348. }
  349. void OBSBasicPreview::GetStretchHandleData(const vec2 &pos, bool ignoreGroup)
  350. {
  351. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  352. OBSScene scene = main->GetCurrentScene();
  353. if (!scene)
  354. return;
  355. float scale = main->previewScale / main->GetDevicePixelRatio();
  356. vec2 scaled_pos = pos;
  357. vec2_divf(&scaled_pos, &scaled_pos, scale);
  358. HandleFindData data(scaled_pos, scale);
  359. obs_scene_enum_items(scene, FindHandleAtPos, &data);
  360. stretchItem = std::move(data.item);
  361. stretchHandle = data.handle;
  362. rotateAngle = data.angle;
  363. rotatePoint = data.rotatePoint;
  364. offsetPoint = data.offsetPoint;
  365. if (stretchHandle != ItemHandle::None) {
  366. matrix4 boxTransform;
  367. vec3 itemUL;
  368. float itemRot;
  369. stretchItemSize = GetItemSize(stretchItem);
  370. obs_sceneitem_get_box_transform(stretchItem, &boxTransform);
  371. itemRot = obs_sceneitem_get_rot(stretchItem);
  372. vec3_from_vec4(&itemUL, &boxTransform.t);
  373. /* build the item space conversion matrices */
  374. matrix4_identity(&itemToScreen);
  375. matrix4_rotate_aa4f(&itemToScreen, &itemToScreen, 0.0f, 0.0f, 1.0f, RAD(itemRot));
  376. matrix4_translate3f(&itemToScreen, &itemToScreen, itemUL.x, itemUL.y, 0.0f);
  377. matrix4_identity(&screenToItem);
  378. matrix4_translate3f(&screenToItem, &screenToItem, -itemUL.x, -itemUL.y, 0.0f);
  379. matrix4_rotate_aa4f(&screenToItem, &screenToItem, 0.0f, 0.0f, 1.0f, RAD(-itemRot));
  380. obs_sceneitem_get_crop(stretchItem, &startCrop);
  381. obs_sceneitem_get_pos(stretchItem, &startItemPos);
  382. obs_source_t *source = obs_sceneitem_get_source(stretchItem);
  383. cropSize.x = float(obs_source_get_width(source) - startCrop.left - startCrop.right);
  384. cropSize.y = float(obs_source_get_height(source) - startCrop.top - startCrop.bottom);
  385. stretchGroup = obs_sceneitem_get_group(scene, stretchItem);
  386. if (stretchGroup && !ignoreGroup) {
  387. obs_sceneitem_get_draw_transform(stretchGroup, &invGroupTransform);
  388. matrix4_inv(&invGroupTransform, &invGroupTransform);
  389. obs_sceneitem_defer_group_resize_begin(stretchGroup);
  390. } else {
  391. stretchGroup = nullptr;
  392. }
  393. }
  394. }
  395. void OBSBasicPreview::keyPressEvent(QKeyEvent *event)
  396. {
  397. if (!IsFixedScaling() || event->isAutoRepeat()) {
  398. OBSQTDisplay::keyPressEvent(event);
  399. return;
  400. }
  401. switch (event->key()) {
  402. case Qt::Key_Space:
  403. setCursor(Qt::OpenHandCursor);
  404. scrollMode = true;
  405. break;
  406. }
  407. OBSQTDisplay::keyPressEvent(event);
  408. }
  409. void OBSBasicPreview::keyReleaseEvent(QKeyEvent *event)
  410. {
  411. if (event->isAutoRepeat()) {
  412. OBSQTDisplay::keyReleaseEvent(event);
  413. return;
  414. }
  415. switch (event->key()) {
  416. case Qt::Key_Space:
  417. scrollMode = false;
  418. setCursor(Qt::ArrowCursor);
  419. break;
  420. }
  421. OBSQTDisplay::keyReleaseEvent(event);
  422. }
  423. void OBSBasicPreview::wheelEvent(QWheelEvent *event)
  424. {
  425. if (scrollMode && IsFixedScaling()) {
  426. const int delta = event->angleDelta().y();
  427. if (delta != 0) {
  428. if (delta > 0)
  429. SetScalingLevel(scalingLevel + 1);
  430. else
  431. SetScalingLevel(scalingLevel - 1);
  432. emit DisplayResized();
  433. }
  434. }
  435. OBSQTDisplay::wheelEvent(event);
  436. }
  437. void OBSBasicPreview::mousePressEvent(QMouseEvent *event)
  438. {
  439. QPointF pos = event->position();
  440. if (scrollMode && IsFixedScaling() && event->button() == Qt::LeftButton) {
  441. setCursor(Qt::ClosedHandCursor);
  442. scrollingFrom.x = pos.x();
  443. scrollingFrom.y = pos.y();
  444. return;
  445. }
  446. if (event->button() == Qt::RightButton) {
  447. scrollMode = false;
  448. setCursor(Qt::ArrowCursor);
  449. }
  450. if (locked) {
  451. OBSQTDisplay::mousePressEvent(event);
  452. return;
  453. }
  454. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  455. float pixelRatio = main->GetDevicePixelRatio();
  456. float x = pos.x() - main->previewX / pixelRatio;
  457. float y = pos.y() - main->previewY / pixelRatio;
  458. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  459. bool altDown = (modifiers & Qt::AltModifier);
  460. bool shiftDown = (modifiers & Qt::ShiftModifier);
  461. bool ctrlDown = (modifiers & Qt::ControlModifier);
  462. OBSQTDisplay::mousePressEvent(event);
  463. if (event->button() != Qt::LeftButton && event->button() != Qt::RightButton)
  464. return;
  465. if (event->button() == Qt::LeftButton)
  466. mouseDown = true;
  467. {
  468. std::lock_guard<std::mutex> lock(selectMutex);
  469. selectedItems.clear();
  470. }
  471. if (altDown)
  472. cropping = true;
  473. if (altDown || shiftDown || ctrlDown) {
  474. vec2 s;
  475. SceneFindBoxData data(s, s);
  476. obs_scene_enum_items(main->GetCurrentScene(), FindSelected, &data);
  477. std::lock_guard<std::mutex> lock(selectMutex);
  478. selectedItems = data.sceneItems;
  479. }
  480. vec2_set(&startPos, x, y);
  481. GetStretchHandleData(startPos, false);
  482. vec2_divf(&startPos, &startPos, main->previewScale / pixelRatio);
  483. startPos.x = std::round(startPos.x);
  484. startPos.y = std::round(startPos.y);
  485. mouseOverItems = SelectedAtPos(startPos);
  486. vec2_zero(&lastMoveOffset);
  487. mousePos = startPos;
  488. wrapper = obs_scene_save_transform_states(main->GetCurrentScene(), true);
  489. changed = false;
  490. }
  491. void OBSBasicPreview::UpdateCursor(uint32_t &flags)
  492. {
  493. if (!stretchItem || obs_sceneitem_locked(stretchItem)) {
  494. unsetCursor();
  495. return;
  496. }
  497. if (!flags && (cursor().shape() != Qt::OpenHandCursor || !scrollMode))
  498. unsetCursor();
  499. if ((cursor().shape() != Qt::ArrowCursor) || flags == 0)
  500. return;
  501. if (flags & ITEM_ROT) {
  502. setCursor(Qt::OpenHandCursor);
  503. return;
  504. }
  505. float rotation = obs_sceneitem_get_rot(stretchItem);
  506. vec2 scale;
  507. obs_sceneitem_get_scale(stretchItem, &scale);
  508. if (rotation < 0.0f)
  509. rotation = 360.0f + rotation;
  510. int octant = int(std::round(rotation / 45.0f));
  511. bool isCorner = (flags & (flags - 1)) != 0;
  512. if ((scale.x < 0.0f) && isCorner)
  513. flags ^= ITEM_LEFT | ITEM_RIGHT;
  514. if ((scale.y < 0.0f) && isCorner)
  515. flags ^= ITEM_TOP | ITEM_BOTTOM;
  516. if (octant % 4 >= 2) {
  517. if (isCorner) {
  518. flags ^= ITEM_TOP | ITEM_BOTTOM;
  519. } else {
  520. flags = (flags >> 2) | (flags << 2);
  521. }
  522. }
  523. if (octant % 2 == 1) {
  524. if (isCorner) {
  525. flags &= (flags % 3 == 0) ? ~ITEM_TOP & ~ITEM_BOTTOM : ~ITEM_LEFT & ~ITEM_RIGHT;
  526. } else {
  527. flags = (flags % 4 == 0) ? flags | flags >> ((flags / 2) - 1)
  528. : flags | ((flags >> 2) | (flags << 2));
  529. }
  530. }
  531. if ((flags & ITEM_LEFT && flags & ITEM_TOP) || (flags & ITEM_RIGHT && flags & ITEM_BOTTOM))
  532. setCursor(Qt::SizeFDiagCursor);
  533. else if ((flags & ITEM_LEFT && flags & ITEM_BOTTOM) || (flags & ITEM_RIGHT && flags & ITEM_TOP))
  534. setCursor(Qt::SizeBDiagCursor);
  535. else if (flags & ITEM_LEFT || flags & ITEM_RIGHT)
  536. setCursor(Qt::SizeHorCursor);
  537. else if (flags & ITEM_TOP || flags & ITEM_BOTTOM)
  538. setCursor(Qt::SizeVerCursor);
  539. }
  540. static bool select_one(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  541. {
  542. obs_sceneitem_t *selectedItem = reinterpret_cast<obs_sceneitem_t *>(param);
  543. if (obs_sceneitem_is_group(item))
  544. obs_sceneitem_group_enum_items(item, select_one, param);
  545. obs_sceneitem_select(item, (selectedItem == item));
  546. return true;
  547. }
  548. void OBSBasicPreview::DoSelect(const vec2 &pos)
  549. {
  550. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  551. OBSScene scene = main->GetCurrentScene();
  552. OBSSceneItem item = GetItemAtPos(pos, true);
  553. obs_scene_enum_items(scene, select_one, (obs_sceneitem_t *)item);
  554. }
  555. void OBSBasicPreview::DoCtrlSelect(const vec2 &pos)
  556. {
  557. OBSSceneItem item = GetItemAtPos(pos, false);
  558. if (!item)
  559. return;
  560. bool selected = obs_sceneitem_selected(item);
  561. obs_sceneitem_select(item, !selected);
  562. }
  563. void OBSBasicPreview::ProcessClick(const vec2 &pos)
  564. {
  565. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  566. if (modifiers & Qt::ControlModifier)
  567. DoCtrlSelect(pos);
  568. else
  569. DoSelect(pos);
  570. }
  571. void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event)
  572. {
  573. if (scrollMode)
  574. setCursor(Qt::OpenHandCursor);
  575. if (locked) {
  576. OBSQTDisplay::mouseReleaseEvent(event);
  577. return;
  578. }
  579. if (mouseDown) {
  580. vec2 pos = GetMouseEventPos(event);
  581. if (!mouseMoved)
  582. ProcessClick(pos);
  583. if (selectionBox) {
  584. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  585. bool altDown = modifiers & Qt::AltModifier;
  586. bool shiftDown = modifiers & Qt::ShiftModifier;
  587. bool ctrlDown = modifiers & Qt::ControlModifier;
  588. std::lock_guard<std::mutex> lock(selectMutex);
  589. if (altDown || ctrlDown || shiftDown) {
  590. for (size_t i = 0; i < selectedItems.size(); i++) {
  591. obs_sceneitem_select(selectedItems[i], true);
  592. }
  593. }
  594. for (size_t i = 0; i < hoveredPreviewItems.size(); i++) {
  595. bool select = true;
  596. obs_sceneitem_t *item = hoveredPreviewItems[i];
  597. if (altDown) {
  598. select = false;
  599. } else if (ctrlDown) {
  600. select = !obs_sceneitem_selected(item);
  601. }
  602. obs_sceneitem_select(hoveredPreviewItems[i], select);
  603. }
  604. }
  605. if (stretchGroup) {
  606. obs_sceneitem_defer_group_resize_end(stretchGroup);
  607. }
  608. stretchItem = nullptr;
  609. stretchGroup = nullptr;
  610. mouseDown = false;
  611. mouseMoved = false;
  612. cropping = false;
  613. selectionBox = false;
  614. unsetCursor();
  615. OBSSceneItem item = GetItemAtPos(pos, true);
  616. std::lock_guard<std::mutex> lock(selectMutex);
  617. hoveredPreviewItems.clear();
  618. hoveredPreviewItems.push_back(item);
  619. selectedItems.clear();
  620. }
  621. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  622. OBSDataAutoRelease rwrapper = obs_scene_save_transform_states(main->GetCurrentScene(), true);
  623. auto undo_redo = [](const std::string &data) {
  624. OBSDataAutoRelease dat = obs_data_create_from_json(data.c_str());
  625. OBSSourceAutoRelease source = obs_get_source_by_uuid(obs_data_get_string(dat, "scene_uuid"));
  626. reinterpret_cast<OBSBasic *>(App()->GetMainWindow())->SetCurrentScene(source.Get(), true);
  627. obs_scene_load_transform_states(data.c_str());
  628. };
  629. if (wrapper && rwrapper) {
  630. std::string undo_data(obs_data_get_json(wrapper));
  631. std::string redo_data(obs_data_get_json(rwrapper));
  632. if (changed && undo_data.compare(redo_data) != 0)
  633. main->undo_s.add_action(
  634. QTStr("Undo.Transform").arg(obs_source_get_name(main->GetCurrentSceneSource())),
  635. undo_redo, undo_redo, undo_data, redo_data);
  636. }
  637. wrapper = nullptr;
  638. }
  639. struct SelectedItemBounds {
  640. bool first = true;
  641. vec3 tl, br;
  642. };
  643. static bool AddItemBounds(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  644. {
  645. SelectedItemBounds *data = reinterpret_cast<SelectedItemBounds *>(param);
  646. vec3 t[4];
  647. auto add_bounds = [data, &t]() {
  648. for (const vec3 &v : t) {
  649. if (data->first) {
  650. vec3_copy(&data->tl, &v);
  651. vec3_copy(&data->br, &v);
  652. data->first = false;
  653. } else {
  654. vec3_min(&data->tl, &data->tl, &v);
  655. vec3_max(&data->br, &data->br, &v);
  656. }
  657. }
  658. };
  659. if (obs_sceneitem_is_group(item)) {
  660. SelectedItemBounds sib;
  661. obs_sceneitem_group_enum_items(item, AddItemBounds, &sib);
  662. if (!sib.first) {
  663. matrix4 xform;
  664. obs_sceneitem_get_draw_transform(item, &xform);
  665. vec3_set(&t[0], sib.tl.x, sib.tl.y, 0.0f);
  666. vec3_set(&t[1], sib.tl.x, sib.br.y, 0.0f);
  667. vec3_set(&t[2], sib.br.x, sib.tl.y, 0.0f);
  668. vec3_set(&t[3], sib.br.x, sib.br.y, 0.0f);
  669. vec3_transform(&t[0], &t[0], &xform);
  670. vec3_transform(&t[1], &t[1], &xform);
  671. vec3_transform(&t[2], &t[2], &xform);
  672. vec3_transform(&t[3], &t[3], &xform);
  673. add_bounds();
  674. }
  675. }
  676. if (!obs_sceneitem_selected(item))
  677. return true;
  678. matrix4 boxTransform;
  679. obs_sceneitem_get_box_transform(item, &boxTransform);
  680. t[0] = GetTransformedPos(0.0f, 0.0f, boxTransform);
  681. t[1] = GetTransformedPos(1.0f, 0.0f, boxTransform);
  682. t[2] = GetTransformedPos(0.0f, 1.0f, boxTransform);
  683. t[3] = GetTransformedPos(1.0f, 1.0f, boxTransform);
  684. add_bounds();
  685. return true;
  686. }
  687. struct OffsetData {
  688. float clampDist;
  689. vec3 tl, br, offset;
  690. };
  691. static bool GetSourceSnapOffset(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  692. {
  693. OffsetData *data = reinterpret_cast<OffsetData *>(param);
  694. if (obs_sceneitem_selected(item))
  695. return true;
  696. matrix4 boxTransform;
  697. obs_sceneitem_get_box_transform(item, &boxTransform);
  698. vec3 t[4] = {GetTransformedPos(0.0f, 0.0f, boxTransform), GetTransformedPos(1.0f, 0.0f, boxTransform),
  699. GetTransformedPos(0.0f, 1.0f, boxTransform), GetTransformedPos(1.0f, 1.0f, boxTransform)};
  700. bool first = true;
  701. vec3 tl, br;
  702. vec3_zero(&tl);
  703. vec3_zero(&br);
  704. for (const vec3 &v : t) {
  705. if (first) {
  706. vec3_copy(&tl, &v);
  707. vec3_copy(&br, &v);
  708. first = false;
  709. } else {
  710. vec3_min(&tl, &tl, &v);
  711. vec3_max(&br, &br, &v);
  712. }
  713. }
  714. // Snap to other source edges
  715. #define EDGE_SNAP(l, r, x, y) \
  716. do { \
  717. double dist = fabsf(l.x - data->r.x); \
  718. if (dist < data->clampDist && fabsf(data->offset.x) < EPSILON && data->tl.y < br.y && \
  719. data->br.y > tl.y && (fabsf(data->offset.x) > dist || data->offset.x < EPSILON)) \
  720. data->offset.x = l.x - data->r.x; \
  721. } while (false)
  722. EDGE_SNAP(tl, br, x, y);
  723. EDGE_SNAP(tl, br, y, x);
  724. EDGE_SNAP(br, tl, x, y);
  725. EDGE_SNAP(br, tl, y, x);
  726. #undef EDGE_SNAP
  727. return true;
  728. }
  729. void OBSBasicPreview::SnapItemMovement(vec2 &offset)
  730. {
  731. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  732. OBSScene scene = main->GetCurrentScene();
  733. SelectedItemBounds data;
  734. obs_scene_enum_items(scene, AddItemBounds, &data);
  735. data.tl.x += offset.x;
  736. data.tl.y += offset.y;
  737. data.br.x += offset.x;
  738. data.br.y += offset.y;
  739. vec3 snapOffset = GetSnapOffset(data.tl, data.br);
  740. const bool snap = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SnappingEnabled");
  741. const bool sourcesSnap = config_get_bool(App()->GetUserConfig(), "BasicWindow", "SourceSnapping");
  742. if (snap == false)
  743. return;
  744. if (sourcesSnap == false) {
  745. offset.x += snapOffset.x;
  746. offset.y += snapOffset.y;
  747. return;
  748. }
  749. const float clampDist =
  750. config_get_double(App()->GetUserConfig(), "BasicWindow", "SnapDistance") / main->previewScale;
  751. OffsetData offsetData;
  752. offsetData.clampDist = clampDist;
  753. offsetData.tl = data.tl;
  754. offsetData.br = data.br;
  755. vec3_copy(&offsetData.offset, &snapOffset);
  756. obs_scene_enum_items(scene, GetSourceSnapOffset, &offsetData);
  757. if (fabsf(offsetData.offset.x) > EPSILON || fabsf(offsetData.offset.y) > EPSILON) {
  758. offset.x += offsetData.offset.x;
  759. offset.y += offsetData.offset.y;
  760. } else {
  761. offset.x += snapOffset.x;
  762. offset.y += snapOffset.y;
  763. }
  764. }
  765. static bool move_items(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  766. {
  767. if (obs_sceneitem_locked(item))
  768. return true;
  769. bool selected = obs_sceneitem_selected(item);
  770. vec2 *offset = reinterpret_cast<vec2 *>(param);
  771. if (obs_sceneitem_is_group(item) && !selected) {
  772. matrix4 transform;
  773. vec3 new_offset;
  774. vec3_set(&new_offset, offset->x, offset->y, 0.0f);
  775. obs_sceneitem_get_draw_transform(item, &transform);
  776. vec4_set(&transform.t, 0.0f, 0.0f, 0.0f, 1.0f);
  777. matrix4_inv(&transform, &transform);
  778. vec3_transform(&new_offset, &new_offset, &transform);
  779. obs_sceneitem_group_enum_items(item, move_items, &new_offset);
  780. }
  781. if (selected) {
  782. vec2 pos;
  783. obs_sceneitem_get_pos(item, &pos);
  784. vec2_add(&pos, &pos, offset);
  785. obs_sceneitem_set_pos(item, &pos);
  786. }
  787. return true;
  788. }
  789. void OBSBasicPreview::MoveItems(const vec2 &pos)
  790. {
  791. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  792. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  793. OBSScene scene = main->GetCurrentScene();
  794. vec2 offset, moveOffset;
  795. vec2_sub(&offset, &pos, &startPos);
  796. vec2_sub(&moveOffset, &offset, &lastMoveOffset);
  797. if (!(modifiers & Qt::ControlModifier))
  798. SnapItemMovement(moveOffset);
  799. vec2_add(&lastMoveOffset, &lastMoveOffset, &moveOffset);
  800. obs_scene_enum_items(scene, move_items, &moveOffset);
  801. }
  802. static bool CounterClockwise(float x1, float x2, float x3, float y1, float y2, float y3)
  803. {
  804. return (y3 - y1) * (x2 - x1) > (y2 - y1) * (x3 - x1);
  805. }
  806. static bool IntersectLine(float x1, float x2, float x3, float x4, float y1, float y2, float y3, float y4)
  807. {
  808. bool a = CounterClockwise(x1, x2, x3, y1, y2, y3);
  809. bool b = CounterClockwise(x1, x2, x4, y1, y2, y4);
  810. bool c = CounterClockwise(x3, x4, x1, y3, y4, y1);
  811. bool d = CounterClockwise(x3, x4, x2, y3, y4, y2);
  812. return (a != b) && (c != d);
  813. }
  814. static bool IntersectBox(matrix4 transform, float x1, float x2, float y1, float y2)
  815. {
  816. float x3, x4, y3, y4;
  817. x3 = transform.t.x;
  818. y3 = transform.t.y;
  819. x4 = x3 + transform.x.x;
  820. y4 = y3 + transform.x.y;
  821. if (IntersectLine(x1, x1, x3, x4, y1, y2, y3, y4) || IntersectLine(x1, x2, x3, x4, y1, y1, y3, y4) ||
  822. IntersectLine(x2, x2, x3, x4, y1, y2, y3, y4) || IntersectLine(x1, x2, x3, x4, y2, y2, y3, y4))
  823. return true;
  824. x4 = x3 + transform.y.x;
  825. y4 = y3 + transform.y.y;
  826. if (IntersectLine(x1, x1, x3, x4, y1, y2, y3, y4) || IntersectLine(x1, x2, x3, x4, y1, y1, y3, y4) ||
  827. IntersectLine(x2, x2, x3, x4, y1, y2, y3, y4) || IntersectLine(x1, x2, x3, x4, y2, y2, y3, y4))
  828. return true;
  829. x3 = transform.t.x + transform.x.x;
  830. y3 = transform.t.y + transform.x.y;
  831. x4 = x3 + transform.y.x;
  832. y4 = y3 + transform.y.y;
  833. if (IntersectLine(x1, x1, x3, x4, y1, y2, y3, y4) || IntersectLine(x1, x2, x3, x4, y1, y1, y3, y4) ||
  834. IntersectLine(x2, x2, x3, x4, y1, y2, y3, y4) || IntersectLine(x1, x2, x3, x4, y2, y2, y3, y4))
  835. return true;
  836. x3 = transform.t.x + transform.y.x;
  837. y3 = transform.t.y + transform.y.y;
  838. x4 = x3 + transform.x.x;
  839. y4 = y3 + transform.x.y;
  840. if (IntersectLine(x1, x1, x3, x4, y1, y2, y3, y4) || IntersectLine(x1, x2, x3, x4, y1, y1, y3, y4) ||
  841. IntersectLine(x2, x2, x3, x4, y1, y2, y3, y4) || IntersectLine(x1, x2, x3, x4, y2, y2, y3, y4))
  842. return true;
  843. return false;
  844. }
  845. #undef PI
  846. bool OBSBasicPreview::FindSelected(obs_scene_t *, obs_sceneitem_t *item, void *param)
  847. {
  848. SceneFindBoxData *data = reinterpret_cast<SceneFindBoxData *>(param);
  849. if (obs_sceneitem_selected(item))
  850. data->sceneItems.push_back(item);
  851. return true;
  852. }
  853. static bool FindItemsInBox(obs_scene_t * /* scene */, obs_sceneitem_t *item, void *param)
  854. {
  855. SceneFindBoxData *data = reinterpret_cast<SceneFindBoxData *>(param);
  856. matrix4 transform;
  857. matrix4 invTransform;
  858. vec3 transformedPos;
  859. vec3 pos3;
  860. vec3 pos3_;
  861. vec2 pos_min, pos_max;
  862. vec2_min(&pos_min, &data->startPos, &data->pos);
  863. vec2_max(&pos_max, &data->startPos, &data->pos);
  864. const float x1 = pos_min.x;
  865. const float x2 = pos_max.x;
  866. const float y1 = pos_min.y;
  867. const float y2 = pos_max.y;
  868. if (!SceneItemHasVideo(item))
  869. return true;
  870. if (obs_sceneitem_locked(item))
  871. return true;
  872. if (!obs_sceneitem_visible(item))
  873. return true;
  874. vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f);
  875. obs_sceneitem_get_box_transform(item, &transform);
  876. matrix4_inv(&invTransform, &transform);
  877. vec3_transform(&transformedPos, &pos3, &invTransform);
  878. vec3_transform(&pos3_, &transformedPos, &transform);
  879. if (CloseFloat(pos3.x, pos3_.x) && CloseFloat(pos3.y, pos3_.y) && transformedPos.x >= 0.0f &&
  880. transformedPos.x <= 1.0f && transformedPos.y >= 0.0f && transformedPos.y <= 1.0f) {
  881. data->sceneItems.push_back(item);
  882. return true;
  883. }
  884. if (transform.t.x > x1 && transform.t.x < x2 && transform.t.y > y1 && transform.t.y < y2) {
  885. data->sceneItems.push_back(item);
  886. return true;
  887. }
  888. if (transform.t.x + transform.x.x > x1 && transform.t.x + transform.x.x < x2 &&
  889. transform.t.y + transform.x.y > y1 && transform.t.y + transform.x.y < y2) {
  890. data->sceneItems.push_back(item);
  891. return true;
  892. }
  893. if (transform.t.x + transform.y.x > x1 && transform.t.x + transform.y.x < x2 &&
  894. transform.t.y + transform.y.y > y1 && transform.t.y + transform.y.y < y2) {
  895. data->sceneItems.push_back(item);
  896. return true;
  897. }
  898. if (transform.t.x + transform.x.x + transform.y.x > x1 && transform.t.x + transform.x.x + transform.y.x < x2 &&
  899. transform.t.y + transform.x.y + transform.y.y > y1 && transform.t.y + transform.x.y + transform.y.y < y2) {
  900. data->sceneItems.push_back(item);
  901. return true;
  902. }
  903. if (transform.t.x + 0.5 * (transform.x.x + transform.y.x) > x1 &&
  904. transform.t.x + 0.5 * (transform.x.x + transform.y.x) < x2 &&
  905. transform.t.y + 0.5 * (transform.x.y + transform.y.y) > y1 &&
  906. transform.t.y + 0.5 * (transform.x.y + transform.y.y) < y2) {
  907. data->sceneItems.push_back(item);
  908. return true;
  909. }
  910. if (IntersectBox(transform, x1, x2, y1, y2)) {
  911. data->sceneItems.push_back(item);
  912. return true;
  913. }
  914. return true;
  915. }
  916. void OBSBasicPreview::BoxItems(const vec2 &startPos, const vec2 &pos)
  917. {
  918. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  919. OBSScene scene = main->GetCurrentScene();
  920. if (!scene)
  921. return;
  922. if (cursor().shape() != Qt::CrossCursor)
  923. setCursor(Qt::CrossCursor);
  924. SceneFindBoxData data(startPos, pos);
  925. obs_scene_enum_items(scene, FindItemsInBox, &data);
  926. std::lock_guard<std::mutex> lock(selectMutex);
  927. hoveredPreviewItems = data.sceneItems;
  928. }
  929. vec3 OBSBasicPreview::CalculateStretchPos(const vec3 &tl, const vec3 &br)
  930. {
  931. uint32_t alignment = obs_sceneitem_get_alignment(stretchItem);
  932. vec3 pos;
  933. vec3_zero(&pos);
  934. if (alignment & OBS_ALIGN_LEFT)
  935. pos.x = tl.x;
  936. else if (alignment & OBS_ALIGN_RIGHT)
  937. pos.x = br.x;
  938. else
  939. pos.x = (br.x - tl.x) * 0.5f + tl.x;
  940. if (alignment & OBS_ALIGN_TOP)
  941. pos.y = tl.y;
  942. else if (alignment & OBS_ALIGN_BOTTOM)
  943. pos.y = br.y;
  944. else
  945. pos.y = (br.y - tl.y) * 0.5f + tl.y;
  946. return pos;
  947. }
  948. void OBSBasicPreview::ClampAspect(vec3 &tl, vec3 &br, vec2 &size, const vec2 &baseSize)
  949. {
  950. float baseAspect = baseSize.x / baseSize.y;
  951. float aspect = size.x / size.y;
  952. uint32_t stretchFlags = (uint32_t)stretchHandle;
  953. if (stretchHandle == ItemHandle::TopLeft || stretchHandle == ItemHandle::TopRight ||
  954. stretchHandle == ItemHandle::BottomLeft || stretchHandle == ItemHandle::BottomRight) {
  955. if (aspect < baseAspect) {
  956. if ((size.y >= 0.0f && size.x >= 0.0f) || (size.y <= 0.0f && size.x <= 0.0f))
  957. size.x = size.y * baseAspect;
  958. else
  959. size.x = size.y * baseAspect * -1.0f;
  960. } else {
  961. if ((size.y >= 0.0f && size.x >= 0.0f) || (size.y <= 0.0f && size.x <= 0.0f))
  962. size.y = size.x / baseAspect;
  963. else
  964. size.y = size.x / baseAspect * -1.0f;
  965. }
  966. } else if (stretchHandle == ItemHandle::TopCenter || stretchHandle == ItemHandle::BottomCenter) {
  967. if ((size.y >= 0.0f && size.x >= 0.0f) || (size.y <= 0.0f && size.x <= 0.0f))
  968. size.x = size.y * baseAspect;
  969. else
  970. size.x = size.y * baseAspect * -1.0f;
  971. } else if (stretchHandle == ItemHandle::CenterLeft || stretchHandle == ItemHandle::CenterRight) {
  972. if ((size.y >= 0.0f && size.x >= 0.0f) || (size.y <= 0.0f && size.x <= 0.0f))
  973. size.y = size.x / baseAspect;
  974. else
  975. size.y = size.x / baseAspect * -1.0f;
  976. }
  977. size.x = std::round(size.x);
  978. size.y = std::round(size.y);
  979. if (stretchFlags & ITEM_LEFT)
  980. tl.x = br.x - size.x;
  981. else if (stretchFlags & ITEM_RIGHT)
  982. br.x = tl.x + size.x;
  983. if (stretchFlags & ITEM_TOP)
  984. tl.y = br.y - size.y;
  985. else if (stretchFlags & ITEM_BOTTOM)
  986. br.y = tl.y + size.y;
  987. }
  988. void OBSBasicPreview::SnapStretchingToScreen(vec3 &tl, vec3 &br)
  989. {
  990. uint32_t stretchFlags = (uint32_t)stretchHandle;
  991. vec3 newTL = GetTransformedPos(tl.x, tl.y, itemToScreen);
  992. vec3 newTR = GetTransformedPos(br.x, tl.y, itemToScreen);
  993. vec3 newBL = GetTransformedPos(tl.x, br.y, itemToScreen);
  994. vec3 newBR = GetTransformedPos(br.x, br.y, itemToScreen);
  995. vec3 boundingTL;
  996. vec3 boundingBR;
  997. vec3_copy(&boundingTL, &newTL);
  998. vec3_min(&boundingTL, &boundingTL, &newTR);
  999. vec3_min(&boundingTL, &boundingTL, &newBL);
  1000. vec3_min(&boundingTL, &boundingTL, &newBR);
  1001. vec3_copy(&boundingBR, &newTL);
  1002. vec3_max(&boundingBR, &boundingBR, &newTR);
  1003. vec3_max(&boundingBR, &boundingBR, &newBL);
  1004. vec3_max(&boundingBR, &boundingBR, &newBR);
  1005. vec3 offset = GetSnapOffset(boundingTL, boundingBR);
  1006. vec3_add(&offset, &offset, &newTL);
  1007. vec3_transform(&offset, &offset, &screenToItem);
  1008. vec3_sub(&offset, &offset, &tl);
  1009. if (stretchFlags & ITEM_LEFT)
  1010. tl.x += offset.x;
  1011. else if (stretchFlags & ITEM_RIGHT)
  1012. br.x += offset.x;
  1013. if (stretchFlags & ITEM_TOP)
  1014. tl.y += offset.y;
  1015. else if (stretchFlags & ITEM_BOTTOM)
  1016. br.y += offset.y;
  1017. }
  1018. static float maxfunc(float x, float y)
  1019. {
  1020. return x > y ? x : y;
  1021. }
  1022. static float minfunc(float x, float y)
  1023. {
  1024. return x < y ? x : y;
  1025. }
  1026. void OBSBasicPreview::CropItem(const vec2 &pos)
  1027. {
  1028. obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(stretchItem);
  1029. uint32_t stretchFlags = (uint32_t)stretchHandle;
  1030. uint32_t align = obs_sceneitem_get_alignment(stretchItem);
  1031. vec3 tl, br, pos3;
  1032. vec3_zero(&tl);
  1033. vec3_set(&br, stretchItemSize.x, stretchItemSize.y, 0.0f);
  1034. vec3_set(&pos3, pos.x, pos.y, 0.0f);
  1035. vec3_transform(&pos3, &pos3, &screenToItem);
  1036. obs_sceneitem_crop crop = startCrop;
  1037. vec2 scale, rawscale;
  1038. obs_sceneitem_get_scale(stretchItem, &rawscale);
  1039. vec2_set(&scale, boundsType == OBS_BOUNDS_NONE ? rawscale.x : fabsf(rawscale.x),
  1040. boundsType == OBS_BOUNDS_NONE ? rawscale.y : fabsf(rawscale.y));
  1041. vec2 max_tl;
  1042. vec2 max_br;
  1043. vec2_set(&max_tl, float(-crop.left) * scale.x, float(-crop.top) * scale.y);
  1044. vec2_set(&max_br, stretchItemSize.x + crop.right * scale.x, stretchItemSize.y + crop.bottom * scale.y);
  1045. typedef std::function<float(float, float)> minmax_func_t;
  1046. minmax_func_t min_x = scale.x < 0.0f && boundsType == OBS_BOUNDS_NONE ? maxfunc : minfunc;
  1047. minmax_func_t min_y = scale.y < 0.0f && boundsType == OBS_BOUNDS_NONE ? maxfunc : minfunc;
  1048. minmax_func_t max_x = scale.x < 0.0f && boundsType == OBS_BOUNDS_NONE ? minfunc : maxfunc;
  1049. minmax_func_t max_y = scale.y < 0.0f && boundsType == OBS_BOUNDS_NONE ? minfunc : maxfunc;
  1050. pos3.x = min_x(pos3.x, max_br.x);
  1051. pos3.x = max_x(pos3.x, max_tl.x);
  1052. pos3.y = min_y(pos3.y, max_br.y);
  1053. pos3.y = max_y(pos3.y, max_tl.y);
  1054. if (stretchFlags & ITEM_LEFT) {
  1055. float maxX = stretchItemSize.x - (2.0 * scale.x);
  1056. pos3.x = tl.x = min_x(pos3.x, maxX);
  1057. } else if (stretchFlags & ITEM_RIGHT) {
  1058. float minX = (2.0 * scale.x);
  1059. pos3.x = br.x = max_x(pos3.x, minX);
  1060. }
  1061. if (stretchFlags & ITEM_TOP) {
  1062. float maxY = stretchItemSize.y - (2.0 * scale.y);
  1063. pos3.y = tl.y = min_y(pos3.y, maxY);
  1064. } else if (stretchFlags & ITEM_BOTTOM) {
  1065. float minY = (2.0 * scale.y);
  1066. pos3.y = br.y = max_y(pos3.y, minY);
  1067. }
  1068. #define ALIGN_X (ITEM_LEFT | ITEM_RIGHT)
  1069. #define ALIGN_Y (ITEM_TOP | ITEM_BOTTOM)
  1070. vec3 newPos;
  1071. vec3_zero(&newPos);
  1072. uint32_t align_x = (align & ALIGN_X);
  1073. uint32_t align_y = (align & ALIGN_Y);
  1074. if (align_x == (stretchFlags & ALIGN_X) && align_x != 0)
  1075. newPos.x = pos3.x;
  1076. else if (align & ITEM_RIGHT)
  1077. newPos.x = stretchItemSize.x;
  1078. else if (!(align & ITEM_LEFT))
  1079. newPos.x = stretchItemSize.x * 0.5f;
  1080. if (align_y == (stretchFlags & ALIGN_Y) && align_y != 0)
  1081. newPos.y = pos3.y;
  1082. else if (align & ITEM_BOTTOM)
  1083. newPos.y = stretchItemSize.y;
  1084. else if (!(align & ITEM_TOP))
  1085. newPos.y = stretchItemSize.y * 0.5f;
  1086. #undef ALIGN_X
  1087. #undef ALIGN_Y
  1088. crop = startCrop;
  1089. if (stretchFlags & ITEM_LEFT)
  1090. crop.left += int(std::round(tl.x / scale.x));
  1091. else if (stretchFlags & ITEM_RIGHT)
  1092. crop.right += int(std::round((stretchItemSize.x - br.x) / scale.x));
  1093. if (stretchFlags & ITEM_TOP)
  1094. crop.top += int(std::round(tl.y / scale.y));
  1095. else if (stretchFlags & ITEM_BOTTOM)
  1096. crop.bottom += int(std::round((stretchItemSize.y - br.y) / scale.y));
  1097. vec3_transform(&newPos, &newPos, &itemToScreen);
  1098. newPos.x = std::round(newPos.x);
  1099. newPos.y = std::round(newPos.y);
  1100. #if 0
  1101. vec3 curPos;
  1102. vec3_zero(&curPos);
  1103. obs_sceneitem_get_pos(stretchItem, (vec2*)&curPos);
  1104. blog(LOG_DEBUG, "curPos {%d, %d} - newPos {%d, %d}",
  1105. int(curPos.x), int(curPos.y),
  1106. int(newPos.x), int(newPos.y));
  1107. blog(LOG_DEBUG, "crop {%d, %d, %d, %d}",
  1108. crop.left, crop.top,
  1109. crop.right, crop.bottom);
  1110. #endif
  1111. obs_sceneitem_defer_update_begin(stretchItem);
  1112. obs_sceneitem_set_crop(stretchItem, &crop);
  1113. if (boundsType == OBS_BOUNDS_NONE)
  1114. obs_sceneitem_set_pos(stretchItem, (vec2 *)&newPos);
  1115. obs_sceneitem_defer_update_end(stretchItem);
  1116. }
  1117. void OBSBasicPreview::StretchItem(const vec2 &pos)
  1118. {
  1119. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  1120. obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(stretchItem);
  1121. uint32_t stretchFlags = (uint32_t)stretchHandle;
  1122. bool shiftDown = (modifiers & Qt::ShiftModifier);
  1123. vec3 tl, br, pos3;
  1124. vec3_zero(&tl);
  1125. vec3_set(&br, stretchItemSize.x, stretchItemSize.y, 0.0f);
  1126. vec3_set(&pos3, pos.x, pos.y, 0.0f);
  1127. vec3_transform(&pos3, &pos3, &screenToItem);
  1128. if (stretchFlags & ITEM_LEFT)
  1129. tl.x = pos3.x;
  1130. else if (stretchFlags & ITEM_RIGHT)
  1131. br.x = pos3.x;
  1132. if (stretchFlags & ITEM_TOP)
  1133. tl.y = pos3.y;
  1134. else if (stretchFlags & ITEM_BOTTOM)
  1135. br.y = pos3.y;
  1136. if (!(modifiers & Qt::ControlModifier))
  1137. SnapStretchingToScreen(tl, br);
  1138. obs_source_t *source = obs_sceneitem_get_source(stretchItem);
  1139. uint32_t source_cx = obs_source_get_width(source);
  1140. uint32_t source_cy = obs_source_get_height(source);
  1141. /* if the source's internal size has been set to 0 for whatever reason
  1142. * while resizing, do not update transform, otherwise source will be
  1143. * stuck invisible until a complete transform reset */
  1144. if (!source_cx || !source_cy)
  1145. return;
  1146. vec2 baseSize;
  1147. vec2_set(&baseSize, float(source_cx), float(source_cy));
  1148. vec2 size;
  1149. vec2_set(&size, br.x - tl.x, br.y - tl.y);
  1150. if (boundsType != OBS_BOUNDS_NONE) {
  1151. if (shiftDown)
  1152. ClampAspect(tl, br, size, baseSize);
  1153. if (tl.x > br.x)
  1154. std::swap(tl.x, br.x);
  1155. if (tl.y > br.y)
  1156. std::swap(tl.y, br.y);
  1157. vec2_abs(&size, &size);
  1158. obs_sceneitem_set_bounds(stretchItem, &size);
  1159. } else {
  1160. obs_sceneitem_crop crop;
  1161. obs_sceneitem_get_crop(stretchItem, &crop);
  1162. baseSize.x -= float(crop.left + crop.right);
  1163. baseSize.y -= float(crop.top + crop.bottom);
  1164. if (!shiftDown)
  1165. ClampAspect(tl, br, size, baseSize);
  1166. vec2_div(&size, &size, &baseSize);
  1167. obs_sceneitem_set_scale(stretchItem, &size);
  1168. }
  1169. pos3 = CalculateStretchPos(tl, br);
  1170. vec3_transform(&pos3, &pos3, &itemToScreen);
  1171. vec2 newPos;
  1172. vec2_set(&newPos, std::round(pos3.x), std::round(pos3.y));
  1173. obs_sceneitem_set_pos(stretchItem, &newPos);
  1174. }
  1175. void OBSBasicPreview::RotateItem(const vec2 &pos)
  1176. {
  1177. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  1178. OBSScene scene = main->GetCurrentScene();
  1179. Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers();
  1180. bool shiftDown = (modifiers & Qt::ShiftModifier);
  1181. bool ctrlDown = (modifiers & Qt::ControlModifier);
  1182. vec2 pos2;
  1183. vec2_copy(&pos2, &pos);
  1184. float angle = atan2(pos2.y - rotatePoint.y, pos2.x - rotatePoint.x) + RAD(90);
  1185. #define ROT_SNAP(rot, thresh) \
  1186. if (abs(angle - RAD(rot)) < RAD(thresh)) { \
  1187. angle = RAD(rot); \
  1188. }
  1189. if (shiftDown) {
  1190. for (int i = 0; i <= 360 / 15; i++) {
  1191. ROT_SNAP(i * 15 - 90, 7.5);
  1192. }
  1193. } else if (!ctrlDown) {
  1194. ROT_SNAP(rotateAngle, 5)
  1195. ROT_SNAP(-90, 5)
  1196. ROT_SNAP(-45, 5)
  1197. ROT_SNAP(0, 5)
  1198. ROT_SNAP(45, 5)
  1199. ROT_SNAP(90, 5)
  1200. ROT_SNAP(135, 5)
  1201. ROT_SNAP(180, 5)
  1202. ROT_SNAP(225, 5)
  1203. ROT_SNAP(270, 5)
  1204. ROT_SNAP(315, 5)
  1205. }
  1206. #undef ROT_SNAP
  1207. vec2 pos3;
  1208. vec2_copy(&pos3, &offsetPoint);
  1209. RotatePos(&pos3, angle);
  1210. pos3.x += rotatePoint.x;
  1211. pos3.y += rotatePoint.y;
  1212. obs_sceneitem_set_rot(stretchItem, DEG(angle));
  1213. obs_sceneitem_set_pos(stretchItem, &pos3);
  1214. }
  1215. void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event)
  1216. {
  1217. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  1218. changed = true;
  1219. QPointF qtPos = event->position();
  1220. float pixelRatio = main->GetDevicePixelRatio();
  1221. if (scrollMode && event->buttons() == Qt::LeftButton) {
  1222. scrollingOffset.x += pixelRatio * (qtPos.x() - scrollingFrom.x);
  1223. scrollingOffset.y += pixelRatio * (qtPos.y() - scrollingFrom.y);
  1224. scrollingFrom.x = qtPos.x();
  1225. scrollingFrom.y = qtPos.y();
  1226. emit DisplayResized();
  1227. return;
  1228. }
  1229. if (locked)
  1230. return;
  1231. bool updateCursor = false;
  1232. if (mouseDown) {
  1233. vec2 pos = GetMouseEventPos(event);
  1234. if (!mouseMoved && !mouseOverItems && stretchHandle == ItemHandle::None) {
  1235. ProcessClick(startPos);
  1236. mouseOverItems = SelectedAtPos(startPos);
  1237. }
  1238. pos.x = std::round(pos.x);
  1239. pos.y = std::round(pos.y);
  1240. if (stretchHandle != ItemHandle::None) {
  1241. if (obs_sceneitem_locked(stretchItem))
  1242. return;
  1243. selectionBox = false;
  1244. OBSScene scene = main->GetCurrentScene();
  1245. obs_sceneitem_t *group = obs_sceneitem_get_group(scene, stretchItem);
  1246. if (group) {
  1247. vec3 group_pos;
  1248. vec3_set(&group_pos, pos.x, pos.y, 0.0f);
  1249. vec3_transform(&group_pos, &group_pos, &invGroupTransform);
  1250. pos.x = group_pos.x;
  1251. pos.y = group_pos.y;
  1252. }
  1253. if (stretchHandle == ItemHandle::Rot) {
  1254. RotateItem(pos);
  1255. setCursor(Qt::ClosedHandCursor);
  1256. } else if (cropping)
  1257. CropItem(pos);
  1258. else
  1259. StretchItem(pos);
  1260. } else if (mouseOverItems) {
  1261. if (cursor().shape() != Qt::SizeAllCursor)
  1262. setCursor(Qt::SizeAllCursor);
  1263. selectionBox = false;
  1264. MoveItems(pos);
  1265. } else {
  1266. selectionBox = true;
  1267. if (!mouseMoved)
  1268. DoSelect(startPos);
  1269. BoxItems(startPos, pos);
  1270. }
  1271. mouseMoved = true;
  1272. mousePos = pos;
  1273. } else {
  1274. vec2 pos = GetMouseEventPos(event);
  1275. OBSSceneItem item = GetItemAtPos(pos, true);
  1276. std::lock_guard<std::mutex> lock(selectMutex);
  1277. hoveredPreviewItems.clear();
  1278. hoveredPreviewItems.push_back(item);
  1279. if (!mouseMoved && hoveredPreviewItems.size() > 0) {
  1280. mousePos = pos;
  1281. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  1282. float scale = main->GetDevicePixelRatio();
  1283. float x = qtPos.x() - main->previewX / scale;
  1284. float y = qtPos.y() - main->previewY / scale;
  1285. vec2_set(&startPos, x, y);
  1286. updateCursor = true;
  1287. }
  1288. }
  1289. if (updateCursor) {
  1290. GetStretchHandleData(startPos, true);
  1291. uint32_t stretchFlags = (uint32_t)stretchHandle;
  1292. UpdateCursor(stretchFlags);
  1293. }
  1294. }
  1295. void OBSBasicPreview::leaveEvent(QEvent *)
  1296. {
  1297. std::lock_guard<std::mutex> lock(selectMutex);
  1298. if (!selectionBox)
  1299. hoveredPreviewItems.clear();
  1300. }
  1301. static void DrawLine(float x1, float y1, float x2, float y2, float thickness, vec2 scale)
  1302. {
  1303. float ySide = (y1 == y2) ? (y1 < 0.5f ? 1.0f : -1.0f) : 0.0f;
  1304. float xSide = (x1 == x2) ? (x1 < 0.5f ? 1.0f : -1.0f) : 0.0f;
  1305. gs_render_start(true);
  1306. gs_vertex2f(x1 - (xSide * (thickness / scale.x) / 2), y1 + (ySide * (thickness / scale.y) / 2));
  1307. gs_vertex2f(x1 + (xSide * (thickness / scale.x) / 2), y1 - (ySide * (thickness / scale.y) / 2));
  1308. gs_vertex2f(x2 + (xSide * (thickness / scale.x) / 2), y2 + (ySide * (thickness / scale.y) / 2));
  1309. gs_vertex2f(x2 - (xSide * (thickness / scale.x) / 2), y2 - (ySide * (thickness / scale.y) / 2));
  1310. gs_vertex2f(x1 - (xSide * (thickness / scale.x) / 2), y1 + (ySide * (thickness / scale.y) / 2));
  1311. gs_vertbuffer_t *line = gs_render_save();
  1312. gs_load_vertexbuffer(line);
  1313. gs_draw(GS_TRISTRIP, 0, 0);
  1314. gs_vertexbuffer_destroy(line);
  1315. }
  1316. static void DrawSquareAtPos(float x, float y, float pixelRatio)
  1317. {
  1318. struct vec3 pos;
  1319. vec3_set(&pos, x, y, 0.0f);
  1320. struct matrix4 matrix;
  1321. gs_matrix_get(&matrix);
  1322. vec3_transform(&pos, &pos, &matrix);
  1323. gs_matrix_push();
  1324. gs_matrix_identity();
  1325. gs_matrix_translate(&pos);
  1326. gs_matrix_translate3f(-HANDLE_RADIUS * pixelRatio, -HANDLE_RADIUS * pixelRatio, 0.0f);
  1327. gs_matrix_scale3f(HANDLE_RADIUS * pixelRatio * 2, HANDLE_RADIUS * pixelRatio * 2, 1.0f);
  1328. gs_draw(GS_TRISTRIP, 0, 0);
  1329. gs_matrix_pop();
  1330. }
  1331. static void DrawRotationHandle(gs_vertbuffer_t *circle, float rot, float pixelRatio, bool invert)
  1332. {
  1333. struct vec3 pos;
  1334. vec3_set(&pos, 0.5f, invert ? 1.0f : 0.0f, 0.0f);
  1335. struct matrix4 matrix;
  1336. gs_matrix_get(&matrix);
  1337. vec3_transform(&pos, &pos, &matrix);
  1338. gs_render_start(true);
  1339. gs_vertex2f(0.5f - 0.34f / HANDLE_RADIUS, 0.5f);
  1340. gs_vertex2f(0.5f - 0.34f / HANDLE_RADIUS, -2.0f);
  1341. gs_vertex2f(0.5f + 0.34f / HANDLE_RADIUS, -2.0f);
  1342. gs_vertex2f(0.5f + 0.34f / HANDLE_RADIUS, 0.5f);
  1343. gs_vertex2f(0.5f - 0.34f / HANDLE_RADIUS, 0.5f);
  1344. gs_vertbuffer_t *line = gs_render_save();
  1345. gs_load_vertexbuffer(line);
  1346. gs_matrix_push();
  1347. gs_matrix_identity();
  1348. gs_matrix_translate(&pos);
  1349. gs_matrix_rotaa4f(0.0f, 0.0f, 1.0f, RAD(rot));
  1350. gs_matrix_translate3f(-HANDLE_RADIUS * 1.5 * pixelRatio, -HANDLE_RADIUS * 1.5 * pixelRatio, 0.0f);
  1351. gs_matrix_scale3f(HANDLE_RADIUS * 3 * pixelRatio, HANDLE_RADIUS * 3 * pixelRatio, 1.0f);
  1352. gs_draw(GS_TRISTRIP, 0, 0);
  1353. gs_matrix_translate3f(0.0f, -HANDLE_RADIUS * 2 / 3, 0.0f);
  1354. gs_load_vertexbuffer(circle);
  1355. gs_draw(GS_TRISTRIP, 0, 0);
  1356. gs_matrix_pop();
  1357. gs_vertexbuffer_destroy(line);
  1358. }
  1359. static void DrawStripedLine(float x1, float y1, float x2, float y2, float thickness, vec2 scale)
  1360. {
  1361. float ySide = (y1 == y2) ? (y1 < 0.5f ? 1.0f : -1.0f) : 0.0f;
  1362. float xSide = (x1 == x2) ? (x1 < 0.5f ? 1.0f : -1.0f) : 0.0f;
  1363. float dist = sqrt(pow((x1 - x2) * scale.x, 2) + pow((y1 - y2) * scale.y, 2));
  1364. float offX = (x2 - x1) / dist;
  1365. float offY = (y2 - y1) / dist;
  1366. for (int i = 0, l = ceil(dist / 15); i < l; i++) {
  1367. gs_render_start(true);
  1368. float xx1 = x1 + i * 15 * offX;
  1369. float yy1 = y1 + i * 15 * offY;
  1370. float dx;
  1371. float dy;
  1372. if (x1 < x2) {
  1373. dx = std::min(xx1 + 7.5f * offX, x2);
  1374. } else {
  1375. dx = std::max(xx1 + 7.5f * offX, x2);
  1376. }
  1377. if (y1 < y2) {
  1378. dy = std::min(yy1 + 7.5f * offY, y2);
  1379. } else {
  1380. dy = std::max(yy1 + 7.5f * offY, y2);
  1381. }
  1382. gs_vertex2f(xx1, yy1);
  1383. gs_vertex2f(xx1 + (xSide * (thickness / scale.x)), yy1 + (ySide * (thickness / scale.y)));
  1384. gs_vertex2f(dx, dy);
  1385. gs_vertex2f(dx + (xSide * (thickness / scale.x)), dy + (ySide * (thickness / scale.y)));
  1386. gs_vertbuffer_t *line = gs_render_save();
  1387. gs_load_vertexbuffer(line);
  1388. gs_draw(GS_TRISTRIP, 0, 0);
  1389. gs_vertexbuffer_destroy(line);
  1390. }
  1391. }
  1392. static void DrawRect(float thickness, vec2 scale)
  1393. {
  1394. gs_render_start(true);
  1395. gs_vertex2f(0.0f, 0.0f);
  1396. gs_vertex2f(0.0f + (thickness / scale.x), 0.0f);
  1397. gs_vertex2f(0.0f, 1.0f);
  1398. gs_vertex2f(0.0f + (thickness / scale.x), 1.0f);
  1399. gs_vertex2f(0.0f, 1.0f - (thickness / scale.y));
  1400. gs_vertex2f(1.0f, 1.0f);
  1401. gs_vertex2f(1.0f, 1.0f - (thickness / scale.y));
  1402. gs_vertex2f(1.0f - (thickness / scale.x), 1.0f);
  1403. gs_vertex2f(1.0f, 0.0f);
  1404. gs_vertex2f(1.0f - (thickness / scale.x), 0.0f);
  1405. gs_vertex2f(1.0f, 0.0f + (thickness / scale.y));
  1406. gs_vertex2f(0.0f, 0.0f);
  1407. gs_vertex2f(0.0f, 0.0f + (thickness / scale.y));
  1408. gs_vertbuffer_t *rect = gs_render_save();
  1409. gs_load_vertexbuffer(rect);
  1410. gs_draw(GS_TRISTRIP, 0, 0);
  1411. gs_vertexbuffer_destroy(rect);
  1412. }
  1413. static inline bool crop_enabled(const obs_sceneitem_crop *crop)
  1414. {
  1415. return crop->left > 0 || crop->top > 0 || crop->right > 0 || crop->bottom > 0;
  1416. }
  1417. bool OBSBasicPreview::DrawSelectedOverflow(obs_scene_t *, obs_sceneitem_t *item, void *param)
  1418. {
  1419. if (obs_sceneitem_locked(item))
  1420. return true;
  1421. if (!SceneItemHasVideo(item))
  1422. return true;
  1423. OBSBasicPreview *prev = reinterpret_cast<OBSBasicPreview *>(param);
  1424. if (!prev->GetOverflowSelectionHidden() && !obs_sceneitem_visible(item))
  1425. return true;
  1426. if (obs_sceneitem_is_group(item)) {
  1427. matrix4 mat;
  1428. obs_sceneitem_get_draw_transform(item, &mat);
  1429. gs_matrix_push();
  1430. gs_matrix_mul(&mat);
  1431. obs_sceneitem_group_enum_items(item, DrawSelectedOverflow, param);
  1432. gs_matrix_pop();
  1433. }
  1434. if (!prev->GetOverflowAlwaysVisible() && !obs_sceneitem_selected(item))
  1435. return true;
  1436. matrix4 boxTransform;
  1437. matrix4 invBoxTransform;
  1438. obs_sceneitem_get_box_transform(item, &boxTransform);
  1439. matrix4_inv(&invBoxTransform, &boxTransform);
  1440. vec3 bounds[] = {
  1441. {{{0.f, 0.f, 0.f}}},
  1442. {{{1.f, 0.f, 0.f}}},
  1443. {{{0.f, 1.f, 0.f}}},
  1444. {{{1.f, 1.f, 0.f}}},
  1445. };
  1446. bool visible = std::all_of(std::begin(bounds), std::end(bounds), [&](const vec3 &b) {
  1447. vec3 pos;
  1448. vec3_transform(&pos, &b, &boxTransform);
  1449. vec3_transform(&pos, &pos, &invBoxTransform);
  1450. return CloseFloat(pos.x, b.x) && CloseFloat(pos.y, b.y);
  1451. });
  1452. if (!visible)
  1453. return true;
  1454. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawSelectedOverflow");
  1455. obs_transform_info info;
  1456. obs_sceneitem_get_info2(item, &info);
  1457. gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_REPEAT);
  1458. gs_eparam_t *image = gs_effect_get_param_by_name(solid, "image");
  1459. gs_eparam_t *scale = gs_effect_get_param_by_name(solid, "scale");
  1460. vec2 s;
  1461. vec2_set(&s, boxTransform.x.x / 96, boxTransform.y.y / 96);
  1462. gs_effect_set_vec2(scale, &s);
  1463. gs_effect_set_texture(image, prev->overflow);
  1464. gs_matrix_push();
  1465. gs_matrix_mul(&boxTransform);
  1466. obs_sceneitem_crop crop;
  1467. obs_sceneitem_get_crop(item, &crop);
  1468. while (gs_effect_loop(solid, "Draw")) {
  1469. gs_draw_sprite(prev->overflow, 0, 1, 1);
  1470. }
  1471. gs_matrix_pop();
  1472. GS_DEBUG_MARKER_END();
  1473. return true;
  1474. }
  1475. bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *, obs_sceneitem_t *item, void *param)
  1476. {
  1477. if (obs_sceneitem_locked(item))
  1478. return true;
  1479. if (!SceneItemHasVideo(item))
  1480. return true;
  1481. OBSBasicPreview *prev = reinterpret_cast<OBSBasicPreview *>(param);
  1482. if (obs_sceneitem_is_group(item)) {
  1483. matrix4 mat;
  1484. obs_transform_info groupInfo;
  1485. obs_sceneitem_get_draw_transform(item, &mat);
  1486. obs_sceneitem_get_info2(item, &groupInfo);
  1487. prev->groupRot = groupInfo.rot;
  1488. gs_matrix_push();
  1489. gs_matrix_mul(&mat);
  1490. obs_sceneitem_group_enum_items(item, DrawSelectedItem, prev);
  1491. gs_matrix_pop();
  1492. prev->groupRot = 0.0f;
  1493. }
  1494. OBSBasic *main = OBSBasic::Get();
  1495. float pixelRatio = main->GetDevicePixelRatio();
  1496. bool hovered = false;
  1497. {
  1498. std::lock_guard<std::mutex> lock(prev->selectMutex);
  1499. for (size_t i = 0; i < prev->hoveredPreviewItems.size(); i++) {
  1500. if (prev->hoveredPreviewItems[i] == item) {
  1501. hovered = true;
  1502. break;
  1503. }
  1504. }
  1505. }
  1506. bool selected = obs_sceneitem_selected(item);
  1507. if (!selected && !hovered)
  1508. return true;
  1509. matrix4 boxTransform;
  1510. matrix4 invBoxTransform;
  1511. obs_sceneitem_get_box_transform(item, &boxTransform);
  1512. matrix4_inv(&invBoxTransform, &boxTransform);
  1513. vec3 bounds[] = {
  1514. {{{0.f, 0.f, 0.f}}},
  1515. {{{1.f, 0.f, 0.f}}},
  1516. {{{0.f, 1.f, 0.f}}},
  1517. {{{1.f, 1.f, 0.f}}},
  1518. };
  1519. main->GetCameraIcon();
  1520. QColor selColor = main->GetSelectionColor();
  1521. QColor cropColor = main->GetCropColor();
  1522. QColor hoverColor = main->GetHoverColor();
  1523. vec4 red;
  1524. vec4 green;
  1525. vec4 blue;
  1526. vec4_set(&red, selColor.redF(), selColor.greenF(), selColor.blueF(), 1.0f);
  1527. vec4_set(&green, cropColor.redF(), cropColor.greenF(), cropColor.blueF(), 1.0f);
  1528. vec4_set(&blue, hoverColor.redF(), hoverColor.greenF(), hoverColor.blueF(), 1.0f);
  1529. bool visible = std::all_of(std::begin(bounds), std::end(bounds), [&](const vec3 &b) {
  1530. vec3 pos;
  1531. vec3_transform(&pos, &b, &boxTransform);
  1532. vec3_transform(&pos, &pos, &invBoxTransform);
  1533. return CloseFloat(pos.x, b.x) && CloseFloat(pos.y, b.y);
  1534. });
  1535. if (!visible)
  1536. return true;
  1537. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawSelectedItem");
  1538. matrix4 curTransform;
  1539. vec2 boxScale;
  1540. gs_matrix_get(&curTransform);
  1541. obs_sceneitem_get_box_scale(item, &boxScale);
  1542. boxScale.x *= curTransform.x.x;
  1543. boxScale.y *= curTransform.y.y;
  1544. obs_transform_info info;
  1545. obs_sceneitem_get_info2(item, &info);
  1546. gs_matrix_push();
  1547. gs_matrix_mul(&boxTransform);
  1548. obs_sceneitem_crop crop;
  1549. obs_sceneitem_get_crop(item, &crop);
  1550. gs_effect_t *eff = gs_get_effect();
  1551. gs_eparam_t *colParam = gs_effect_get_param_by_name(eff, "color");
  1552. gs_effect_set_vec4(colParam, &red);
  1553. if (info.bounds_type == OBS_BOUNDS_NONE && crop_enabled(&crop)) {
  1554. #define DRAW_SIDE(side, x1, y1, x2, y2) \
  1555. if (hovered && !selected) { \
  1556. gs_effect_set_vec4(colParam, &blue); \
  1557. DrawLine(x1, y1, x2, y2, HANDLE_RADIUS *pixelRatio / 2, boxScale); \
  1558. } else if (crop.side > 0) { \
  1559. gs_effect_set_vec4(colParam, &green); \
  1560. DrawStripedLine(x1, y1, x2, y2, HANDLE_RADIUS *pixelRatio / 2, boxScale); \
  1561. } else { \
  1562. DrawLine(x1, y1, x2, y2, HANDLE_RADIUS *pixelRatio / 2, boxScale); \
  1563. } \
  1564. gs_effect_set_vec4(colParam, &red);
  1565. DRAW_SIDE(left, 0.0f, 0.0f, 0.0f, 1.0f);
  1566. DRAW_SIDE(top, 0.0f, 0.0f, 1.0f, 0.0f);
  1567. DRAW_SIDE(right, 1.0f, 0.0f, 1.0f, 1.0f);
  1568. DRAW_SIDE(bottom, 0.0f, 1.0f, 1.0f, 1.0f);
  1569. #undef DRAW_SIDE
  1570. } else {
  1571. if (!selected) {
  1572. gs_effect_set_vec4(colParam, &blue);
  1573. DrawRect(HANDLE_RADIUS * pixelRatio / 2, boxScale);
  1574. } else {
  1575. DrawRect(HANDLE_RADIUS * pixelRatio / 2, boxScale);
  1576. }
  1577. }
  1578. gs_load_vertexbuffer(main->box);
  1579. gs_effect_set_vec4(colParam, &red);
  1580. if (selected) {
  1581. DrawSquareAtPos(0.0f, 0.0f, pixelRatio);
  1582. DrawSquareAtPos(0.0f, 1.0f, pixelRatio);
  1583. DrawSquareAtPos(1.0f, 0.0f, pixelRatio);
  1584. DrawSquareAtPos(1.0f, 1.0f, pixelRatio);
  1585. DrawSquareAtPos(0.5f, 0.0f, pixelRatio);
  1586. DrawSquareAtPos(0.0f, 0.5f, pixelRatio);
  1587. DrawSquareAtPos(0.5f, 1.0f, pixelRatio);
  1588. DrawSquareAtPos(1.0f, 0.5f, pixelRatio);
  1589. if (!prev->circleFill) {
  1590. gs_render_start(true);
  1591. float angle = 180;
  1592. for (int i = 0, l = 40; i < l; i++) {
  1593. gs_vertex2f(sin(RAD(angle)) / 2 + 0.5f, cos(RAD(angle)) / 2 + 0.5f);
  1594. angle += 360 / l;
  1595. gs_vertex2f(sin(RAD(angle)) / 2 + 0.5f, cos(RAD(angle)) / 2 + 0.5f);
  1596. gs_vertex2f(0.5f, 1.0f);
  1597. }
  1598. prev->circleFill = gs_render_save();
  1599. }
  1600. bool invert = info.scale.y < 0.0f && info.bounds_type == OBS_BOUNDS_NONE;
  1601. DrawRotationHandle(prev->circleFill, info.rot + prev->groupRot, pixelRatio, invert);
  1602. }
  1603. gs_matrix_pop();
  1604. GS_DEBUG_MARKER_END();
  1605. return true;
  1606. }
  1607. bool OBSBasicPreview::DrawSelectionBox(float x1, float y1, float x2, float y2, gs_vertbuffer_t *rectFill)
  1608. {
  1609. OBSBasic *main = OBSBasic::Get();
  1610. float pixelRatio = main->GetDevicePixelRatio();
  1611. x1 = std::round(x1);
  1612. x2 = std::round(x2);
  1613. y1 = std::round(y1);
  1614. y2 = std::round(y2);
  1615. gs_effect_t *eff = gs_get_effect();
  1616. gs_eparam_t *colParam = gs_effect_get_param_by_name(eff, "color");
  1617. vec4 fillColor;
  1618. vec4_set(&fillColor, 0.7f, 0.7f, 0.7f, 0.5f);
  1619. vec4 borderColor;
  1620. vec4_set(&borderColor, 1.0f, 1.0f, 1.0f, 1.0f);
  1621. vec2 scale;
  1622. vec2_set(&scale, std::abs(x2 - x1), std::abs(y2 - y1));
  1623. gs_matrix_push();
  1624. gs_matrix_identity();
  1625. gs_matrix_translate3f(x1, y1, 0.0f);
  1626. gs_matrix_scale3f(x2 - x1, y2 - y1, 1.0f);
  1627. gs_effect_set_vec4(colParam, &fillColor);
  1628. gs_load_vertexbuffer(rectFill);
  1629. gs_draw(GS_TRISTRIP, 0, 0);
  1630. gs_effect_set_vec4(colParam, &borderColor);
  1631. DrawRect(HANDLE_RADIUS * pixelRatio / 2, scale);
  1632. gs_matrix_pop();
  1633. return true;
  1634. }
  1635. void OBSBasicPreview::DrawOverflow()
  1636. {
  1637. if (locked)
  1638. return;
  1639. if (overflowHidden)
  1640. return;
  1641. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawOverflow");
  1642. if (!overflow) {
  1643. std::string path;
  1644. GetDataFilePath("images/overflow.png", path);
  1645. overflow = gs_texture_create_from_file(path.c_str());
  1646. }
  1647. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  1648. OBSScene scene = main->GetCurrentScene();
  1649. if (scene) {
  1650. gs_matrix_push();
  1651. gs_matrix_scale3f(main->previewScale, main->previewScale, 1.0f);
  1652. obs_scene_enum_items(scene, DrawSelectedOverflow, this);
  1653. gs_matrix_pop();
  1654. }
  1655. gs_load_vertexbuffer(nullptr);
  1656. GS_DEBUG_MARKER_END();
  1657. }
  1658. void OBSBasicPreview::DrawSceneEditing()
  1659. {
  1660. if (locked)
  1661. return;
  1662. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_DEFAULT, "DrawSceneEditing");
  1663. OBSBasic *main = reinterpret_cast<OBSBasic *>(App()->GetMainWindow());
  1664. gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
  1665. gs_technique_t *tech = gs_effect_get_technique(solid, "Solid");
  1666. gs_technique_begin(tech);
  1667. gs_technique_begin_pass(tech, 0);
  1668. OBSScene scene = main->GetCurrentScene();
  1669. if (scene) {
  1670. gs_matrix_push();
  1671. gs_matrix_scale3f(main->previewScale, main->previewScale, 1.0f);
  1672. obs_scene_enum_items(scene, DrawSelectedItem, this);
  1673. gs_matrix_pop();
  1674. }
  1675. if (selectionBox) {
  1676. if (!rectFill) {
  1677. gs_render_start(true);
  1678. gs_vertex2f(0.0f, 0.0f);
  1679. gs_vertex2f(1.0f, 0.0f);
  1680. gs_vertex2f(0.0f, 1.0f);
  1681. gs_vertex2f(1.0f, 1.0f);
  1682. rectFill = gs_render_save();
  1683. }
  1684. DrawSelectionBox(startPos.x * main->previewScale, startPos.y * main->previewScale,
  1685. mousePos.x * main->previewScale, mousePos.y * main->previewScale, rectFill);
  1686. }
  1687. gs_load_vertexbuffer(nullptr);
  1688. gs_technique_end_pass(tech);
  1689. gs_technique_end(tech);
  1690. GS_DEBUG_MARKER_END();
  1691. }
  1692. void OBSBasicPreview::ResetScrollingOffset()
  1693. {
  1694. vec2_zero(&scrollingOffset);
  1695. }
  1696. void OBSBasicPreview::SetScalingLevel(int32_t newScalingLevelVal)
  1697. {
  1698. newScalingLevelVal = std::clamp(newScalingLevelVal, -MAX_SCALING_LEVEL, MAX_SCALING_LEVEL);
  1699. float newScalingAmountVal = pow(ZOOM_SENSITIVITY, float(newScalingLevelVal));
  1700. scalingLevel = newScalingLevelVal;
  1701. SetScalingAmount(newScalingAmountVal);
  1702. }
  1703. void OBSBasicPreview::SetScalingAmount(float newScalingAmountVal)
  1704. {
  1705. scrollingOffset.x *= newScalingAmountVal / scalingAmount;
  1706. scrollingOffset.y *= newScalingAmountVal / scalingAmount;
  1707. if (scalingAmount == newScalingAmountVal)
  1708. return;
  1709. scalingAmount = newScalingAmountVal;
  1710. emit scalingChanged(scalingAmount);
  1711. }
  1712. void OBSBasicPreview::SetScalingLevelAndAmount(int32_t newScalingLevelVal, float newScalingAmountVal)
  1713. {
  1714. newScalingLevelVal = std::clamp(newScalingLevelVal, -MAX_SCALING_LEVEL, MAX_SCALING_LEVEL);
  1715. scalingLevel = newScalingLevelVal;
  1716. SetScalingAmount(newScalingAmountVal);
  1717. }
  1718. OBSBasicPreview *OBSBasicPreview::Get()
  1719. {
  1720. return OBSBasic::Get()->ui->preview;
  1721. }
  1722. static obs_source_t *CreateLabel(float pixelRatio, int i)
  1723. {
  1724. OBSDataAutoRelease settings = obs_data_create();
  1725. OBSDataAutoRelease font = obs_data_create();
  1726. #if defined(_WIN32)
  1727. obs_data_set_string(font, "face", "Arial");
  1728. #elif defined(__APPLE__)
  1729. obs_data_set_string(font, "face", "Helvetica");
  1730. #else
  1731. obs_data_set_string(font, "face", "Monospace");
  1732. #endif
  1733. obs_data_set_int(font, "flags", 1); // Bold text
  1734. obs_data_set_int(font, "size", 16 * pixelRatio);
  1735. obs_data_set_obj(settings, "font", font);
  1736. obs_data_set_bool(settings, "outline", true);
  1737. #ifdef _WIN32
  1738. obs_data_set_int(settings, "outline_color", 0x000000);
  1739. obs_data_set_int(settings, "outline_size", 3);
  1740. const char *text_source_id = "text_gdiplus";
  1741. #else
  1742. const char *text_source_id = "text_ft2_source";
  1743. #endif
  1744. DStr name;
  1745. dstr_printf(name, "Preview spacing label %d", i);
  1746. return obs_source_create_private(text_source_id, name, settings);
  1747. }
  1748. static void SetLabelText(int sourceIndex, int px)
  1749. {
  1750. OBSBasicPreview *prev = OBSBasicPreview::Get();
  1751. if (px == prev->spacerPx[sourceIndex])
  1752. return;
  1753. std::string text = std::to_string(px) + " px";
  1754. obs_source_t *source = prev->spacerLabel[sourceIndex];
  1755. OBSDataAutoRelease settings = obs_source_get_settings(source);
  1756. obs_data_set_string(settings, "text", text.c_str());
  1757. obs_source_update(source, settings);
  1758. prev->spacerPx[sourceIndex] = px;
  1759. }
  1760. static void DrawLabel(OBSSource source, vec3 &pos, vec3 &viewport)
  1761. {
  1762. if (!source)
  1763. return;
  1764. vec3_mul(&pos, &pos, &viewport);
  1765. gs_matrix_push();
  1766. gs_matrix_identity();
  1767. gs_matrix_translate(&pos);
  1768. obs_source_video_render(source);
  1769. gs_matrix_pop();
  1770. }
  1771. static void DrawSpacingLine(vec3 &start, vec3 &end, vec3 &viewport, float pixelRatio)
  1772. {
  1773. OBSBasic *main = OBSBasic::Get();
  1774. matrix4 transform;
  1775. matrix4_identity(&transform);
  1776. transform.x.x = viewport.x;
  1777. transform.y.y = viewport.y;
  1778. gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
  1779. gs_technique_t *tech = gs_effect_get_technique(solid, "Solid");
  1780. QColor selColor = main->GetSelectionColor();
  1781. vec4 color;
  1782. vec4_set(&color, selColor.redF(), selColor.greenF(), selColor.blueF(), 1.0f);
  1783. gs_effect_set_vec4(gs_effect_get_param_by_name(solid, "color"), &color);
  1784. gs_technique_begin(tech);
  1785. gs_technique_begin_pass(tech, 0);
  1786. gs_matrix_push();
  1787. gs_matrix_mul(&transform);
  1788. vec2 scale;
  1789. vec2_set(&scale, viewport.x, viewport.y);
  1790. DrawLine(start.x, start.y, end.x, end.y, pixelRatio * (HANDLE_RADIUS / 2), scale);
  1791. gs_matrix_pop();
  1792. gs_load_vertexbuffer(nullptr);
  1793. gs_technique_end_pass(tech);
  1794. gs_technique_end(tech);
  1795. }
  1796. static void RenderSpacingHelper(int sourceIndex, vec3 &start, vec3 &end, vec3 &viewport, float pixelRatio)
  1797. {
  1798. bool horizontal = (sourceIndex == 2 || sourceIndex == 3);
  1799. // If outside of preview, don't render
  1800. if (!((horizontal && (end.x >= start.x)) || (!horizontal && (end.y >= start.y))))
  1801. return;
  1802. float length = vec3_dist(&start, &end);
  1803. obs_video_info ovi;
  1804. obs_get_video_info(&ovi);
  1805. float px;
  1806. if (horizontal) {
  1807. px = length * ovi.base_width;
  1808. } else {
  1809. px = length * ovi.base_height;
  1810. }
  1811. if (px <= 0.0f)
  1812. return;
  1813. OBSBasicPreview *prev = OBSBasicPreview::Get();
  1814. obs_source_t *source = prev->spacerLabel[sourceIndex];
  1815. vec3 labelSize, labelPos;
  1816. vec3_set(&labelSize, obs_source_get_width(source), obs_source_get_height(source), 1.0f);
  1817. vec3_div(&labelSize, &labelSize, &viewport);
  1818. vec3 labelMargin;
  1819. vec3_set(&labelMargin, SPACER_LABEL_MARGIN * pixelRatio, SPACER_LABEL_MARGIN * pixelRatio, 1.0f);
  1820. vec3_div(&labelMargin, &labelMargin, &viewport);
  1821. vec3_set(&labelPos, end.x, end.y, end.z);
  1822. if (horizontal) {
  1823. labelPos.x -= (end.x - start.x) / 2;
  1824. labelPos.x -= labelSize.x / 2;
  1825. labelPos.y -= labelMargin.y + (labelSize.y / 2) + (HANDLE_RADIUS / viewport.y);
  1826. } else {
  1827. labelPos.y -= (end.y - start.y) / 2;
  1828. labelPos.y -= labelSize.y / 2;
  1829. labelPos.x += labelMargin.x;
  1830. }
  1831. DrawSpacingLine(start, end, viewport, pixelRatio);
  1832. SetLabelText(sourceIndex, (int)px);
  1833. DrawLabel(source, labelPos, viewport);
  1834. }
  1835. void OBSBasicPreview::DrawSpacingHelpers()
  1836. {
  1837. if (locked)
  1838. return;
  1839. OBSBasic *main = OBSBasic::Get();
  1840. vec2 s;
  1841. SceneFindBoxData data(s, s);
  1842. OBSScene scene = main->GetCurrentScene();
  1843. obs_scene_enum_items(scene, FindSelected, &data);
  1844. if (data.sceneItems.size() != 1)
  1845. return;
  1846. OBSSceneItem item = data.sceneItems[0];
  1847. if (!item)
  1848. return;
  1849. if (obs_sceneitem_locked(item))
  1850. return;
  1851. vec2 itemSize = GetItemSize(item);
  1852. if (itemSize.x == 0.0f || itemSize.y == 0.0f)
  1853. return;
  1854. obs_sceneitem_t *parentGroup = obs_sceneitem_get_group(scene, item);
  1855. if (parentGroup && obs_sceneitem_locked(parentGroup))
  1856. return;
  1857. matrix4 boxTransform;
  1858. obs_sceneitem_get_box_transform(item, &boxTransform);
  1859. obs_transform_info oti;
  1860. obs_sceneitem_get_info2(item, &oti);
  1861. obs_video_info ovi;
  1862. obs_get_video_info(&ovi);
  1863. vec3 size;
  1864. vec3_set(&size, ovi.base_width, ovi.base_height, 1.0f);
  1865. // Init box transform side locations
  1866. vec3 left, right, top, bottom;
  1867. vec3_set(&left, 0.0f, 0.5f, 1.0f);
  1868. vec3_set(&right, 1.0f, 0.5f, 1.0f);
  1869. vec3_set(&top, 0.5f, 0.0f, 1.0f);
  1870. vec3_set(&bottom, 0.5f, 1.0f, 1.0f);
  1871. // Decide which side to use with box transform, based on rotation
  1872. // Seems hacky, probably a better way to do it
  1873. float rot = oti.rot;
  1874. if (parentGroup) {
  1875. obs_transform_info groupOti;
  1876. obs_sceneitem_get_info2(parentGroup, &groupOti);
  1877. //Correct the scene item rotation angle
  1878. rot = oti.rot + groupOti.rot;
  1879. // Correct the scene item box transform
  1880. // Based on scale, rotation angle, position of parent's group
  1881. matrix4_scale3f(&boxTransform, &boxTransform, groupOti.scale.x, groupOti.scale.y, 1.0f);
  1882. matrix4_rotate_aa4f(&boxTransform, &boxTransform, 0.0f, 0.0f, 1.0f, RAD(groupOti.rot));
  1883. matrix4_translate3f(&boxTransform, &boxTransform, groupOti.pos.x, groupOti.pos.y, 0.0f);
  1884. }
  1885. // Switch top/bottom or right/left if scale is negative
  1886. if (oti.scale.x < 0.0f && oti.bounds_type == OBS_BOUNDS_NONE) {
  1887. vec3 l = left;
  1888. vec3 r = right;
  1889. vec3_copy(&left, &r);
  1890. vec3_copy(&right, &l);
  1891. }
  1892. if (oti.scale.y < 0.0f && oti.bounds_type == OBS_BOUNDS_NONE) {
  1893. vec3 t = top;
  1894. vec3 b = bottom;
  1895. vec3_copy(&top, &b);
  1896. vec3_copy(&bottom, &t);
  1897. }
  1898. if (rot >= HELPER_ROT_BREAKPOINT) {
  1899. for (float i = HELPER_ROT_BREAKPOINT; i <= 360.0f; i += 90.0f) {
  1900. if (rot < i)
  1901. break;
  1902. vec3 l = left;
  1903. vec3 r = right;
  1904. vec3 t = top;
  1905. vec3 b = bottom;
  1906. vec3_copy(&top, &l);
  1907. vec3_copy(&right, &t);
  1908. vec3_copy(&bottom, &r);
  1909. vec3_copy(&left, &b);
  1910. }
  1911. } else if (rot <= -HELPER_ROT_BREAKPOINT) {
  1912. for (float i = -HELPER_ROT_BREAKPOINT; i >= -360.0f; i -= 90.0f) {
  1913. if (rot > i)
  1914. break;
  1915. vec3 l = left;
  1916. vec3 r = right;
  1917. vec3 t = top;
  1918. vec3 b = bottom;
  1919. vec3_copy(&top, &r);
  1920. vec3_copy(&right, &b);
  1921. vec3_copy(&bottom, &l);
  1922. vec3_copy(&left, &t);
  1923. }
  1924. }
  1925. // Get sides of box transform
  1926. left = GetTransformedPos(left.x, left.y, boxTransform);
  1927. right = GetTransformedPos(right.x, right.y, boxTransform);
  1928. top = GetTransformedPos(top.x, top.y, boxTransform);
  1929. bottom = GetTransformedPos(bottom.x, bottom.y, boxTransform);
  1930. bottom.y = size.y - bottom.y;
  1931. right.x = size.x - right.x;
  1932. // Init viewport
  1933. vec3 viewport;
  1934. vec3_set(&viewport, main->previewCX, main->previewCY, 1.0f);
  1935. vec3_div(&left, &left, &viewport);
  1936. vec3_div(&right, &right, &viewport);
  1937. vec3_div(&top, &top, &viewport);
  1938. vec3_div(&bottom, &bottom, &viewport);
  1939. vec3_mulf(&left, &left, main->previewScale);
  1940. vec3_mulf(&right, &right, main->previewScale);
  1941. vec3_mulf(&top, &top, main->previewScale);
  1942. vec3_mulf(&bottom, &bottom, main->previewScale);
  1943. // Draw spacer lines and labels
  1944. vec3 start, end;
  1945. float pixelRatio = main->GetDevicePixelRatio();
  1946. for (int i = 0; i < 4; i++) {
  1947. if (!spacerLabel[i])
  1948. spacerLabel[i] = CreateLabel(pixelRatio, i);
  1949. }
  1950. vec3_set(&start, top.x, 0.0f, 1.0f);
  1951. vec3_set(&end, top.x, top.y, 1.0f);
  1952. RenderSpacingHelper(0, start, end, viewport, pixelRatio);
  1953. vec3_set(&start, bottom.x, 1.0f - bottom.y, 1.0f);
  1954. vec3_set(&end, bottom.x, 1.0f, 1.0f);
  1955. RenderSpacingHelper(1, start, end, viewport, pixelRatio);
  1956. vec3_set(&start, 0.0f, left.y, 1.0f);
  1957. vec3_set(&end, left.x, left.y, 1.0f);
  1958. RenderSpacingHelper(2, start, end, viewport, pixelRatio);
  1959. vec3_set(&start, 1.0f - right.x, right.y, 1.0f);
  1960. vec3_set(&end, 1.0f, right.y, 1.0f);
  1961. RenderSpacingHelper(3, start, end, viewport, pixelRatio);
  1962. }
  1963. void OBSBasicPreview::ClampScrollingOffsets()
  1964. {
  1965. obs_video_info ovi;
  1966. obs_get_video_info(&ovi);
  1967. QSize targetSize = GetPixelSize(this);
  1968. vec3 target, offset;
  1969. vec3_set(&target, (float)targetSize.width(), (float)targetSize.height(), 1.0f);
  1970. vec3_set(&offset, (float)ovi.base_width, (float)ovi.base_height, 1.0f);
  1971. vec3_mulf(&offset, &offset, scalingAmount);
  1972. vec3_sub(&offset, &offset, &target);
  1973. vec3_mulf(&offset, &offset, 0.5f);
  1974. vec3_maxf(&offset, &offset, 0.0f);
  1975. vec3_divf(&target, &target, 2.0f);
  1976. vec3_add(&offset, &offset, &target);
  1977. scrollingOffset.x = std::clamp(scrollingOffset.x, -offset.x, offset.x);
  1978. scrollingOffset.y = std::clamp(scrollingOffset.y, -offset.y, offset.y);
  1979. UpdateXScrollBar(offset.x);
  1980. UpdateYScrollBar(offset.y);
  1981. }
  1982. void OBSBasicPreview::XScrollBarMoved(int value)
  1983. {
  1984. updatingXScrollBar = true;
  1985. scrollingOffset.x = float(-value);
  1986. emit DisplayResized();
  1987. updatingXScrollBar = false;
  1988. }
  1989. void OBSBasicPreview::YScrollBarMoved(int value)
  1990. {
  1991. updatingYScrollBar = true;
  1992. scrollingOffset.y = float(-value);
  1993. emit DisplayResized();
  1994. updatingYScrollBar = false;
  1995. }
  1996. void OBSBasicPreview::UpdateXScrollBar(float cx)
  1997. {
  1998. if (updatingXScrollBar)
  1999. return;
  2000. OBSBasic *main = OBSBasic::Get();
  2001. if (!main->ui->previewXScrollBar->isVisible())
  2002. return;
  2003. main->ui->previewXScrollBar->setRange(int(-cx), int(cx));
  2004. QSize targetSize = GetPixelSize(this);
  2005. main->ui->previewXScrollBar->setPageStep(targetSize.width() / std::min(scalingAmount, 1.0f));
  2006. QSignalBlocker sig(main->ui->previewXScrollBar);
  2007. main->ui->previewXScrollBar->setValue(int(-scrollingOffset.x));
  2008. }
  2009. void OBSBasicPreview::UpdateYScrollBar(float cy)
  2010. {
  2011. if (updatingYScrollBar)
  2012. return;
  2013. OBSBasic *main = OBSBasic::Get();
  2014. if (!main->ui->previewYScrollBar->isVisible())
  2015. return;
  2016. main->ui->previewYScrollBar->setRange(int(-cy), int(cy));
  2017. QSize targetSize = GetPixelSize(this);
  2018. main->ui->previewYScrollBar->setPageStep(targetSize.height() / std::min(scalingAmount, 1.0f));
  2019. QSignalBlocker sig(main->ui->previewYScrollBar);
  2020. main->ui->previewYScrollBar->setValue(int(-scrollingOffset.y));
  2021. }