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-config.h b/libobs/obs-config.h index 4e110bbff..2411cf1d9 100644 --- a/libobs/obs-config.h +++ b/libobs/obs-config.h @@ -34,14 +34,14 @@ * * Reset to zero each major version */ -#define LIBOBS_API_MINOR_VER 2 +#define LIBOBS_API_MINOR_VER 3 /* * Increment if backward-compatible bug fix * * Reset to zero each major or minor version */ -#define LIBOBS_API_PATCH_VER 4 +#define LIBOBS_API_PATCH_VER 0 #define LIBOBS_API_VER ((LIBOBS_API_MAJOR_VER << 24) | \ (LIBOBS_API_MINOR_VER << 16) | \ diff --git a/libobs/obs-defs.h b/libobs/obs-defs.h index cf570b925..f2c9717e7 100644 --- a/libobs/obs-defs.h +++ b/libobs/obs-defs.h @@ -20,6 +20,12 @@ /** Maximum number of source channels for output and per display */ #define MAX_CHANNELS 64 +#define OBS_ALIGN_CENTER (0) +#define OBS_ALIGN_LEFT (1<<0) +#define OBS_ALIGN_RIGHT (1<<1) +#define OBS_ALIGN_TOP (1<<2) +#define OBS_ALIGN_BOTTOM (1<<3) + #define MODULE_SUCCESS 0 #define MODULE_ERROR -1 #define MODULE_FILE_NOT_FOUND -2 diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index d663ac768..f2feaa01a 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -26,6 +26,9 @@ static const char *obs_scene_signals[] = { "void item_move_down(ptr scene, ptr item)", "void item_move_top(ptr scene, ptr item)", "void item_move_bottom(ptr scene, ptr item)", + "void item_select(ptr scene, ptr item)", + "void item_deselect(ptr scene, ptr item)", + "void item_transform(ptr scene, ptr item)", NULL }; @@ -155,6 +158,143 @@ static inline void attach_sceneitem(struct obs_scene *parent, } } +static void add_alignment(struct vec2 *v, uint32_t align, int cx, int cy) +{ + if (align & OBS_ALIGN_RIGHT) + v->x += (float)cx; + else if ((align & OBS_ALIGN_LEFT) == 0) + v->x += (float)(cx / 2); + + if (align & OBS_ALIGN_BOTTOM) + v->y += (float)cy; + else if ((align & OBS_ALIGN_TOP) == 0) + v->y += (float)(cy / 2); +} + +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; + uint32_t bounds_type = item->bounds_type; + float width_diff, height_diff; + + 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; + + if (item->bounds_type == OBS_BOUNDS_SCALE_OUTER) + use_width = !use_width; + + mul = use_width ? + item->bounds.x / width : + item->bounds.y / height; + + vec2_mulf(scale, scale, mul); + + } else if (bounds_type == OBS_BOUNDS_SCALE_TO_WIDTH) { + vec2_mulf(scale, scale, item->bounds.x / width); + + } else if (bounds_type == OBS_BOUNDS_SCALE_TO_HEIGHT) { + vec2_mulf(scale, scale, item->bounds.y / height); + + } else if (bounds_type == OBS_BOUNDS_STRETCH) { + scale->x = item->bounds.x / (float)(*cx); + scale->y = item->bounds.y / (float)(*cy); + } + + width = (float)(*cx) * scale->x; + height = (float)(*cy) * scale->y; + width_diff = item->bounds.x - width; + height_diff = item->bounds.y - height; + *cx = (uint32_t)item->bounds.x; + *cy = (uint32_t)item->bounds.y; + + add_alignment(origin, item->bounds_align, + (int)-width_diff, (int)-height_diff); +} + +static void update_item_transform(struct obs_scene_item *item) +{ + uint32_t width = obs_source_getwidth(item->source); + uint32_t height = obs_source_getheight(item->source); + uint32_t cx = width; + uint32_t cy = height; + struct vec2 base_origin = {0.0f, 0.0f}; + struct vec2 origin = {0.0f, 0.0f}; + struct vec2 scale = item->scale; + struct calldata params = {0}; + + /* ----------------------- */ + + if (item->bounds_type != OBS_BOUNDS_NONE) { + calculate_bounds_data(item, &origin, &scale, &cx, &cy); + } else { + cx = (uint32_t)((float)cx * scale.x); + cy = (uint32_t)((float)cy * scale.y); + } + + add_alignment(&origin, item->align, (int)cx, (int)cy); + + matrix4_identity(&item->draw_transform); + matrix4_scale3f(&item->draw_transform, &item->draw_transform, + scale.x, scale.y, 1.0f); + matrix4_translate3f(&item->draw_transform, &item->draw_transform, + -origin.x, -origin.y, 0.0f); + matrix4_rotate_aa4f(&item->draw_transform, &item->draw_transform, + 0.0f, 0.0f, 1.0f, RAD(item->rot)); + matrix4_translate3f(&item->draw_transform, &item->draw_transform, + item->pos.x, item->pos.y, 0.0f); + + /* ----------------------- */ + + if (item->bounds_type != OBS_BOUNDS_NONE) { + vec2_copy(&scale, &item->bounds); + } else { + scale.x = (float)width * item->scale.x; + scale.y = (float)height * item->scale.y; + } + + add_alignment(&base_origin, item->align, (int)scale.x, (int)scale.y); + + matrix4_identity(&item->box_transform); + matrix4_scale3f(&item->box_transform, &item->box_transform, + scale.x, scale.y, 1.0f); + matrix4_translate3f(&item->box_transform, &item->box_transform, + -base_origin.x, -base_origin.y, 0.0f); + matrix4_rotate_aa4f(&item->box_transform, &item->box_transform, + 0.0f, 0.0f, 1.0f, RAD(item->rot)); + matrix4_translate3f(&item->box_transform, &item->box_transform, + item->pos.x, item->pos.y, 0.0f); + + /* ----------------------- */ + + item->last_width = width; + item->last_height = height; + + calldata_setptr(¶ms, "scene", item->parent); + calldata_setptr(¶ms, "item", item); + signal_handler_signal(item->parent->source->context.signals, + "item_transform", ¶ms); + calldata_free(¶ms); +} + +static inline bool source_size_changed(struct obs_scene_item *item) +{ + uint32_t width = obs_source_getwidth(item->source); + uint32_t height = obs_source_getheight(item->source); + + return item->last_width != width || item->last_height != height; +} + static void scene_video_render(void *data, effect_t effect) { struct obs_scene *scene = data; @@ -173,14 +313,12 @@ static void scene_video_render(void *data, effect_t effect) continue; } + if (source_size_changed(item)) + update_item_transform(item); + gs_matrix_push(); - gs_matrix_translate3f(item->origin.x, item->origin.y, 0.0f); - gs_matrix_scale3f(item->scale.x, item->scale.y, 1.0f); - gs_matrix_rotaa4f(0.0f, 0.0f, 1.0f, RAD(-item->rot)); - gs_matrix_translate3f(-item->pos.x, -item->pos.y, 0.0f); - + gs_matrix_mul(&item->draw_transform); obs_source_video_render(item->source); - gs_matrix_pop(); item = item->next; @@ -205,12 +343,24 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t item_data) item = obs_scene_add(scene, source); + obs_data_set_default_int(item_data, "align", + OBS_ALIGN_TOP | OBS_ALIGN_LEFT); + item->rot = (float)obs_data_getdouble(item_data, "rot"); + item->align = (uint32_t)obs_data_getint(item_data, "align"); item->visible = obs_data_getbool(item_data, "visible"); - obs_data_get_vec2(item_data, "origin", &item->origin); obs_data_get_vec2(item_data, "pos", &item->pos); obs_data_get_vec2(item_data, "scale", &item->scale); + + item->bounds_type = + (enum obs_bounds_type)obs_data_getint(item_data, "bounds_type"); + item->bounds_align = + (uint32_t)obs_data_getint(item_data, "bounds_align"); + obs_data_get_vec2(item_data, "bounds", &item->bounds); + obs_source_release(source); + + update_item_transform(item); } static void scene_load(void *scene, obs_data_t settings) @@ -238,12 +388,15 @@ static void scene_save_item(obs_data_array_t array, struct obs_scene_item *item) obs_data_t item_data = obs_data_create(); const char *name = obs_source_getname(item->source); - obs_data_setstring(item_data, "name", name); - obs_data_setbool (item_data, "visible", item->visible); - obs_data_setdouble(item_data, "rot", item->rot); - obs_data_set_vec2 (item_data, "origin", &item->origin); - obs_data_set_vec2 (item_data, "pos", &item->pos); - obs_data_set_vec2 (item_data, "scale", &item->scale); + obs_data_setstring(item_data, "name", name); + obs_data_setbool (item_data, "visible", item->visible); + obs_data_setdouble(item_data, "rot", item->rot); + obs_data_set_vec2 (item_data, "pos", &item->pos); + obs_data_set_vec2 (item_data, "scale", &item->scale); + obs_data_setint (item_data, "align", (int)item->align); + obs_data_setint (item_data, "bounds_type", (int)item->bounds_type); + obs_data_setint (item_data, "bounds_align", (int)item->bounds_align); + obs_data_set_vec2 (item_data, "bounds", &item->bounds); obs_data_array_push_back(array, item_data); obs_data_release(item_data); @@ -400,7 +553,10 @@ obs_sceneitem_t obs_scene_add(obs_scene_t scene, obs_source_t source) item->visible = true; item->parent = scene; item->ref = 1; + item->align = OBS_ALIGN_TOP | OBS_ALIGN_LEFT; vec2_set(&item->scale, 1.0f, 1.0f); + matrix4_identity(&item->draw_transform); + matrix4_identity(&item->box_transform); obs_source_addref(source); obs_source_add_child(scene->source, source); @@ -495,28 +651,59 @@ obs_source_t obs_sceneitem_getsource(obs_sceneitem_t item) return item ? item->source : NULL; } +void obs_sceneitem_select(obs_sceneitem_t item, bool select) +{ + struct calldata params = {0}; + const char *command = select ? "item_select" : "item_deselect"; + + if (!item || item->selected == select) + return; + + item->selected = select; + + calldata_setptr(¶ms, "scene", item->parent); + calldata_setptr(¶ms, "item", item); + signal_handler_signal(item->parent->source->context.signals, + command, ¶ms); + + calldata_free(¶ms); +} + +bool obs_sceneitem_selected(obs_sceneitem_t item) +{ + return item ? item->selected : false; +} + void obs_sceneitem_setpos(obs_sceneitem_t item, const struct vec2 *pos) { - if (item) + if (item) { vec2_copy(&item->pos, pos); + update_item_transform(item); + } } void obs_sceneitem_setrot(obs_sceneitem_t item, float rot) { - if (item) + if (item) { item->rot = rot; -} - -void obs_sceneitem_setorigin(obs_sceneitem_t item, const struct vec2 *origin) -{ - if (item) - vec2_copy(&item->origin, origin); + update_item_transform(item); + } } void obs_sceneitem_setscale(obs_sceneitem_t item, const struct vec2 *scale) { - if (item) + if (item) { vec2_copy(&item->scale, scale); + update_item_transform(item); + } +} + +void obs_sceneitem_setalignment(obs_sceneitem_t item, uint32_t alignment) +{ + if (item) { + item->align = alignment; + update_item_transform(item); + } } static inline void signal_move_dir(struct obs_scene_item *item, @@ -583,6 +770,32 @@ void obs_sceneitem_setorder(obs_sceneitem_t item, enum order_movement movement) obs_scene_release(scene); } +void obs_sceneitem_set_bounds_type(obs_sceneitem_t item, + enum obs_bounds_type type) +{ + if (item) { + item->bounds_type = type; + update_item_transform(item); + } +} + +void obs_sceneitem_set_bounds_alignment(obs_sceneitem_t item, + uint32_t alignment) +{ + if (item) { + item->bounds_align = alignment; + update_item_transform(item); + } +} + +void obs_sceneitem_set_bounds(obs_sceneitem_t item, const struct vec2 *bounds) +{ + if (item) { + item->bounds = *bounds; + update_item_transform(item); + } +} + void obs_sceneitem_getpos(obs_sceneitem_t item, struct vec2 *pos) { if (item) @@ -594,14 +807,72 @@ float obs_sceneitem_getrot(obs_sceneitem_t item) return item ? item->rot : 0.0f; } -void obs_sceneitem_getorigin(obs_sceneitem_t item, struct vec2 *origin) -{ - if (item) - vec2_copy(origin, &item->origin); -} - void obs_sceneitem_getscale(obs_sceneitem_t item, struct vec2 *scale) { if (item) vec2_copy(scale, &item->scale); } + +uint32_t obs_sceneitem_getalignment(obs_sceneitem_t item) +{ + return item ? item->align : 0; +} + +enum obs_bounds_type obs_sceneitem_get_bounds_type(obs_sceneitem_t item) +{ + return item ? item->bounds_type : OBS_BOUNDS_NONE; +} + +uint32_t obs_sceneitem_get_bounds_alignment(obs_sceneitem_t item) +{ + return item ? item->bounds_align : 0; +} + +void obs_sceneitem_get_bounds(obs_sceneitem_t item, struct vec2 *bounds) +{ + if (item) + *bounds = item->bounds; +} + +void obs_sceneitem_get_info(obs_sceneitem_t item, + struct obs_sceneitem_info *info) +{ + if (item && info) { + info->pos = item->pos; + info->rot = item->rot; + info->scale = item->scale; + info->alignment = item->align; + info->bounds_type = item->bounds_type; + info->bounds_alignment = item->bounds_align; + info->bounds = item->bounds; + } +} + +void obs_sceneitem_set_info(obs_sceneitem_t item, + const struct obs_sceneitem_info *info) +{ + if (item && info) { + item->pos = info->pos; + item->rot = info->rot; + item->scale = info->scale; + item->align = info->alignment; + item->bounds_type = info->bounds_type; + item->bounds_align = info->bounds_alignment; + item->bounds = info->bounds; + update_item_transform(item); + } +} + +void obs_sceneitem_get_draw_transform(obs_sceneitem_t item, + struct matrix4 *transform) +{ + if (item) + matrix4_copy(transform, &item->draw_transform); +} + +void obs_sceneitem_get_box_transform(obs_sceneitem_t item, + struct matrix4 *transform) +{ + if (item) + matrix4_copy(transform, &item->box_transform); +} diff --git a/libobs/obs-scene.h b/libobs/obs-scene.h index 5451a7f91..f827919da 100644 --- a/libobs/obs-scene.h +++ b/libobs/obs-scene.h @@ -19,6 +19,7 @@ #include "obs.h" #include "obs-internal.h" +#include "graphics/matrix4.h" /* how obs scene! */ @@ -29,11 +30,24 @@ struct obs_scene_item { struct obs_scene *parent; struct obs_source *source; bool visible; + bool selected; - struct vec2 origin; struct vec2 pos; struct vec2 scale; float rot; + uint32_t align; + + /* last width/height of the source, this is used to check whether + * ths transform needs updating */ + uint32_t last_width; + uint32_t last_height; + + struct matrix4 box_transform; + struct matrix4 draw_transform; + + enum obs_bounds_type bounds_type; + uint32_t bounds_align; + struct vec2 bounds; /* would do **prev_next, but not really great for reordering */ struct obs_scene_item *prev; diff --git a/libobs/obs.h b/libobs/obs.h index 8b25d8354..6829b45ec 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -21,6 +21,7 @@ #include "util/bmem.h" #include "graphics/graphics.h" #include "graphics/vec2.h" +#include "graphics/vec3.h" #include "media-io/audio-io.h" #include "media-io/video-io.h" #include "callback/signal.h" @@ -32,6 +33,8 @@ #include "obs-ui.h" #include "obs-properties.h" +struct matrix4; + /* opaque types */ struct obs_display; struct obs_view; @@ -85,13 +88,38 @@ enum allow_direct_render { ALLOW_DIRECT_RENDERING, }; +/** + * Used with scene items to indicate the type of bounds to use for scene items. + * Mostly determines how the image will be scaled within those bounds, or + * whether to use bounds at all. + */ +enum obs_bounds_type { + OBS_BOUNDS_NONE, /**< no bounds */ + 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_MAX_ONLY, /**< no scaling, maximum size only */ +}; + +struct obs_sceneitem_info { + struct vec2 pos; + float rot; + struct vec2 scale; + uint32_t alignment; + + enum obs_bounds_type bounds_type; + uint32_t bounds_alignment; + struct vec2 bounds; +}; + /** * Video initialization structure */ struct obs_video_info { /** - * Graphics module to use (usually "libobs-opengl" or - * "libobs-d3d11") + * Graphics module to use (usually "libobs-opengl" or "libobs-d3d11") */ const char *graphics_module; @@ -669,20 +697,44 @@ EXPORT obs_scene_t obs_sceneitem_getscene(obs_sceneitem_t item); /** Gets the source of a scene item. */ EXPORT obs_source_t obs_sceneitem_getsource(obs_sceneitem_t item); -/* Functions for gettings/setting specific oriantation of a scene item */ +EXPORT void obs_sceneitem_select(obs_sceneitem_t item, bool select); +EXPORT bool obs_sceneitem_selected(obs_sceneitem_t item); + +/* Functions for gettings/setting specific orientation of a scene item */ EXPORT void obs_sceneitem_setpos(obs_sceneitem_t item, const struct vec2 *pos); -EXPORT void obs_sceneitem_setrot(obs_sceneitem_t item, float rot); -EXPORT void obs_sceneitem_setorigin(obs_sceneitem_t item, - const struct vec2 *origin); +EXPORT void obs_sceneitem_setrot(obs_sceneitem_t item, float rot_deg); EXPORT void obs_sceneitem_setscale(obs_sceneitem_t item, const struct vec2 *scale); +EXPORT void obs_sceneitem_setalignment(obs_sceneitem_t item, + uint32_t alignment); EXPORT void obs_sceneitem_setorder(obs_sceneitem_t item, enum order_movement movement); +EXPORT void obs_sceneitem_set_bounds_type(obs_sceneitem_t item, + enum obs_bounds_type type); +EXPORT void obs_sceneitem_set_bounds_alignment(obs_sceneitem_t item, + uint32_t alignment); +EXPORT void obs_sceneitem_set_bounds(obs_sceneitem_t item, + const struct vec2 *bounds); + EXPORT void obs_sceneitem_getpos(obs_sceneitem_t item, struct vec2 *pos); EXPORT float obs_sceneitem_getrot(obs_sceneitem_t item); -EXPORT void obs_sceneitem_getorigin(obs_sceneitem_t item, struct vec2 *center); EXPORT void obs_sceneitem_getscale(obs_sceneitem_t item, struct vec2 *scale); +EXPORT uint32_t obs_sceneitem_getalignment(obs_sceneitem_t item); + +EXPORT enum obs_bounds_type obs_sceneitem_get_bounds_type(obs_sceneitem_t item); +EXPORT uint32_t obs_sceneitem_get_bounds_alignment(obs_sceneitem_t item); +EXPORT void obs_sceneitem_get_bounds(obs_sceneitem_t item, struct vec2 *bounds); + +EXPORT void obs_sceneitem_get_info(obs_sceneitem_t item, + struct obs_sceneitem_info *info); +EXPORT void obs_sceneitem_set_info(obs_sceneitem_t item, + const struct obs_sceneitem_info *info); + +EXPORT void obs_sceneitem_get_draw_transform(obs_sceneitem_t item, + struct matrix4 *transform); +EXPORT void obs_sceneitem_get_box_transform(obs_sceneitem_t item, + struct matrix4 *transform); /* ------------------------------------------------------------------------- */ diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index 27b2422b2..6231e904b 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -57,6 +57,8 @@ set(obs_SOURCES window-basic-settings.cpp window-basic-properties.cpp window-basic-source-select.cpp + window-basic-transform.cpp + window-basic-preview.cpp window-namedialog.cpp window-log-reply.cpp properties-view.cpp @@ -71,6 +73,8 @@ set(obs_HEADERS window-basic-settings.hpp window-basic-properties.hpp window-basic-source-select.hpp + window-basic-transform.hpp + window-basic-preview.hpp window-namedialog.hpp window-log-reply.hpp properties-view.hpp @@ -83,6 +87,7 @@ set(obs_UI forms/NameDialog.ui forms/OBSLogReply.ui forms/OBSBasic.ui + forms/OBSBasicTransform.ui forms/OBSBasicSettings.ui forms/OBSBasicSourceSelect.ui forms/OBSBasicProperties.ui) diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui index 1637c29a4..6568bb9c9 100644 --- a/obs/forms/OBSBasic.ui +++ b/obs/forms/OBSBasic.ui @@ -29,7 +29,7 @@ - + 0 @@ -305,7 +305,7 @@ - Volume + Mixer @@ -463,7 +463,35 @@ + + + Basic.MainMenu.Edit + + + + Basic.MainMenu.Edit.Transform + + + + + + + + + + + + + + + + + + + + + @@ -610,12 +638,90 @@ Basic.MainMenu.Help.Logs.UploadCurrentLog + + + false + + + Basic.MainMenu.Edit.Undo + + + + + false + + + Basic.MainMenu.Edit.Redo + + + + + Basic.MainMenu.Edit.Transform.EditTransform + + + + + Basic.MainMenu.Edit.Transform.Rotate90CW + + + + + Basic.MainMenu.Edit.Transform.Rotate90CCW + + + + + Basic.MainMenu.Edit.Transform.Rotate180 + + + + + Basic.MainMenu.Edit.Transform.FitToScreen + + + Ctrl+F + + + + + Basic.MainMenu.Edit.Transform.StretchToScreen + + + Ctrl+S + + + + + Basic.MainMenu.Edit.Transform.ResetTransform + + + Ctrl+R + + + + + Basic.MainMenu.Edit.Transform.CenterToScreen + + + Ctrl+C + + + + + Basic.MainMenu.Edit.Transform.FlipHorizontal + + + + + Basic.MainMenu.Edit.Transform.FlipVertical + + - OBSQTDisplay + OBSBasicPreview QWidget -
qt-display.hpp
+
window-basic-preview.hpp
1
diff --git a/obs/forms/OBSBasicTransform.ui b/obs/forms/OBSBasicTransform.ui new file mode 100644 index 000000000..a242bf97a --- /dev/null +++ b/obs/forms/OBSBasicTransform.ui @@ -0,0 +1,462 @@ + + + OBSBasicTransform + + + false + + + + 0 + 0 + 564 + 241 + + + + Basic.TransformWindow + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + 0 + 0 + + + + + 170 + 0 + + + + Basic.TransformWindow.Position + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 0 + + + + -9001.000000000000000 + + + 9001.000000000000000 + + + + + + + + 100 + 0 + + + + -9001.000000000000000 + + + 9001.000000000000000 + + + + + + + + + + Basic.TransformWindow.Rotation + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + -360.000000000000000 + + + 360.000000000000000 + + + 0.100000000000000 + + + + + + + Basic.TransformWindow.Scale + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 0 + + + + -9001.000000000000000 + + + 9001.000000000000000 + + + 0.010000000000000 + + + + + + + + 100 + 0 + + + + -9001.000000000000000 + + + 9001.000000000000000 + + + 0.010000000000000 + + + + + + + + + + Basic.TransformWindow.Alignment + + + + + + + Basic.TransformWindow.Alignment.TopLeft + + + + Basic.TransformWindow.Alignment.TopLeft + + + + + Basic.TransformWindow.Alignment.TopCenter + + + + + Basic.TransformWindow.Alignment.TopRight + + + + + Basic.TransformWindow.Alignment.CenterLeft + + + + + Basic.TransformWindow.Alignment.Center + + + + + Basic.TransformWindow.Alignment.CenterRight + + + + + Basic.TransformWindow.Alignment.BottomLeft + + + + + Basic.TransformWindow.Alignment.BottomCenter + + + + + Basic.TransformWindow.Alignment.BottomRight + + + + + + + + Basic.TransformWindow.BoundsType + + + + + + + + Basic.TransformWindow.BoundsType.None + + + + + Basic.TransformWindow.BoundsType.Stretch + + + + + Basic.TransformWindow.BoundsType.ScaleInner + + + + + Basic.TransformWindow.BoundsType.ScaleOuter + + + + + Basic.TransformWindow.BoundsType.ScaleToWidth + + + + + Basic.TransformWindow.BoundsType.ScaleToHeight + + + + + Basic.TransformWindow.BoundsType.MaxOnly + + + + + + + + Qt::Vertical + + + + 20 + 20 + + + + + + + + Basic.TransformWindow.BoundsAlignment + + + + + + + Basic.TransformWindow.Bounds + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 100 + 0 + + + + 1.000000000000000 + + + 9001.000000000000000 + + + + + + + false + + + + 100 + 0 + + + + 1.000000000000000 + + + 9001.000000000000000 + + + + + + + + + + false + + + Basic.TransformWindow.Alignment.TopLeft + + + + Basic.TransformWindow.Alignment.TopLeft + + + + + Basic.TransformWindow.Alignment.TopCenter + + + + + Basic.TransformWindow.Alignment.TopRight + + + + + Basic.TransformWindow.Alignment.CenterLeft + + + + + Basic.TransformWindow.Alignment.Center + + + + + Basic.TransformWindow.Alignment.CenterRight + + + + + Basic.TransformWindow.Alignment.BottomLeft + + + + + Basic.TransformWindow.Alignment.BottomCenter + + + + + Basic.TransformWindow.Alignment.BottomRight + + + + + + + + + + + diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 93ce65ab2..75b072ba9 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" @@ -47,6 +48,8 @@ #include #include +#define PREVIEW_EDGE_SIZE 10 + using namespace std; Q_DECLARE_METATYPE(OBSScene); @@ -55,15 +58,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 +438,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 +507,8 @@ void OBSBasic::OBSInit() if (!InitService()) throw "Failed to initialize service"; + InitPrimitives(); + Load(savePath); ResetAudioDevices(); } @@ -501,14 +519,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(); } @@ -788,24 +818,73 @@ void OBSBasic::ChannelChanged(void *data, calldata_t params) Q_ARG(OBSSource, OBSSource(source))); } +void OBSBasic::DrawBackdrop(float cx, float cy) +{ + if (!box) + return; + + effect_t solid = obs_get_solid_effect(); + eparam_t color = effect_getparambyname(solid, "color"); + technique_t tech = effect_gettechnique(solid, "Solid"); + + vec4 colorVal; + vec4_set(&colorVal, 0.0f, 0.0f, 0.0f, 1.0f); + effect_setvec4(solid, color, &colorVal); + + technique_begin(tech); + technique_beginpass(tech, 0); + gs_matrix_push(); + gs_matrix_identity(); + gs_matrix_scale3f(float(cx), float(cy), 1.0f); + + gs_load_vertexbuffer(box); + gs_draw(GS_TRISTRIP, 0, 0); + + gs_matrix_pop(); + technique_endpass(tech); + technique_end(tech); + + gs_load_vertexbuffer(nullptr); +} + 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); + + window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); obs_render_main_view(); + gs_load_vertexbuffer(nullptr); + + /* --------------------------------------- */ + + float right = float(window->ui->preview->width()) - window->previewX; + float bottom = float(window->ui->preview->height()) - window->previewY; + + gs_ortho(-window->previewX, right, + -window->previewY, bottom, + -100.0f, 100.0f); + gs_resetviewport(); + + window->ui->preview->DrawSceneEditing(); + + /* --------------------------------------- */ gs_projection_pop(); gs_viewport_pop(); @@ -981,9 +1060,13 @@ void OBSBasic::ResizePreview(uint32_t cx, uint32_t cy) /* resize preview panel to fix to the top section of the window */ targetSize = GetPixelSize(ui->preview); GetScaleAndCenterPos(int(cx), int(cy), - targetSize.width(), targetSize.height(), + targetSize.width() - PREVIEW_EDGE_SIZE * 2, + targetSize.height() - PREVIEW_EDGE_SIZE * 2, previewX, previewY, previewScale); + previewX += float(PREVIEW_EDGE_SIZE); + previewY += float(PREVIEW_EDGE_SIZE); + if (isVisible()) { if (resizeTimer) killTimer(resizeTimer); @@ -1155,8 +1238,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 +1667,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..542673ec2 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,43 @@ 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 DrawBackdrop(float cx, float cy); void SetupEncoders(); @@ -97,7 +107,8 @@ private: void InitOBSCallbacks(); - OBSScene GetCurrentScene(); + void InitPrimitives(); + OBSSceneItem GetCurrentSceneItem(); void GetFPSCommon(uint32_t &num, uint32_t &den) const; @@ -153,6 +164,8 @@ private: void AddSourcePopupMenu(const QPoint &pos); public: + OBSScene GetCurrentScene(); + obs_service_t GetService(); void SetService(obs_service_t service); @@ -167,6 +180,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 +199,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 +229,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 +