UI: Add scene editing

So, scene editing was interesting (and by interesting I mean
excruciating).  I almost implemented 'manipulator' visuals (ala 3dsmax
for example), and used 3 modes for controlling position/rotation/size,
but in a 2D editing, it felt clunky, so I defaulted back to simply
click-and-drag for movement, and then took a similar though slightly
different looking approach for handling scaling and reszing.

I also added a number of menu item helpers related to positioning,
scaling, rotating, flipping, and resetting the transform back to
default.

There is also a new 'transform' dialog (accessible via menu) which will
allow you to manually edit every single transform variable of a scene
item directly if desired.

If a scene item does not have bounds active, pulling on the sides of a
source will cause it to resize it via base scale rather than by the
bounding box system (if the source resizes that scale will apply).  If
bounds are active, it will modify the bounding box only instead.

How a source scales when a bounding box is active depends on the type of
bounds being used.  You can set it to scale to the inner bounds, the
outer bounds, scale to bounds width only, scale to bounds height only,
and a setting to stretch to bounds (which forces a source to always draw
at the bounding box size rather than be affected by its internal size).
You can also set it to be used as a 'maximum' size, so that the source
doesn't necessarily get scaled unless it extends beyond the bounds.

Like in OBS1, objects will snap to the edges unless the control key is
pressed.  However, this will now happen even if the object is rotated or
oriented in any strange way.  Snapping will also occur when stretching
or changing the bounding box size.
This commit is contained in:
jp9000 2014-06-15 00:54:48 -07:00
parent b23f8cc6e1
commit 452e0695f4
15 changed files with 2148 additions and 49 deletions

View file

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

View file

@ -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);
}

View file

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

View file

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

View file

@ -29,7 +29,7 @@
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="OBSQTDisplay" name="preview" native="true">
<widget class="OBSBasicPreview" name="preview" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -305,7 +305,7 @@
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Volume</string>
<string>Mixer</string>
</property>
</widget>
</item>
@ -463,7 +463,35 @@
</widget>
<addaction name="menuLogFiles"/>
</widget>
<widget class="QMenu" name="menuBasic_MainMenu_Edit">
<property name="title">
<string>Basic.MainMenu.Edit</string>
</property>
<widget class="QMenu" name="menuBasic_MainMenu_Edit_Transform">
<property name="title">
<string>Basic.MainMenu.Edit.Transform</string>
</property>
<addaction name="actionEditTransform"/>
<addaction name="actionResetTransform"/>
<addaction name="separator"/>
<addaction name="actionRotate90CW"/>
<addaction name="actionRotate90CCW"/>
<addaction name="actionRotate180"/>
<addaction name="separator"/>
<addaction name="actionFlipHorizontal"/>
<addaction name="actionFlipVertical"/>
<addaction name="separator"/>
<addaction name="actionFitToScreen"/>
<addaction name="actionStretchToScreen"/>
<addaction name="actionCenterToScreen"/>
</widget>
<addaction name="actionUndo"/>
<addaction name="actionRedo"/>
<addaction name="separator"/>
<addaction name="menuBasic_MainMenu_Edit_Transform"/>
</widget>
<addaction name="menu_File"/>
<addaction name="menuBasic_MainMenu_Edit"/>
<addaction name="menuBasic_MainMenu_Help"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
@ -610,12 +638,90 @@
<string>Basic.MainMenu.Help.Logs.UploadCurrentLog</string>
</property>
</action>
<action name="actionUndo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Basic.MainMenu.Edit.Undo</string>
</property>
</action>
<action name="actionRedo">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Basic.MainMenu.Edit.Redo</string>
</property>
</action>
<action name="actionEditTransform">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.EditTransform</string>
</property>
</action>
<action name="actionRotate90CW">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.Rotate90CW</string>
</property>
</action>
<action name="actionRotate90CCW">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.Rotate90CCW</string>
</property>
</action>
<action name="actionRotate180">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.Rotate180</string>
</property>
</action>
<action name="actionFitToScreen">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.FitToScreen</string>
</property>
<property name="shortcut">
<string>Ctrl+F</string>
</property>
</action>
<action name="actionStretchToScreen">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.StretchToScreen</string>
</property>
<property name="shortcut">
<string>Ctrl+S</string>
</property>
</action>
<action name="actionResetTransform">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.ResetTransform</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<action name="actionCenterToScreen">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.CenterToScreen</string>
</property>
<property name="shortcut">
<string>Ctrl+C</string>
</property>
</action>
<action name="actionFlipHorizontal">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.FlipHorizontal</string>
</property>
</action>
<action name="actionFlipVertical">
<property name="text">
<string>Basic.MainMenu.Edit.Transform.FlipVertical</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>
<class>OBSQTDisplay</class>
<class>OBSBasicPreview</class>
<extends>QWidget</extends>
<header>qt-display.hpp</header>
<header>window-basic-preview.hpp</header>
<container>1</container>
</customwidget>
</customwidgets>

View file

