diff --git a/build/data/obs-studio/locale/en.txt b/build/data/obs-studio/locale/en.txt index 25dd32626..cf981b9b4 100644 --- a/build/data/obs-studio/locale/en.txt +++ b/build/data/obs-studio/locale/en.txt @@ -15,7 +15,7 @@ MoveUp="Move Up" MoveDown="Move Down" Settings="Settings" Exit="Exit" -Volume="Volume" +Mixer="Mixer" Browse="Browse" Mono="Mono" Stereo="Stereo" @@ -66,6 +66,34 @@ Basic.Main.DefaultSceneName.Text="Scene %1" Basic.SourceSelect.CreateNew="Create new" Basic.SourceSelect.AddExisting="Add Existing" +# transform window +Basic.TransformWindow="Scene Item Transform" +Basic.TransformWindow.Position="Position" +Basic.TransformWindow.Rotation="Rotation" +Basic.TransformWindow.Scale="Scale" +Basic.TransformWindow.Alignment="Positional Alignment" +Basic.TransformWindow.BoundsType="Bounding Box Type" +Basic.TransformWindow.BoundsAlignment="Alignment in Bounding Box" +Basic.TransformWindow.Bounds="Bounding Box Size" + +Basic.TransformWindow.Alignment.TopLeft="Top Left" +Basic.TransformWindow.Alignment.TopCenter="Top Center" +Basic.TransformWindow.Alignment.TopRight="Top Right" +Basic.TransformWindow.Alignment.CenterLeft="Center Left" +Basic.TransformWindow.Alignment.Center="Center" +Basic.TransformWindow.Alignment.CenterRight="Center Right" +Basic.TransformWindow.Alignment.BottomLeft="Bottom Left" +Basic.TransformWindow.Alignment.BottomCenter="Bottom Center" +Basic.TransformWindow.Alignment.BottomRight="Bottom Right" + +Basic.TransformWindow.BoundsType.None="No bounds" +Basic.TransformWindow.BoundsType.MaxOnly="Maximum size only" +Basic.TransformWindow.BoundsType.ScaleInner="Scale to inner bounds" +Basic.TransformWindow.BoundsType.ScaleOuter="Scale to outer bounds" +Basic.TransformWindow.BoundsType.ScaleToWidth="Scale to width of bounds" +Basic.TransformWindow.BoundsType.ScaleToHeight="Scale to height of bounds" +Basic.TransformWindow.BoundsType.Stretch="Stretch to bounds" + # no scene warning Basic.Main.AddSourceHelp.Title="Cannot Add Source" Basic.Main.AddSourceHelp.Text="You need to have at least 1 scene to add a source." @@ -86,6 +114,24 @@ Basic.MainMenu.File.Import="&Import" Basic.MainMenu.File.Settings="&Settings" Basic.MainMenu.File.Exit="E&xit" +# basic mode edit menu +Basic.MainMenu.Edit="&Edit" +Basic.MainMenu.Edit.Undo="&Undo" +Basic.MainMenu.Edit.Redo="&Redo" +Basic.MainMenu.Edit.UndoAction="&Undo $1" +Basic.MainMenu.Edit.RedoAction="&Redo $1" +Basic.MainMenu.Edit.Transform="&Transform" +Basic.MainMenu.Edit.Transform.EditTransform="&Edit Transform..." +Basic.MainMenu.Edit.Transform.ResetTransform="&Reset Transform" +Basic.MainMenu.Edit.Transform.Rotate90CW="Rotate 90 degrees CW" +Basic.MainMenu.Edit.Transform.Rotate90CCW="Rotate 90 degrees CCW" +Basic.MainMenu.Edit.Transform.Rotate180="Rotate 180 degrees" +Basic.MainMenu.Edit.Transform.FlipHorizontal="Flip &Horizontal" +Basic.MainMenu.Edit.Transform.FlipVertical="Flip &Vertical" +Basic.MainMenu.Edit.Transform.FitToScreen="&Fit to screen" +Basic.MainMenu.Edit.Transform.StretchToScreen="&Stretch to screen" +Basic.MainMenu.Edit.Transform.CenterToScreen="&Center to screen" + # basic mode help menu Basic.MainMenu.Help="&Help" Basic.MainMenu.Help.Logs="&Log Files" diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index cd76a106a..f2feaa01a 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -175,14 +175,19 @@ static void calculate_bounds_data(struct obs_scene_item *item, struct vec2 *origin, struct vec2 *scale, uint32_t *cx, uint32_t *cy) { - float width = (float)(*cx) * fabsf(scale->x); - float height = (float)(*cy) * fabsf(scale->y); - float item_aspect = width / height; - float bounds_aspect = item->bounds.x / item->bounds.y; - float width_diff, height_diff; + float width = (float)(*cx) * fabsf(scale->x); + float height = (float)(*cy) * fabsf(scale->y); + float item_aspect = width / height; + float bounds_aspect = item->bounds.x / item->bounds.y; + uint32_t bounds_type = item->bounds_type; + float width_diff, height_diff; - if (item->bounds_type == OBS_BOUNDS_SCALE_INNER || - item->bounds_type == OBS_BOUNDS_SCALE_OUTER) { + if (item->bounds_type == OBS_BOUNDS_MAX_ONLY) + if (width > item->bounds.x || height > item->bounds.y) + bounds_type = OBS_BOUNDS_SCALE_INNER; + + if (bounds_type == OBS_BOUNDS_SCALE_INNER || + bounds_type == OBS_BOUNDS_SCALE_OUTER) { bool use_width = (bounds_aspect < item_aspect); float mul; @@ -195,13 +200,13 @@ static void calculate_bounds_data(struct obs_scene_item *item, vec2_mulf(scale, scale, mul); - } else if (item->bounds_type == OBS_BOUNDS_SCALE_TO_WIDTH) { + } else if (bounds_type == OBS_BOUNDS_SCALE_TO_WIDTH) { vec2_mulf(scale, scale, item->bounds.x / width); - } else if (item->bounds_type == OBS_BOUNDS_SCALE_TO_HEIGHT) { + } else if (bounds_type == OBS_BOUNDS_SCALE_TO_HEIGHT) { vec2_mulf(scale, scale, item->bounds.y / height); - } else if (item->bounds_type == OBS_BOUNDS_STRETCH) { + } else if (bounds_type == OBS_BOUNDS_STRETCH) { scale->x = item->bounds.x / (float)(*cx); scale->y = item->bounds.y / (float)(*cy); } diff --git a/libobs/obs.h b/libobs/obs.h index c6efffa79..6829b45ec 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -95,12 +95,12 @@ enum allow_direct_render { */ enum obs_bounds_type { OBS_BOUNDS_NONE, /**< no bounds */ - OBS_BOUNDS_ALIGN_ONLY, /**< no scaling to bounds, align only */ + OBS_BOUNDS_STRETCH, /**< stretch (ignores base scale) */ OBS_BOUNDS_SCALE_INNER, /**< scales to inner rectangle */ OBS_BOUNDS_SCALE_OUTER, /**< scales to outer rectangle */ OBS_BOUNDS_SCALE_TO_WIDTH, /**< scales to the width */ OBS_BOUNDS_SCALE_TO_HEIGHT, /**< scales to the height */ - OBS_BOUNDS_STRETCH /**< stretch (ignores base scale) */ + OBS_BOUNDS_MAX_ONLY, /**< no scaling, maximum size only */ }; struct obs_sceneitem_info { diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index 27b2422b2..6231e904b 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -57,6 +57,8 @@ set(obs_SOURCES window-basic-settings.cpp window-basic-properties.cpp window-basic-source-select.cpp + window-basic-transform.cpp + window-basic-preview.cpp window-namedialog.cpp window-log-reply.cpp properties-view.cpp @@ -71,6 +73,8 @@ set(obs_HEADERS window-basic-settings.hpp window-basic-properties.hpp window-basic-source-select.hpp + window-basic-transform.hpp + window-basic-preview.hpp window-namedialog.hpp window-log-reply.hpp properties-view.hpp @@ -83,6 +87,7 @@ set(obs_UI forms/NameDialog.ui forms/OBSLogReply.ui forms/OBSBasic.ui + forms/OBSBasicTransform.ui forms/OBSBasicSettings.ui forms/OBSBasicSourceSelect.ui forms/OBSBasicProperties.ui) diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui index 1637c29a4..6568bb9c9 100644 --- a/obs/forms/OBSBasic.ui +++ b/obs/forms/OBSBasic.ui @@ -29,7 +29,7 @@ - + 0 @@ -305,7 +305,7 @@ - Volume + Mixer @@ -463,7 +463,35 @@ + + + Basic.MainMenu.Edit + + + + Basic.MainMenu.Edit.Transform + + + + + + + + + + + + + + + + + + + + + @@ -610,12 +638,90 @@ Basic.MainMenu.Help.Logs.UploadCurrentLog + + + false + + + Basic.MainMenu.Edit.Undo + + + + + false + + + Basic.MainMenu.Edit.Redo + + + + + Basic.MainMenu.Edit.Transform.EditTransform + + + + + Basic.MainMenu.Edit.Transform.Rotate90CW + + + + + Basic.MainMenu.Edit.Transform.Rotate90CCW + + + + + Basic.MainMenu.Edit.Transform.Rotate180 + + + + + Basic.MainMenu.Edit.Transform.FitToScreen + + + Ctrl+F + + + + + Basic.MainMenu.Edit.Transform.StretchToScreen + + + Ctrl+S + + + + + Basic.MainMenu.Edit.Transform.ResetTransform + + + Ctrl+R + + + + + Basic.MainMenu.Edit.Transform.CenterToScreen + + + Ctrl+C + + + + + Basic.MainMenu.Edit.Transform.FlipHorizontal + + + + + Basic.MainMenu.Edit.Transform.FlipVertical + + - OBSQTDisplay + OBSBasicPreview QWidget -
qt-display.hpp
+
window-basic-preview.hpp
1
diff --git a/obs/forms/OBSBasicTransform.ui b/obs/forms/OBSBasicTransform.ui new file mode 100644 index 000000000..a242bf97a --- /dev/null +++ b/obs/forms/OBSBasicTransform.ui @@ -0,0 +1,462 @@ + + + OBSBasicTransform + + + false + + + + 0 + 0 + 564 + 241 + + + + Basic.TransformWindow + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 0 + + + + + 170 + 0 + + + + Basic.TransformWindow.Position + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 0 + + + + -9001.000000000000000 + + + 9001.000000000000000 + + + + + + + + 100 + 0 + + + + -9001.000000000000000 + + + 9001.000000000000000 + + + + + + + + + + Basic.TransformWindow.Rotation + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + -360.000000000000000 + + + 360.000000000000000 + + + 0.100000000000000 + + + + + + + Basic.TransformWindow.Scale + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 0 + + + + -9001.000000000000000 + + + 9001.000000000000000 + + + 0.010000000000000 + + + + + + + + 100 + 0 + + + + -9001.000000000000000 + + + 9001.000000000000000 + + + 0.010000000000000 + + + + + + + + + + Basic.TransformWindow.Alignment + + + + + + + Basic.TransformWindow.Alignment.TopLeft + + + + Basic.TransformWindow.Alignment.TopLeft + + + + + Basic.TransformWindow.Alignment.TopCenter + + + + + Basic.TransformWindow.Alignment.TopRight + + + + + Basic.TransformWindow.Alignment.CenterLeft + + + + + Basic.TransformWindow.Alignment.Center + + + + + Basic.TransformWindow.Alignment.CenterRight + + + + + Basic.TransformWindow.Alignment.BottomLeft + + + + + Basic.TransformWindow.Alignment.BottomCenter + + + + + Basic.TransformWindow.Alignment.BottomRight + + + + + + + + Basic.TransformWindow.BoundsType + + + + + + + + Basic.TransformWindow.BoundsType.None + + + + + Basic.TransformWindow.BoundsType.Stretch + + + + + Basic.TransformWindow.BoundsType.ScaleInner + + + + + Basic.TransformWindow.BoundsType.ScaleOuter + + + + + Basic.TransformWindow.BoundsType.ScaleToWidth + + + + + Basic.TransformWindow.BoundsType.ScaleToHeight + + + + + Basic.TransformWindow.BoundsType.MaxOnly + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + Basic.TransformWindow.BoundsAlignment + + + + + + + Basic.TransformWindow.Bounds + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 100 + 0 + + + + 1.000000000000000 + + + 9001.000000000000000 + + + + + + + false + + + + 100 + 0 + + + + 1.000000000000000 + + + 9001.000000000000000 + + + + + + + + + + false + + + Basic.TransformWindow.Alignment.TopLeft + + + + Basic.TransformWindow.Alignment.TopLeft + + + + + Basic.TransformWindow.Alignment.TopCenter + + + + + Basic.TransformWindow.Alignment.TopRight + + + + + Basic.TransformWindow.Alignment.CenterLeft + + + + + Basic.TransformWindow.Alignment.Center + + + + + Basic.TransformWindow.Alignment.CenterRight + + + + + Basic.TransformWindow.Alignment.BottomLeft + + + + + Basic.TransformWindow.Alignment.BottomCenter + + + + + Basic.TransformWindow.Alignment.BottomRight + + + + + + + + + + + diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 93ce65ab2..2996b6ecf 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include "obs-app.hpp" #include "platform.hpp" @@ -55,15 +56,6 @@ Q_DECLARE_METATYPE(order_movement); OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow (parent), - properties (nullptr), - fileOutput (nullptr), - streamOutput (nullptr), - service (nullptr), - aac (nullptr), - x264 (nullptr), - sceneChanging (false), - resizeTimer (0), - activeRefs (0), ui (new Ui::OBSBasic) { ui->setupUi(this); @@ -444,6 +436,28 @@ void OBSBasic::InitOBSCallbacks() OBSBasic::SourceDeactivated, this); } +void OBSBasic::InitPrimitives() +{ + gs_entercontext(obs_graphics()); + + gs_renderstart(true); + gs_vertex2f(0.0f, 0.0f); + gs_vertex2f(0.0f, 1.0f); + gs_vertex2f(1.0f, 1.0f); + gs_vertex2f(1.0f, 0.0f); + gs_vertex2f(0.0f, 0.0f); + box = gs_rendersave(); + + gs_renderstart(true); + for (int i = 0; i <= 360; i += (360/20)) { + float pos = RAD(float(i)); + gs_vertex2f(cosf(pos), sinf(pos)); + } + circle = gs_rendersave(); + + gs_leavecontext(); +} + void OBSBasic::OBSInit() { BPtr savePath(os_get_config_path("obs-studio/basic/scenes.json")); @@ -491,6 +505,8 @@ void OBSBasic::OBSInit() if (!InitService()) throw "Failed to initialize service"; + InitPrimitives(); + Load(savePath); ResetAudioDevices(); } @@ -501,14 +517,26 @@ OBSBasic::~OBSBasic() SaveService(); Save(savePath); + /* XXX: any obs data must be released before calling obs_shutdown. + * currently, we can't automate this with C++ RAII because of the + * delicate nature of obs_shutdown needing to be freed before the UI + * can be freed, and we have no control over the destruction order of + * the Qt UI stuff, so we have to manually clear any references to + * libobs. */ if (properties) delete properties; + if (transformWindow) + delete transformWindow; - /* free the lists before shutting down to remove the scene/item - * references */ ClearVolumeControls(); ui->sources->clear(); ui->scenes->clear(); + + gs_entercontext(obs_graphics()); + vertexbuffer_destroy(box); + vertexbuffer_destroy(circle); + gs_leavecontext(); + obs_shutdown(); } @@ -792,21 +820,26 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy) { OBSBasic *window = static_cast(data); obs_video_info ovi; - int newCX, newCY; obs_get_video_info(&ovi); - newCX = int(window->previewScale * float(ovi.base_width)); - newCY = int(window->previewScale * float(ovi.base_height)); + window->previewCX = int(window->previewScale * float(ovi.base_width)); + window->previewCY = int(window->previewScale * float(ovi.base_height)); gs_viewport_push(); gs_projection_push(); gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), -100.0f, 100.0f); - gs_setviewport(window->previewX, window->previewY, newCX, newCY); + gs_setviewport(window->previewX, window->previewY, + window->previewCX, window->previewCY); obs_render_main_view(); + gs_ortho(0.0f, float(window->previewCX), 0.0f, float(window->previewCY), + -100.0f, 100.0f); + + window->ui->preview->DrawSceneEditing(); + gs_projection_pop(); gs_viewport_pop(); @@ -1155,8 +1188,23 @@ void OBSBasic::on_actionSceneDown_triggered() void OBSBasic::on_sources_currentItemChanged(QListWidgetItem *current, QListWidgetItem *prev) { - /* TODO */ - UNUSED_PARAMETER(current); + auto select_one = [] (obs_scene_t scene, obs_sceneitem_t item, + void *param) + { + obs_sceneitem_t selectedItem = + *reinterpret_cast(param); + obs_sceneitem_select(item, (selectedItem == item)); + + UNUSED_PARAMETER(scene); + return true; + }; + + if (!current) + return; + + OBSSceneItem item = current->data(Qt::UserRole).value(); + obs_scene_enum_items(GetCurrentScene(), select_one, &item); + UNUSED_PARAMETER(prev); } @@ -1569,3 +1617,192 @@ config_t OBSBasic::Config() const { return basicConfig; } + +void OBSBasic::on_actionEditTransform_triggered() +{ + delete transformWindow; + transformWindow = new OBSBasicTransform(this); + transformWindow->show(); +} + +void OBSBasic::on_actionResetTransform_triggered() +{ + auto func = [] (obs_scene_t scene, obs_sceneitem_t item, void *param) + { + if (!obs_sceneitem_selected(item)) + return true; + + obs_sceneitem_info info; + vec2_set(&info.pos, 0.0f, 0.0f); + vec2_set(&info.scale, 1.0f, 1.0f); + info.rot = 0.0f; + info.alignment = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; + info.bounds_type = OBS_BOUNDS_NONE; + info.bounds_alignment = OBS_ALIGN_CENTER; + vec2_set(&info.bounds, 0.0f, 0.0f); + obs_sceneitem_set_info(item, &info); + + UNUSED_PARAMETER(scene); + UNUSED_PARAMETER(param); + return true; + }; + + obs_scene_enum_items(GetCurrentScene(), func, nullptr); +} + +static vec3 GetItemTL(obs_sceneitem_t item) +{ + matrix4 boxTransform; + obs_sceneitem_get_box_transform(item, &boxTransform); + + vec3 tl; + vec3_set(&tl, M_INFINITE, M_INFINITE, 0.0f); + + auto GetMinPos = [&] (vec3 &val, float x, float y) + { + vec3 pos; + vec3_set(&pos, x, y, 0.0f); + vec3_transform(&pos, &pos, &boxTransform); + vec3_min(&val, &val, &pos); + }; + + GetMinPos(tl, 0.0f, 0.0f); + GetMinPos(tl, 1.0f, 0.0f); + GetMinPos(tl, 0.0f, 1.0f); + GetMinPos(tl, 1.0f, 1.0f); + return tl; +} + +static void SetItemTL(obs_sceneitem_t item, const vec3 &tl) +{ + vec3 newTL; + vec2 pos; + + obs_sceneitem_getpos(item, &pos); + newTL = GetItemTL(item); + pos.x += tl.x - newTL.x; + pos.y += tl.y - newTL.y; + obs_sceneitem_setpos(item, &pos); +} + +static bool RotateSelectedSources(obs_scene_t scene, obs_sceneitem_t item, + void *param) +{ + if (!obs_sceneitem_selected(item)) + return true; + + float rot = *reinterpret_cast(param); + + vec3 tl = GetItemTL(item); + + rot += obs_sceneitem_getrot(item); + if (rot >= 360.0f) rot -= 360.0f; + else if (rot <= -360.0f) rot += 360.0f; + obs_sceneitem_setrot(item, rot); + + SetItemTL(item, tl); + + UNUSED_PARAMETER(scene); + UNUSED_PARAMETER(param); + return true; +}; + +void OBSBasic::on_actionRotate90CW_triggered() +{ + float f90CW = 90.0f; + obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CW); +} + +void OBSBasic::on_actionRotate90CCW_triggered() +{ + float f90CCW = -90.0f; + obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f90CCW); +} + +void OBSBasic::on_actionRotate180_triggered() +{ + float f180 = 180.0f; + obs_scene_enum_items(GetCurrentScene(), RotateSelectedSources, &f180); +} + +static bool MultiplySelectedItemScale(obs_scene_t scene, obs_sceneitem_t item, + void *param) +{ + vec2 &mul = *reinterpret_cast(param); + + if (!obs_sceneitem_selected(item)) + return true; + + vec3 tl = GetItemTL(item); + + vec2 scale; + obs_sceneitem_getscale(item, &scale); + vec2_mul(&scale, &scale, &mul); + obs_sceneitem_setscale(item, &scale); + + SetItemTL(item, tl); + return true; +} + +void OBSBasic::on_actionFlipHorizontal_triggered() +{ + vec2 scale = {-1.0f, 1.0f}; + obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, + &scale); +} + +void OBSBasic::on_actionFlipVertical_triggered() +{ + vec2 scale = {1.0f, -1.0f}; + obs_scene_enum_items(GetCurrentScene(), MultiplySelectedItemScale, + &scale); +} + +static bool CenterAlignSelectedItems(obs_scene_t scene, obs_sceneitem_t item, + void *param) +{ + obs_bounds_type boundsType = *reinterpret_cast(param); + + if (!obs_sceneitem_selected(item)) + return true; + + obs_video_info ovi; + obs_get_video_info(&ovi); + + obs_sceneitem_info itemInfo; + vec2_set(&itemInfo.pos, 0.0f, 0.0f); + vec2_set(&itemInfo.scale, 1.0f, 1.0f); + itemInfo.alignment = OBS_ALIGN_LEFT | OBS_ALIGN_TOP; + itemInfo.rot = 0.0f; + + vec2_set(&itemInfo.bounds, + float(ovi.base_width), float(ovi.base_height)); + itemInfo.bounds_type = boundsType; + itemInfo.bounds_alignment = OBS_ALIGN_CENTER; + + obs_sceneitem_set_info(item, &itemInfo); + + UNUSED_PARAMETER(scene); + return true; +} + +void OBSBasic::on_actionFitToScreen_triggered() +{ + obs_bounds_type boundsType = OBS_BOUNDS_SCALE_INNER; + obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, + &boundsType); +} + +void OBSBasic::on_actionStretchToScreen_triggered() +{ + obs_bounds_type boundsType = OBS_BOUNDS_STRETCH; + obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, + &boundsType); +} + +void OBSBasic::on_actionCenterToScreen_triggered() +{ + obs_bounds_type boundsType = OBS_BOUNDS_MAX_ONLY; + obs_scene_enum_items(GetCurrentScene(), CenterAlignSelectedItems, + &boundsType); +} diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index 91c29445a..5bacd6feb 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -25,6 +25,7 @@ #include #include "window-main.hpp" #include "window-basic-properties.hpp" +#include "window-basic-transform.hpp" #include @@ -45,34 +46,41 @@ class QNetworkReply; class OBSBasic : public OBSMainWindow { Q_OBJECT + friend class OBSBasicPreview; + private: std::unordered_map sourceSceneRefs; std::vector volumes; QPointer properties; + QPointer transformWindow; QNetworkAccessManager networkManager; QBuffer logUploadPostData; - QNetworkReply *logUploadReply; + QNetworkReply *logUploadReply = nullptr; QByteArray logUploadReturnData; - obs_output_t fileOutput; - obs_output_t streamOutput; - obs_service_t service; - obs_encoder_t aac; - obs_encoder_t x264; + obs_output_t fileOutput = nullptr; + obs_output_t streamOutput = nullptr; + obs_service_t service = nullptr; + obs_encoder_t aac = nullptr; + obs_encoder_t x264 = nullptr; - bool sceneChanging; + vertbuffer_t box = nullptr; + vertbuffer_t circle = nullptr; - int previewX, previewY; - float previewScale; - int resizeTimer; + bool sceneChanging = false; + + int previewX = 0, previewY = 0; + int previewCX = 0, previewCY = 0; + float previewScale = 0.0f; + int resizeTimer = 0; ConfigFile basicConfig; - int activeRefs; + int activeRefs = 0; void SetupEncoders(); @@ -97,7 +105,8 @@ private: void InitOBSCallbacks(); - OBSScene GetCurrentScene(); + void InitPrimitives(); + OBSSceneItem GetCurrentSceneItem(); void GetFPSCommon(uint32_t &num, uint32_t &den) const; @@ -153,6 +162,8 @@ private: void AddSourcePopupMenu(const QPoint &pos); public: + OBSScene GetCurrentScene(); + obs_service_t GetService(); void SetService(obs_service_t service); @@ -167,6 +178,14 @@ public: void SaveProject(); void LoadProject(); + inline void GetDisplayRect(int &x, int &y, int &cx, int &cy) + { + x = previewX; + y = previewY; + cx = previewCX; + cy = previewCY; + } + protected: virtual void closeEvent(QCloseEvent *event) override; virtual void changeEvent(QEvent *event) override; @@ -178,6 +197,20 @@ private slots: void on_action_Open_triggered(); void on_action_Save_triggered(); void on_action_Settings_triggered(); + void on_actionUploadCurrentLog_triggered(); + void on_actionUploadLastLog_triggered(); + + void on_actionEditTransform_triggered(); + void on_actionResetTransform_triggered(); + void on_actionRotate90CW_triggered(); + void on_actionRotate90CCW_triggered(); + void on_actionRotate180_triggered(); + void on_actionFlipHorizontal_triggered(); + void on_actionFlipVertical_triggered(); + void on_actionFitToScreen_triggered(); + void on_actionStretchToScreen_triggered(); + void on_actionCenterToScreen_triggered(); + void on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *prev); void on_scenes_customContextMenuRequested(const QPoint &pos); @@ -194,8 +227,7 @@ private slots: void on_actionSourceProperties_triggered(); void on_actionSourceUp_triggered(); void on_actionSourceDown_triggered(); - void on_actionUploadCurrentLog_triggered(); - void on_actionUploadLastLog_triggered(); + void on_streamButton_clicked(); void on_recordButton_clicked(); void on_settingsButton_clicked(); diff --git a/obs/window-basic-preview.cpp b/obs/window-basic-preview.cpp new file mode 100644 index 000000000..6d49a8c86 --- /dev/null +++ b/obs/window-basic-preview.cpp @@ -0,0 +1,718 @@ +#include +#include + +#include +#include +#include +#include "window-basic-preview.hpp" +#include "window-basic-main.hpp" +#include "obs-app.hpp" + +#define HANDLE_RADIUS 4.0f +#define HANDLE_SEL_RADIUS (HANDLE_RADIUS * 1.5f) +#define CLAMP_DISTANCE 10.0f + +/* TODO: make C++ math classes and clean up code here later */ + +OBSBasicPreview::OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags) + : OBSQTDisplay(parent, flags) +{ + setMouseTracking(true); +} + +vec2 OBSBasicPreview::GetMouseEventPos(QMouseEvent *event) +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + vec2 pos = { + (float(event->x()) - main->previewX) / main->previewScale, + (float(event->y()) - main->previewY) / main->previewScale + }; + + return pos; +} + +struct SceneFindData { + const vec2 &pos; + OBSSceneItem item; + bool selectBelow; + + inline SceneFindData(const vec2 &pos_, bool selectBelow_) + : pos (pos_), + selectBelow (selectBelow_) + {} +}; + +static bool FindItemAtPos(obs_scene_t scene, obs_sceneitem_t item, void *param) +{ + SceneFindData *data = reinterpret_cast(param); + matrix4 transform; + vec3 transformedPos; + vec3 pos3 = {data->pos.x, data->pos.y, 0.0f}; + + obs_sceneitem_get_box_transform(item, &transform); + + matrix4_inv(&transform, &transform); + vec3_transform(&transformedPos, &pos3, &transform); + + if (transformedPos.x >= 0.0f && transformedPos.x <= 1.0f && + transformedPos.y >= 0.0f && transformedPos.y <= 1.0f) { + if (data->selectBelow && obs_sceneitem_selected(item)) { + if (data->item) + return false; + else + data->selectBelow = false; + } + + data->item = item; + } + + UNUSED_PARAMETER(scene); + return true; +} + +static vec3 GetTransformedPos(float x, float y, const matrix4 &mat) +{ + vec3 result; + vec3_set(&result, x, y, 0.0f); + vec3_transform(&result, &result, &mat); + return result; +} + +static vec3 GetTransformedPosScaled(float x, float y, const matrix4 &mat, + float scale) +{ + vec3 result; + vec3_set(&result, x, y, 0.0f); + vec3_transform(&result, &result, &mat); + vec3_mulf(&result, &result, scale); + return result; +} + +static inline vec2 GetOBSScreenSize() +{ + obs_video_info ovi; + vec2 size = {0.0f, 0.0f}; + + if (obs_get_video_info(&ovi)) { + size.x = float(ovi.base_width); + size.y = float(ovi.base_height); + } + + return size; +} + +vec3 OBSBasicPreview::GetScreenSnapOffset(const vec3 &tl, const vec3 &br) +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + vec2 screenSize = GetOBSScreenSize(); + vec3 clampOffset; + + vec3_zero(&clampOffset); + + const float clampDist = CLAMP_DISTANCE / main->previewScale; + + if (fabsf(tl.x) < clampDist) + clampOffset.x = -tl.x; + if (fabsf(clampOffset.x) < EPSILON && + fabsf(screenSize.x - br.x) < clampDist) + clampOffset.x = screenSize.x - br.x; + + if (fabsf(tl.y) < clampDist) + clampOffset.y = -tl.y; + if (fabsf(clampOffset.y) < EPSILON && + fabsf(screenSize.y - br.y) < clampDist) + clampOffset.y = screenSize.y - br.y; + + return clampOffset; +} + +OBSSceneItem OBSBasicPreview::GetItemAtPos(const vec2 &pos, bool selectBelow) +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + + OBSScene scene = main->GetCurrentScene(); + if (!scene) + return OBSSceneItem(); + + SceneFindData data(pos, selectBelow); + obs_scene_enum_items(scene, FindItemAtPos, &data); + return data.item; +} + +static bool CheckItemSelected(obs_scene_t scene, obs_sceneitem_t item, + void *param) +{ + SceneFindData *data = reinterpret_cast(param); + matrix4 transform; + vec3 transformedPos; + vec3 pos3 = {data->pos.x, data->pos.y, 0.0f}; + + obs_sceneitem_get_box_transform(item, &transform); + + matrix4_inv(&transform, &transform); + vec3_transform(&transformedPos, &pos3, &transform); + + if (transformedPos.x >= 0.0f && transformedPos.x <= 1.0f && + transformedPos.y >= 0.0f && transformedPos.y <= 1.0f) { + if (obs_sceneitem_selected(item)) { + data->item = item; + return false; + } + } + + UNUSED_PARAMETER(scene); + return true; +} + +bool OBSBasicPreview::SelectedAtPos(const vec2 &pos) +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + + OBSScene scene = main->GetCurrentScene(); + if (!scene) + return false; + + SceneFindData data(pos, false); + obs_scene_enum_items(scene, CheckItemSelected, &data); + return !!data.item; +} + +struct HandleFindData { + const vec2 &pos; + const float scale; + + OBSSceneItem item; + ItemHandle handle = ItemHandle::None; + + inline HandleFindData(const vec2 &pos_, float scale_) + : pos (pos_), + scale (scale_) + {} +}; + +static bool FindHandleAtPos(obs_scene_t scene, obs_sceneitem_t item, + void *param) +{ + if (!obs_sceneitem_selected(item)) + return true; + + HandleFindData *data = reinterpret_cast(param); + matrix4 transform; + vec3 pos3 = {data->pos.x, data->pos.y, 0.0f}; + float closestHandle = HANDLE_SEL_RADIUS; + + obs_sceneitem_get_box_transform(item, &transform); + + auto TestHandle = [&] (float x, float y, ItemHandle handle) + { + vec3 handlePos = GetTransformedPosScaled(x, y, transform, + data->scale); + + float dist = vec3_dist(&handlePos, &pos3); + if (dist < HANDLE_SEL_RADIUS) { + if (dist < closestHandle) { + closestHandle = dist; + data->handle = handle; + data->item = item; + } + } + }; + + TestHandle(0.0f, 0.0f, ItemHandle::TopLeft); + TestHandle(0.5f, 0.0f, ItemHandle::TopCenter); + TestHandle(1.0f, 0.0f, ItemHandle::TopRight); + TestHandle(0.0f, 0.5f, ItemHandle::CenterLeft); + TestHandle(1.0f, 0.5f, ItemHandle::CenterRight); + TestHandle(0.0f, 1.0f, ItemHandle::BottomLeft); + TestHandle(0.5f, 1.0f, ItemHandle::BottomCenter); + TestHandle(1.0f, 1.0f, ItemHandle::BottomRight); + + UNUSED_PARAMETER(scene); + return true; +} + +static vec2 GetItemSize(obs_sceneitem_t item) +{ + obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(item); + vec2 size; + + if (boundsType != OBS_BOUNDS_NONE) { + obs_sceneitem_get_bounds(item, &size); + } else { + obs_source_t source = obs_sceneitem_getsource(item); + vec2 scale; + + obs_sceneitem_getscale(item, &scale); + size.x = float(obs_source_getwidth(source)) * scale.x; + size.y = float(obs_source_getheight(source)) * scale.y; + } + + return size; +} + +void OBSBasicPreview::GetStretchHandleData(const vec2 &pos) +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + + OBSScene scene = main->GetCurrentScene(); + if (!scene) + return; + + HandleFindData data(pos, main->previewScale); + obs_scene_enum_items(scene, FindHandleAtPos, &data); + + stretchItem = std::move(data.item); + stretchHandle = data.handle; + + if (stretchHandle != ItemHandle::None) { + matrix4 boxTransform; + vec3 itemUL; + float itemRot; + + stretchItemSize = GetItemSize(stretchItem); + + obs_sceneitem_get_box_transform(stretchItem, &boxTransform); + itemRot = obs_sceneitem_getrot(stretchItem); + vec3_from_vec4(&itemUL, &boxTransform.t); + + /* build the item space conversion matrices */ + matrix4_identity(&itemToScreen); + matrix4_rotate_aa4f(&itemToScreen, &itemToScreen, + 0.0f, 0.0f, 1.0f, RAD(itemRot)); + matrix4_translate3f(&itemToScreen, &itemToScreen, + itemUL.x, itemUL.y, 0.0f); + + matrix4_identity(&screenToItem); + matrix4_translate3f(&screenToItem, &screenToItem, + -itemUL.x, -itemUL.y, 0.0f); + matrix4_rotate_aa4f(&screenToItem, &screenToItem, + 0.0f, 0.0f, 1.0f, RAD(-itemRot)); + } +} + +void OBSBasicPreview::mousePressEvent(QMouseEvent *event) +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + float x = float(event->x()) - main->previewX; + float y = float(event->y()) - main->previewY; + + if (event->button() != Qt::LeftButton || + x < 0.0f || y < 0.0f || x > main->previewCX || y > main->previewCY) + return; + + mouseDown = true; + + vec2_set(&startPos, x, y); + GetStretchHandleData(startPos); + + vec2_divf(&startPos, &startPos, main->previewScale); + startPos.x = std::round(startPos.x); + startPos.y = std::round(startPos.y); + + mouseOverItems = SelectedAtPos(startPos); + vec2_zero(&lastMoveOffset); +} + +static bool select_one(obs_scene_t scene, obs_sceneitem_t item, void *param) +{ + obs_sceneitem_t selectedItem = reinterpret_cast(param); + obs_sceneitem_select(item, (selectedItem == item)); + + UNUSED_PARAMETER(scene); + return true; +} + +void OBSBasicPreview::DoSelect(const vec2 &pos) +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + + OBSScene scene = main->GetCurrentScene(); + OBSSceneItem item = GetItemAtPos(pos, true); + + obs_scene_enum_items(scene, select_one, (obs_sceneitem_t)item); +} + +void OBSBasicPreview::DoCtrlSelect(const vec2 &pos) +{ + OBSSceneItem item = GetItemAtPos(pos, false); + if (!item) + return; + + bool selected = obs_sceneitem_selected(item); + obs_sceneitem_select(item, !selected); +} + +void OBSBasicPreview::ProcessClick(const vec2 &pos) +{ + Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers(); + + if (modifiers & Qt::ControlModifier) + DoCtrlSelect(pos); + else + DoSelect(pos); +} + +void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event) +{ + if (mouseDown) { + vec2 pos = GetMouseEventPos(event); + + if (!mouseMoved) + ProcessClick(pos); + + stretchItem = nullptr; + mouseDown = false; + mouseMoved = false; + } +} + +struct SelectedItemBounds { + bool first = true; + vec3 tl, br; +}; + +static bool AddItemBounds(obs_scene_t scene, obs_sceneitem_t item, + void *param) +{ + SelectedItemBounds *data = reinterpret_cast(param); + + if (!obs_sceneitem_selected(item)) + return true; + + matrix4 boxTransform; + obs_sceneitem_get_box_transform(item, &boxTransform); + + vec3 t[4] = { + GetTransformedPos(0.0f, 0.0f, boxTransform), + GetTransformedPos(1.0f, 0.0f, boxTransform), + GetTransformedPos(0.0f, 1.0f, boxTransform), + GetTransformedPos(1.0f, 1.0f, boxTransform) + }; + + for (const vec3 &v : t) { + if (data->first) { + vec3_copy(&data->tl, &v); + vec3_copy(&data->br, &v); + data->first = false; + } else { + vec3_min(&data->tl, &data->tl, &v); + vec3_max(&data->br, &data->br, &v); + } + } + + UNUSED_PARAMETER(scene); + return true; +} + +void OBSBasicPreview::SnapItemMovement(vec2 &offset) +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + OBSScene scene = main->GetCurrentScene(); + + SelectedItemBounds data; + obs_scene_enum_items(scene, AddItemBounds, &data); + + data.tl.x += offset.x; + data.tl.y += offset.y; + data.br.x += offset.x; + data.br.y += offset.y; + + vec3 snapOffset = GetScreenSnapOffset(data.tl, data.br); + offset.x += snapOffset.x; + offset.y += snapOffset.y; +} + +static bool move_items(obs_scene_t scene, obs_sceneitem_t item, void *param) +{ + vec2 *offset = reinterpret_cast(param); + + if (obs_sceneitem_selected(item)) { + vec2 pos; + obs_sceneitem_getpos(item, &pos); + vec2_add(&pos, &pos, offset); + obs_sceneitem_setpos(item, &pos); + } + + UNUSED_PARAMETER(scene); + return true; +} + +void OBSBasicPreview::MoveItems(const vec2 &pos) +{ + Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers(); + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + OBSScene scene = main->GetCurrentScene(); + + vec2 offset, moveOffset; + vec2_sub(&offset, &pos, &startPos); + vec2_sub(&moveOffset, &offset, &lastMoveOffset); + + if (!(modifiers & Qt::ControlModifier)) + SnapItemMovement(moveOffset); + + vec2_add(&lastMoveOffset, &lastMoveOffset, &moveOffset); + + obs_scene_enum_items(scene, move_items, &moveOffset); +} + +vec3 OBSBasicPreview::CalculateStretchPos(const vec3 &tl, const vec3 &br) +{ + uint32_t alignment = obs_sceneitem_getalignment(stretchItem); + vec3 pos; + + vec3_zero(&pos); + + if (alignment & OBS_ALIGN_LEFT) + pos.x = tl.x; + else if (alignment & OBS_ALIGN_RIGHT) + pos.x = br.x; + else + pos.x = (br.x - tl.x) * 0.5f + tl.x; + + if (alignment & OBS_ALIGN_TOP) + pos.y = tl.y; + else if (alignment & OBS_ALIGN_BOTTOM) + pos.y = br.y; + else + pos.y = (br.y - tl.y) * 0.5f + tl.y; + + return pos; +} + +void OBSBasicPreview::ClampAspect(vec3 &tl, vec3 &br, vec2 &size, + const vec2 &baseSize) +{ + float baseAspect = baseSize.x / baseSize.y; + float aspect = size.x / size.y; + uint32_t stretchFlags = (uint32_t)stretchHandle; + + if (stretchHandle == ItemHandle::TopLeft || + stretchHandle == ItemHandle::TopRight || + stretchHandle == ItemHandle::BottomLeft || + stretchHandle == ItemHandle::BottomRight) { + if (aspect < baseAspect) + size.x = size.y * baseAspect; + else + size.y = size.x / baseAspect; + + } else if (stretchHandle == ItemHandle::TopCenter || + stretchHandle == ItemHandle::BottomCenter) { + size.x = size.y * baseAspect; + + } else if (stretchHandle == ItemHandle::CenterLeft || + stretchHandle == ItemHandle::CenterRight) { + size.y = size.x / baseAspect; + } + + size.x = std::round(size.x); + size.y = std::round(size.y); + + if (stretchFlags & ITEM_LEFT) + tl.x = br.x - size.x; + else if (stretchFlags & ITEM_RIGHT) + br.x = tl.x + size.x; + + if (stretchFlags & ITEM_TOP) + tl.y = br.y - size.y; + else if (stretchFlags & ITEM_BOTTOM) + br.y = tl.y + size.y; +} + +void OBSBasicPreview::SnapStretchingToScreen(vec3 &tl, vec3 &br) +{ + uint32_t stretchFlags = (uint32_t)stretchHandle; + vec3 newTL = GetTransformedPos(tl.x, tl.y, itemToScreen); + vec3 newTR = GetTransformedPos(br.x, tl.y, itemToScreen); + vec3 newBL = GetTransformedPos(tl.x, br.y, itemToScreen); + vec3 newBR = GetTransformedPos(br.x, br.y, itemToScreen); + vec3 boundingTL; + vec3 boundingBR; + + vec3_copy(&boundingTL, &newTL); + vec3_min(&boundingTL, &boundingTL, &newTR); + vec3_min(&boundingTL, &boundingTL, &newBL); + vec3_min(&boundingTL, &boundingTL, &newBR); + + vec3_copy(&boundingBR, &newTL); + vec3_max(&boundingBR, &boundingBR, &newTR); + vec3_max(&boundingBR, &boundingBR, &newBL); + vec3_max(&boundingBR, &boundingBR, &newBR); + + vec3 offset = GetScreenSnapOffset(boundingTL, boundingBR); + vec3_add(&offset, &offset, &newTL); + vec3_transform(&offset, &offset, &screenToItem); + vec3_sub(&offset, &offset, &tl); + + if (stretchFlags & ITEM_LEFT) + tl.x += offset.x; + else if (stretchFlags & ITEM_RIGHT) + br.x += offset.x; + + if (stretchFlags & ITEM_TOP) + tl.y += offset.y; + else if (stretchFlags & ITEM_BOTTOM) + br.y += offset.y; +} + +void OBSBasicPreview::StretchItem(const vec2 &pos) +{ + Qt::KeyboardModifiers modifiers = QGuiApplication::keyboardModifiers(); + obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(stretchItem); + uint32_t stretchFlags = (uint32_t)stretchHandle; + bool shiftDown = (modifiers & Qt::ShiftModifier); + vec3 tl, br, pos3; + + vec3_zero(&tl); + vec3_set(&br, stretchItemSize.x, stretchItemSize.y, 0.0f); + + vec3_set(&pos3, pos.x, pos.y, 0.0f); + vec3_transform(&pos3, &pos3, &screenToItem); + + if (stretchFlags & ITEM_LEFT) + tl.x = pos3.x; + else if (stretchFlags & ITEM_RIGHT) + br.x = pos3.x; + + if (stretchFlags & ITEM_TOP) + tl.y = pos3.y; + else if (stretchFlags & ITEM_BOTTOM) + br.y = pos3.y; + + if (!(modifiers & Qt::ControlModifier)) + SnapStretchingToScreen(tl, br); + + obs_source_t source = obs_sceneitem_getsource(stretchItem); + vec2 baseSize = { + float(obs_source_getwidth(source)), + float(obs_source_getheight(source)) + }; + + vec2 size = {br.x - tl.x, br.y - tl.y}; + + if (boundsType != OBS_BOUNDS_NONE) { + if (boundsType == OBS_BOUNDS_STRETCH && !shiftDown) + ClampAspect(tl, br, size, baseSize); + + if (tl.x > br.x) std::swap(tl.x, br.x); + if (tl.y > br.y) std::swap(tl.y, br.y); + + vec2_abs(&size, &size); + + obs_sceneitem_set_bounds(stretchItem, &size); + } else { + if (!shiftDown) + ClampAspect(tl, br, size, baseSize); + + vec2_div(&size, &size, &baseSize); + obs_sceneitem_setscale(stretchItem, &size); + } + + pos3 = CalculateStretchPos(tl, br); + vec3_transform(&pos3, &pos3, &itemToScreen); + vec2 newPos = {pos3.x, pos3.y}; + + newPos.x = std::round(newPos.x); + newPos.y = std::round(newPos.y); + + obs_sceneitem_setpos(stretchItem, &newPos); +} + +void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event) +{ + if (mouseDown) { + vec2 pos = GetMouseEventPos(event); + + if (!mouseMoved && !mouseOverItems && + stretchHandle == ItemHandle::None) { + ProcessClick(startPos); + mouseOverItems = SelectedAtPos(startPos); + } + + pos.x = std::round(pos.x); + pos.y = std::round(pos.y); + + if (stretchHandle != ItemHandle::None) + StretchItem(pos); + else if (mouseOverItems) + MoveItems(pos); + + mouseMoved = true; + } +} + +static void DrawCircleAtPos(float x, float y, matrix4 &matrix, + float previewScale) +{ + struct vec3 pos; + vec3_set(&pos, x, y, 0.0f); + vec3_transform(&pos, &pos, &matrix); + vec3_mulf(&pos, &pos, previewScale); + + gs_matrix_push(); + gs_matrix_translate(&pos); + gs_draw(GS_LINESTRIP, 0, 0); + gs_matrix_pop(); +} + +bool OBSBasicPreview::DrawSelectedItem(obs_scene_t scene, obs_sceneitem_t item, + void *param) +{ + if (!obs_sceneitem_selected(item)) + return true; + + OBSBasicPreview *preview = reinterpret_cast(param); + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + + gs_load_vertexbuffer(main->circle); + + matrix4 boxTransform; + obs_sceneitem_get_box_transform(item, &boxTransform); + + gs_matrix_push(); + gs_matrix_scale3f(HANDLE_RADIUS, HANDLE_RADIUS, 1.0f); + DrawCircleAtPos(0.0f, 0.0f, boxTransform, main->previewScale); + DrawCircleAtPos(0.0f, 1.0f, boxTransform, main->previewScale); + DrawCircleAtPos(1.0f, 0.0f, boxTransform, main->previewScale); + DrawCircleAtPos(1.0f, 1.0f, boxTransform, main->previewScale); + DrawCircleAtPos(0.5f, 0.0f, boxTransform, main->previewScale); + DrawCircleAtPos(0.0f, 0.5f, boxTransform, main->previewScale); + DrawCircleAtPos(0.5f, 1.0f, boxTransform, main->previewScale); + DrawCircleAtPos(1.0f, 0.5f, boxTransform, main->previewScale); + gs_matrix_pop(); + + gs_load_vertexbuffer(main->box); + + gs_matrix_push(); + gs_matrix_set(&boxTransform); + gs_matrix_scale3f(main->previewScale, main->previewScale, 1.0f); + gs_draw(GS_LINESTRIP, 0, 0); + + gs_matrix_pop(); + + UNUSED_PARAMETER(scene); + return true; +} + +void OBSBasicPreview::DrawSceneEditing() +{ + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + + effect_t solid = obs_get_solid_effect(); + technique_t tech = effect_gettechnique(solid, "Solid"); + + vec4 color; + vec4_set(&color, 1.0f, 0.0f, 0.0f, 1.0f); + effect_setvec4(solid, effect_getparambyname(solid, "color"), &color); + + technique_begin(tech); + technique_beginpass(tech, 0); + + OBSScene scene = main->GetCurrentScene(); + if (scene) + obs_scene_enum_items(scene, DrawSelectedItem, this); + + gs_load_vertexbuffer(nullptr); + + technique_endpass(tech); + technique_end(tech); +} diff --git a/obs/window-basic-preview.hpp b/obs/window-basic-preview.hpp new file mode 100644 index 000000000..602e3c10b --- /dev/null +++ b/obs/window-basic-preview.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include +#include "qt-display.hpp" +#include "obs-app.hpp" + +class OBSBasic; +class QMouseEvent; + +#define ITEM_LEFT (1<<0) +#define ITEM_RIGHT (1<<1) +#define ITEM_TOP (1<<2) +#define ITEM_BOTTOM (1<<3) + +enum class ItemHandle : uint32_t { + None = 0, + TopLeft = ITEM_TOP | ITEM_LEFT, + TopCenter = ITEM_TOP, + TopRight = ITEM_TOP | ITEM_RIGHT, + CenterLeft = ITEM_LEFT, + CenterRight = ITEM_RIGHT, + BottomLeft = ITEM_BOTTOM | ITEM_LEFT, + BottomCenter = ITEM_BOTTOM, + BottomRight = ITEM_BOTTOM | ITEM_RIGHT +}; + +class OBSBasicPreview : public OBSQTDisplay { + Q_OBJECT + +private: + OBSSceneItem stretchItem; + ItemHandle stretchHandle = ItemHandle::None; + vec2 stretchItemSize; + matrix4 screenToItem; + matrix4 itemToScreen; + + vec2 startPos; + vec2 lastMoveOffset; + bool mouseDown = false; + bool mouseMoved = false; + bool mouseOverItems = false; + + static vec2 GetMouseEventPos(QMouseEvent *event); + static bool DrawSelectedItem(obs_scene_t scene, obs_sceneitem_t item, + void *param); + + static OBSSceneItem GetItemAtPos(const vec2 &pos, bool selectBelow); + static bool SelectedAtPos(const vec2 &pos); + + static void DoSelect(const vec2 &pos); + static void DoCtrlSelect(const vec2 &pos); + + static vec3 GetScreenSnapOffset(const vec3 &tl, const vec3 &br); + + void GetStretchHandleData(const vec2 &pos); + + void SnapStretchingToScreen(vec3 &tl, vec3 &br); + void ClampAspect(vec3 &tl, vec3 &br, vec2 &size, const vec2 &baseSize); + vec3 CalculateStretchPos(const vec3 &tl, const vec3 &br); + void StretchItem(const vec2 &pos); + + static void SnapItemMovement(vec2 &offset); + void MoveItems(const vec2 &pos); + + void ProcessClick(const vec2 &pos); + +public: + OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags = 0); + + virtual void mousePressEvent(QMouseEvent *event) override; + virtual void mouseReleaseEvent(QMouseEvent *event) override; + virtual void mouseMoveEvent(QMouseEvent *event) override; + + void DrawSceneEditing(); + + /* use libobs allocator for alignment because the matrices itemToScreen + * and screenToItem may contain SSE data, which will cause SSE + * instructions to crash if the data is not aligned to at least a 16 + * byte boundry. */ + static inline void* operator new(size_t size) {return bmalloc(size);} + static inline void operator delete(void* ptr) {bfree(ptr);} +}; diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index 25174b58e..9735272f3 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -92,7 +92,6 @@ void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal, QObject::connect(widget, signal, this, slot); } -#define COMBO_CHANGED SIGNAL(currentIndexChanged(int)) #define COMBO_CHANGED SIGNAL(currentIndexChanged(int)) #define EDIT_CHANGED SIGNAL(textChanged(const QString &)) #define CBEDIT_CHANGED SIGNAL(editTextChanged(const QString &)) diff --git a/obs/window-basic-transform.cpp b/obs/window-basic-transform.cpp new file mode 100644 index 000000000..013f66d30 --- /dev/null +++ b/obs/window-basic-transform.cpp @@ -0,0 +1,253 @@ +#include "window-basic-transform.hpp" +#include "window-basic-main.hpp" + +Q_DECLARE_METATYPE(OBSSceneItem); + +static OBSSceneItem FindASelectedItem(OBSScene scene) +{ + auto func = [] (obs_scene_t scene, obs_sceneitem_t item, void *param) + { + OBSSceneItem &dst = *reinterpret_cast(param); + + if (obs_sceneitem_selected(item)) { + dst = item; + return false; + } + + return true; + }; + + OBSSceneItem item; + obs_scene_enum_items(scene, func, &item); + return item; +} + +void OBSBasicTransform::HookWidget(QWidget *widget, const char *signal, + const char *slot) +{ + QObject::connect(widget, signal, this, slot); +} + +#define COMBO_CHANGED SIGNAL(currentIndexChanged(int)) +#define DSCROLL_CHANGED SIGNAL(valueChanged(double)) + +OBSBasicTransform::OBSBasicTransform(OBSBasic *parent) + : QDialog (parent), + ui (new Ui::OBSBasicTransform), + main (parent) +{ + setAttribute(Qt::WA_DeleteOnClose); + + ui->setupUi(this); + + HookWidget(ui->positionX, DSCROLL_CHANGED, SLOT(OnControlChanged())); + HookWidget(ui->positionY, DSCROLL_CHANGED, SLOT(OnControlChanged())); + HookWidget(ui->rotation, DSCROLL_CHANGED, SLOT(OnControlChanged())); + HookWidget(ui->scaleX, DSCROLL_CHANGED, SLOT(OnControlChanged())); + HookWidget(ui->scaleY, DSCROLL_CHANGED, SLOT(OnControlChanged())); + HookWidget(ui->align, COMBO_CHANGED, SLOT(OnControlChanged())); + HookWidget(ui->boundsType, COMBO_CHANGED, SLOT(OnBoundsType(int))); + HookWidget(ui->boundsAlign, COMBO_CHANGED, SLOT(OnControlChanged())); + HookWidget(ui->boundsWidth, DSCROLL_CHANGED, SLOT(OnControlChanged())); + HookWidget(ui->boundsHeight, DSCROLL_CHANGED, SLOT(OnControlChanged())); + + OBSScene curScene = main->GetCurrentScene(); + SetScene(curScene); + SetItem(FindASelectedItem(curScene)); + + channelChangedSignal.Connect(obs_signalhandler(), "channel_change", + OBSChannelChanged, this); +} + +void OBSBasicTransform::SetScene(OBSScene scene) +{ + transformSignal.Disconnect(); + selectSignal.Disconnect(); + deselectSignal.Disconnect(); + removeSignal.Disconnect(); + + if (scene) { + OBSSource source = obs_scene_getsource(scene); + signal_handler_t signal = obs_source_signalhandler(source); + + transformSignal.Connect(signal, "item_transform", + OBSSceneItemTransform, this); + removeSignal.Connect(signal, "item_remove", + OBSSceneItemRemoved, this); + selectSignal.Connect(signal, "item_select", + OBSSceneItemSelect, this); + deselectSignal.Connect(signal, "item_deselect", + OBSSceneItemDeselect, this); + } +} + +void OBSBasicTransform::SetItem(OBSSceneItem newItem) +{ + QMetaObject::invokeMethod(this, "SetItemQt", + Q_ARG(OBSSceneItem, OBSSceneItem(newItem))); +} + +void OBSBasicTransform::SetItemQt(OBSSceneItem newItem) +{ + item = newItem; + if (item) + RefreshControls(); + + setEnabled(!!item); +} + +void OBSBasicTransform::OBSChannelChanged(void *param, calldata_t data) +{ + OBSBasicTransform *window = reinterpret_cast(param); + uint32_t channel = (uint32_t)calldata_int(data, "channel"); + OBSSource source = (obs_source_t)calldata_ptr(data, "source"); + + if (channel == 0) { + OBSScene scene = obs_scene_fromsource(source); + window->SetScene(scene); + + if (!scene) + window->SetItem(nullptr); + else + window->SetItem(FindASelectedItem(scene)); + } +} + +void OBSBasicTransform::OBSSceneItemTransform(void *param, calldata_t data) +{ + OBSBasicTransform *window = reinterpret_cast(param); + OBSSceneItem item = (obs_sceneitem_t)calldata_ptr(data, "item"); + + if (item == window->item && !window->ignoreTransformSignal) + QMetaObject::invokeMethod(window, "RefreshControls"); +} + +void OBSBasicTransform::OBSSceneItemRemoved(void *param, calldata_t data) +{ + OBSBasicTransform *window = reinterpret_cast(param); + OBSScene scene = (obs_scene_t)calldata_ptr(data, "scene"); + OBSSceneItem item = (obs_sceneitem_t)calldata_ptr(data, "item"); + + if (item == window->item) + window->SetItem(FindASelectedItem(scene)); +} + +void OBSBasicTransform::OBSSceneItemSelect(void *param, calldata_t data) +{ + OBSBasicTransform *window = reinterpret_cast(param); + OBSSceneItem item = (obs_sceneitem_t)calldata_ptr(data, "item"); + + if (item != window->item) + window->SetItem(item); +} + +void OBSBasicTransform::OBSSceneItemDeselect(void *param, calldata_t data) +{ + OBSBasicTransform *window = reinterpret_cast(param); + OBSScene scene = (obs_scene_t)calldata_ptr(data, "scene"); + OBSSceneItem item = (obs_sceneitem_t)calldata_ptr(data, "item"); + + if (item == window->item) + window->SetItem(FindASelectedItem(scene)); +} + +static const uint32_t listToAlign[] = { + OBS_ALIGN_TOP | OBS_ALIGN_LEFT, + OBS_ALIGN_TOP, + OBS_ALIGN_TOP | OBS_ALIGN_RIGHT, + OBS_ALIGN_LEFT, + OBS_ALIGN_CENTER, + OBS_ALIGN_RIGHT, + OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT, + OBS_ALIGN_BOTTOM, + OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT +}; + +static int AlignToList(uint32_t align) +{ + int index = 0; + for (uint32_t curAlign : listToAlign) { + if (curAlign == align) + return index; + + index++; + } + + return 0; +} + +void OBSBasicTransform::RefreshControls() +{ + if (!item) + return; + + obs_sceneitem_info osi; + obs_sceneitem_get_info(item, &osi); + + int alignIndex = AlignToList(osi.alignment); + int boundsAlignIndex = AlignToList(osi.bounds_alignment); + + ignoreItemChange = true; + ui->positionX->setValue(osi.pos.x); + ui->positionY->setValue(osi.pos.y); + ui->rotation->setValue(osi.rot); + ui->scaleX->setValue(osi.scale.x); + ui->scaleY->setValue(osi.scale.y); + ui->align->setCurrentIndex(alignIndex); + + ui->boundsType->setCurrentIndex(int(osi.bounds_type)); + ui->boundsAlign->setCurrentIndex(boundsAlignIndex); + ui->boundsWidth->setValue(osi.bounds.x); + ui->boundsHeight->setValue(osi.bounds.y); + ignoreItemChange = false; +} + +void OBSBasicTransform::OnBoundsType(int index) +{ + if (index == -1) + return; + + obs_bounds_type type = (obs_bounds_type)index; + bool enable = (type != OBS_BOUNDS_NONE); + + ui->boundsAlign->setEnabled(enable); + ui->boundsWidth->setEnabled(enable); + ui->boundsHeight->setEnabled(enable); + + if (!ignoreItemChange) { + obs_bounds_type lastType = obs_sceneitem_get_bounds_type(item); + if (lastType == OBS_BOUNDS_NONE) { + OBSSource source = obs_sceneitem_getsource(item); + int width = (int)obs_source_getwidth(source); + int height = (int)obs_source_getheight(source); + + ui->boundsWidth->setValue(width); + ui->boundsHeight->setValue(height); + } + } + + OnControlChanged(); +} + +void OBSBasicTransform::OnControlChanged() +{ + if (ignoreItemChange) + return; + + obs_sceneitem_info osi; + osi.pos.x = float(ui->positionX->value()); + osi.pos.y = float(ui->positionY->value()); + osi.rot = float(ui->rotation->value()); + osi.scale.x = float(ui->scaleX->value()); + osi.scale.y = float(ui->scaleY->value()); + osi.alignment = listToAlign[ui->align->currentIndex()]; + + osi.bounds_type = (obs_bounds_type)ui->boundsType->currentIndex(); + osi.bounds_alignment = listToAlign[ui->boundsAlign->currentIndex()]; + osi.bounds.x = float(ui->boundsWidth->value()); + osi.bounds.y = float(ui->boundsHeight->value()); + + ignoreTransformSignal = true; + obs_sceneitem_set_info(item, &osi); + ignoreTransformSignal = false; +} diff --git a/obs/window-basic-transform.hpp b/obs/window-basic-transform.hpp new file mode 100644 index 000000000..6730d0fc8 --- /dev/null +++ b/obs/window-basic-transform.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include "ui_OBSBasicTransform.h" + +class OBSBasic; + +class OBSBasicTransform : public QDialog { + Q_OBJECT + +private: + std::unique_ptr ui; + + OBSBasic *main; + OBSSceneItem item; + OBSSignal channelChangedSignal; + OBSSignal transformSignal; + OBSSignal removeSignal; + OBSSignal selectSignal; + OBSSignal deselectSignal; + + bool ignoreTransformSignal = false; + bool ignoreItemChange = false; + + void HookWidget(QWidget *widget, const char *signal, const char *slot); + + void SetScene(OBSScene scene); + void SetItem(OBSSceneItem newItem); + + static void OBSChannelChanged(void *param, calldata_t data); + + static void OBSSceneItemTransform(void *param, calldata_t data); + static void OBSSceneItemRemoved(void *param, calldata_t data); + static void OBSSceneItemSelect(void *param, calldata_t data); + static void OBSSceneItemDeselect(void *param, calldata_t data); + +private slots: + void RefreshControls(); + void SetItemQt(OBSSceneItem newItem); + void OnBoundsType(int index); + void OnControlChanged(); + +public: + OBSBasicTransform(OBSBasic *parent); +}; diff --git a/vs/2013/obs-studio/obs-studio.vcxproj b/vs/2013/obs-studio/obs-studio.vcxproj index 36b6727f3..1ce9b4919 100644 --- a/vs/2013/obs-studio/obs-studio.vcxproj +++ b/vs/2013/obs-studio/obs-studio.vcxproj @@ -22,6 +22,43 @@ + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing window-basic-preview.hpp... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing window-basic-preview.hpp... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing window-basic-preview.hpp... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing window-basic-preview.hpp... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork" + + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing window-basic-transform.hpp... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing window-basic-transform.hpp... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing window-basic-transform.hpp... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing window-basic-transform.hpp... + .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB -DQT_NETWORK_LIB "-I.\..\..\..\libobs" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" "-I.\..\..\..\obs" "-I$(QTDIR)\include\QtNetwork" + + $(QTDIR)\bin\moc.exe;%(FullPath) @@ -235,9 +272,11 @@ + + @@ -260,6 +299,10 @@ true true + + true + true + true true @@ -272,6 +315,10 @@ true true + + true + true + true true @@ -314,6 +361,10 @@ true true + + true + true + true true @@ -326,6 +377,10 @@ true true + + true + true + true true @@ -553,6 +608,26 @@ "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + + + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + $(QTDIR)\bin\uic.exe;%(AdditionalInputs) + Uic%27ing %(Identity)... + .\GeneratedFiles\ui_%(Filename).h;%(Outputs) + "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" + + {B12702AD-ABFB-343A-A199-8E24837244A3} Qt4VSv1.0 diff --git a/vs/2013/obs-studio/obs-studio.vcxproj.filters b/vs/2013/obs-studio/obs-studio.vcxproj.filters index f7b2d32fd..d0e155c8f 100644 --- a/vs/2013/obs-studio/obs-studio.vcxproj.filters +++ b/vs/2013/obs-studio/obs-studio.vcxproj.filters @@ -89,6 +89,15 @@ Form Files + + Header Files + + + Form Files + + + Header Files + @@ -121,6 +130,9 @@ Generated Files + + Generated Files + @@ -225,6 +237,24 @@ Source Files + + Generated Files\Debug + + + Generated Files\Release + + + Source Files + + + Generated Files\Debug + + + Generated Files\Release + + + Source Files +