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 @@
+
+
@@ -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
-
+
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
+