@ -0,0 +1,462 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>OBSBasicTransform</class>
<widget class="QDialog" name="OBSBasicTransform">
<property name="enabled">
<bool>false</bool>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>564</width>
<height>241</height>
</rect>
</property>
<property name="windowTitle">
<string>Basic.TransformWindow</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>170</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>Basic.TransformWindow.Position</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QWidget" name="widget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QDoubleSpinBox" name="positionX">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<double>-9001.000000000000000</double>
</property>
<property name="maximum">
<double>9001.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="positionY">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<double>-9001.000000000000000</double>
</property>
<property name="maximum">
<double>9001.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Basic.TransformWindow.Rotation</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="rotation">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<double>-360.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
<property name="singleStep">
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Basic.TransformWindow.Scale</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QWidget" name="widget_2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QDoubleSpinBox" name="scaleX">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<double>-9001.000000000000000</double>
</property>
<property name="maximum">
<double>9001.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="scaleY">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<double>-9001.000000000000000</double>
</property>
<property name="maximum">
<double>9001.000000000000000</double>
</property>
<property name="singleStep">
<double>0.010000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Basic.TransformWindow.Alignment</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="align">
<property name="currentText">
<string>Basic.TransformWindow.Alignment.TopLeft</string>
</property>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.TopLeft</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.TopCenter</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.TopRight</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.CenterLeft</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.Center</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.CenterRight</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.BottomLeft</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.BottomCenter</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.BottomRight</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Basic.TransformWindow.BoundsType</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="boundsType">
<item>
<property name="text">
<string>Basic.TransformWindow.BoundsType.None</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.BoundsType.Stretch</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.BoundsType.ScaleInner</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.BoundsType.ScaleOuter</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.BoundsType.ScaleToWidth</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.BoundsType.ScaleToHeight</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.BoundsType.MaxOnly</string>
</property>
</item>
</widget>
</item>
<item row="4" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Basic.TransformWindow.BoundsAlignment</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Basic.TransformWindow.Bounds</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QWidget" name="widget_3" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QDoubleSpinBox" name="boundsWidth">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>9001.000000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="boundsHeight">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>9001.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="6" column="1">
<widget class="QComboBox" name="boundsAlign">
<property name="enabled">
<bool>false</bool>
</property>
<property name="currentText">
<string>Basic.TransformWindow.Alignment.TopLeft</string>
</property>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.TopLeft</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.TopCenter</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.TopRight</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.CenterLeft</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.Center</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.CenterRight</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.BottomLeft</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.BottomCenter</string>
</property>
</item>
<item>
<property name="text">
<string>Basic.TransformWindow.Alignment.BottomRight</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -26,6 +26,7 @@
#include <util/dstr.h>
#include <util/util.hpp>
#include <util/platform.h>
#include <graphics/math-defs.h>
#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<char> 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<OBSBasic*>(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<OBSSceneItem*>(param);
obs_sceneitem_select(item, (selectedItem == item));
UNUSED_PARAMETER(scene);
return true;
};
if (!current)
return;
OBSSceneItem item = current->data(Qt::UserRole).value<OBSSceneItem>();
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<float*>(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<vec2*>(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<obs_bounds_type*>(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);
}

View file

@ -25,6 +25,7 @@
#include <memory>
#include "window-main.hpp"
#include "window-basic-properties.hpp"
#include "window-basic-transform.hpp"
#include <util/util.hpp>
@ -45,34 +46,41 @@ class QNetworkReply;
class OBSBasic : public OBSMainWindow {
Q_OBJECT
friend class OBSBasicPreview;
private:
std::unordered_map<obs_source_t, int> sourceSceneRefs;
std::vector<VolControl*> volumes;
QPointer<OBSBasicProperties> properties;
QPointer<OBSBasicTransform> 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();

View file

@ -0,0 +1,718 @@
#include <QGuiApplication>
#include <QMouseEvent>
#include <cmath>
#include <graphics/vec4.h>
#include <graphics/matrix4.h>
#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<OBSBasic*>(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<SceneFindData*>(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<OBSBasic*>(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<OBSBasic*>(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<SceneFindData*>(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<OBSBasic*>(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<HandleFindData*>(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<OBSBasic*>(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<OBSBasic*>(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<obs_sceneitem_t>(param);
obs_sceneitem_select(item, (selectedItem == item));
UNUSED_PARAMETER(scene);
return true;
}
void OBSBasicPreview::DoSelect(const vec2 &pos)
{
OBSBasic *main = reinterpret_cast<OBSBasic*>(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<SelectedItemBounds*>(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<OBSBasic*>(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<vec2*>(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<OBSBasic*>(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<OBSBasicPreview*>(param);
OBSBasic *main = reinterpret_cast<OBSBasic*>(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<OBSBasic*>(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);
}

View file

@ -0,0 +1,84 @@
#pragma once
#include <obs.hpp>
#include <graphics/vec2.h>
#include <graphics/matrix4.h>
#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);}
};

View file

@ -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 &))

View file

@ -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<OBSSceneItem*>(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<OBSBasicTransform*>(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<OBSBasicTransform*>(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<OBSBasicTransform*>(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<OBSBasicTransform*>(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<OBSBasicTransform*>(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;
}

View file

@ -0,0 +1,47 @@
#pragma once
#include <obs.hpp>
#include <memory>
#include "ui_OBSBasicTransform.h"
class OBSBasic;
class OBSBasicTransform : public QDialog {
Q_OBJECT
private:
std::unique_ptr<Ui::OBSBasicTransform> 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);
};

View file

@ -22,6 +22,43 @@
<ClInclude Include="..\..\..\libobs\obs-ui.h" />
<ClInclude Include="..\..\..\obs\display-helpers.hpp" />
<ClInclude Include="..\..\..\obs\platform.hpp" />
<CustomBuild Include="..\..\..\obs\window-basic-preview.hpp">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing window-basic-preview.hpp...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing window-basic-preview.hpp...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Moc%27ing window-basic-preview.hpp...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing window-basic-preview.hpp...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(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"</Command>
</CustomBuild>
<CustomBuild Include="..\..\..\obs\window-basic-transform.hpp">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Moc%27ing window-basic-transform.hpp...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing window-basic-transform.hpp...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Moc%27ing window-basic-transform.hpp...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(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"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing window-basic-transform.hpp...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(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"</Command>
</CustomBuild>
<ClInclude Include="GeneratedFiles\ui_OBSBasicTransform.h" />
<ClInclude Include="GeneratedFiles\ui_OBSLogReply.h" />
<CustomBuild Include="..\..\..\obs\window-basic-source-select.hpp">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
@ -235,9 +272,11 @@
<ClCompile Include="..\..\..\obs\qt-wrappers.cpp" />
<ClCompile Include="..\..\..\obs\volume-control.cpp" />
<ClCompile Include="..\..\..\obs\window-basic-main.cpp" />
<ClCompile Include="..\..\..\obs\window-basic-preview.cpp" />
<ClCompile Include="..\..\..\obs\window-basic-properties.cpp" />
<ClCompile Include="..\..\..\obs\window-basic-settings.cpp" />
<ClCompile Include="..\..\..\obs\window-basic-source-select.cpp" />
<ClCompile Include="..\..\..\obs\window-basic-transform.cpp" />
<ClCompile Include="..\..\..\obs\window-log-reply.cpp" />
<ClCompile Include="..\..\..\obs\window-namedialog.cpp" />
<ClCompile Include="GeneratedFiles\Debug\moc_obs-app.cpp">
@ -260,6 +299,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_window-basic-preview.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_window-basic-properties.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
@ -272,6 +315,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_window-basic-transform.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_window-log-reply.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
@ -314,6 +361,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_window-basic-preview.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_window-basic-properties.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@ -326,6 +377,10 @@
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_window-basic-transform.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_window-log-reply.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
@ -553,6 +608,26 @@
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<CustomBuild Include="..\..\..\obs\forms\OBSBasicTransform.ui">
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Uic%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">.\GeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">"$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Uic%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\GeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Uic%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">.\GeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">"$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Uic%27ing %(Identity)...</Message>
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\GeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
</CustomBuild>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{B12702AD-ABFB-343A-A199-8E24837244A3}</ProjectGuid>
<Keyword>Qt4VSv1.0</Keyword>

View file

@ -89,6 +89,15 @@
<CustomBuild Include="..\..\..\obs\forms\OBSLogReply.ui">
<Filter>Form Files</Filter>
</CustomBuild>
<CustomBuild Include="..\..\..\obs\window-basic-preview.hpp">
<Filter>Header Files</Filter>
</CustomBuild>
<CustomBuild Include="..\..\..\obs\forms\OBSBasicTransform.ui">
<Filter>Form Files</Filter>
</CustomBuild>
<CustomBuild Include="..\..\..\obs\window-basic-transform.hpp">
<Filter>Header Files</Filter>
</CustomBuild>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\..\obs\platform.hpp">
@ -121,6 +130,9 @@
<ClInclude Include="GeneratedFiles\ui_OBSLogReply.h">
<Filter>Generated Files</Filter>
</ClInclude>
<ClInclude Include="GeneratedFiles\ui_OBSBasicTransform.h">
<Filter>Generated Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\..\..\obs\obs-app.cpp">
@ -225,6 +237,24 @@
<ClCompile Include="..\..\..\obs\window-log-reply.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_window-basic-preview.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_window-basic-preview.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="..\..\..\obs\window-basic-preview.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Debug\moc_window-basic-transform.cpp">
<Filter>Generated Files\Debug</Filter>
</ClCompile>
<ClCompile Include="GeneratedFiles\Release\moc_window-basic-transform.cpp">
<Filter>Generated Files\Release</Filter>
</ClCompile>
<ClCompile Include="..\..\..\obs\window-basic-transform.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Image Include="..\..\..\obs\forms\images\add.ico">