diff --git a/UI/data/images/overflow.png b/UI/data/images/overflow.png new file mode 100644 index 000000000..5bddc3384 Binary files /dev/null and b/UI/data/images/overflow.png differ diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index 597f077ff..0791a8534 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -599,6 +599,10 @@ Basic.Settings.General.SysTray="System Tray" Basic.Settings.General.SysTrayWhenStarted="Minimize to system tray when started" Basic.Settings.General.SystemTrayHideMinimize="Always minimize to system tray instead of task bar" Basic.Settings.General.SaveProjectors="Save projectors on exit" +Basic.Settings.General.Preview="Preview" +Basic.Settings.General.OverflowHidden="Hide overflow" +Basic.Settings.General.OverflowAlwaysVisible="Overflow always visible" +Basic.Settings.General.OverflowSelectionHidden="Show overflow even when source is invisible" Basic.Settings.General.SwitchOnDoubleClick="Transition to scene when double-clicked" Basic.Settings.General.StudioPortraitLayout="Enable portrait/vertical layout" Basic.Settings.General.Multiview="Multiview" diff --git a/UI/forms/OBSBasicSettings.ui b/UI/forms/OBSBasicSettings.ui index fc8fed8a9..f123f0e37 100644 --- a/UI/forms/OBSBasicSettings.ui +++ b/UI/forms/OBSBasicSettings.ui @@ -540,6 +540,58 @@ + + + + StudioMode.Preview + + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + 2 + + + + + Basic.Settings.General.OverflowAlwaysVisible + + + + + + + Qt::Horizontal + + + + 170 + 5 + + + + + + + + Basic.Settings.General.OverflowSelectionHidden + + + + + + + Basic.Settings.General.OverflowHidden + + + + + + diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index 93704f7bd..e227a8ab7 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -3278,6 +3278,16 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy) gs_viewport_push(); gs_projection_push(); + QSize previewSize = GetPixelSize(window->ui->preview); + float right = float(previewSize.width()) - window->previewX; + float bottom = float(previewSize.height()) - window->previewY; + + gs_ortho(-window->previewX, right, + -window->previewY, bottom, + -100.0f, 100.0f); + + window->ui->preview->DrawOverflow(); + /* --------------------------------------- */ gs_ortho(0.0f, float(ovi.base_width), 0.0f, float(ovi.base_height), @@ -3287,6 +3297,7 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy) window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height)); + if (window->IsPreviewProgramMode()) { OBSScene scene = window->GetCurrentScene(); obs_source_t *source = obs_scene_get_source(scene); @@ -3299,9 +3310,6 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy) /* --------------------------------------- */ - QSize previewSize = GetPixelSize(window->ui->preview); - float right = float(previewSize.width()) - window->previewX; - float bottom = float(previewSize.height()) - window->previewY; gs_ortho(-window->previewX, right, -window->previewY, bottom, diff --git a/UI/window-basic-preview.cpp b/UI/window-basic-preview.cpp index 58af1ffa8..e75a384ea 100644 --- a/UI/window-basic-preview.cpp +++ b/UI/window-basic-preview.cpp @@ -3,11 +3,13 @@ #include #include +#include #include #include #include "window-basic-preview.hpp" #include "window-basic-main.hpp" #include "obs-app.hpp" +#include "platform.hpp" #define HANDLE_RADIUS 4.0f #define HANDLE_SEL_RADIUS (HANDLE_RADIUS * 1.5f) @@ -24,6 +26,13 @@ OBSBasicPreview::OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags) setMouseTracking(true); } +OBSBasicPreview::~OBSBasicPreview() +{ + if (overflow) { + gs_texture_destroy(overflow); + } +} + vec2 OBSBasicPreview::GetMouseEventPos(QMouseEvent *event) { OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); @@ -1213,6 +1222,92 @@ static inline bool crop_enabled(const obs_sceneitem_crop *crop) crop->bottom > 0; } +bool OBSBasicPreview::DrawSelectedOverflow(obs_scene_t *scene, + obs_sceneitem_t *item, void *param) +{ + if (obs_sceneitem_locked(item)) + return true; + + if (!SceneItemHasVideo(item)) + return true; + + bool select = config_get_bool(GetGlobalConfig(), "BasicWindow", + "OverflowSelectionHidden"); + + if (!select && !obs_sceneitem_visible(item)) + return true; + + if (obs_sceneitem_is_group(item)) { + matrix4 mat; + obs_sceneitem_get_draw_transform(item, &mat); + + gs_matrix_push(); + gs_matrix_mul(&mat); + obs_sceneitem_group_enum_items(item, DrawSelectedOverflow, param); + gs_matrix_pop(); + } + + bool always = config_get_bool(GetGlobalConfig(), "BasicWindow", + "OverflowAlwaysVisible"); + + if (!always && !obs_sceneitem_selected(item)) + return true; + + OBSBasicPreview *prev = reinterpret_cast(param); + + matrix4 boxTransform; + matrix4 invBoxTransform; + obs_sceneitem_get_box_transform(item, &boxTransform); + matrix4_inv(&invBoxTransform, &boxTransform); + + vec3 bounds[] = { + {{{0.f, 0.f, 0.f}}}, + {{{1.f, 0.f, 0.f}}}, + {{{0.f, 1.f, 0.f}}}, + {{{1.f, 1.f, 0.f}}}, + }; + + bool visible = std::all_of(std::begin(bounds), std::end(bounds), + [&](const vec3 &b) + { + vec3 pos; + vec3_transform(&pos, &b, &boxTransform); + vec3_transform(&pos, &pos, &invBoxTransform); + return CloseFloat(pos.x, b.x) && CloseFloat(pos.y, b.y); + }); + + if (!visible) + return true; + + obs_transform_info info; + obs_sceneitem_get_info(item, &info); + + gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_REPEAT); + gs_eparam_t *image = gs_effect_get_param_by_name(solid, "image"); + gs_eparam_t *scale = gs_effect_get_param_by_name(solid, "scale"); + + vec2 s; + vec2_set(&s, boxTransform.x.x / 96, boxTransform.y.y / 96); + + gs_effect_set_vec2(scale, &s); + gs_effect_set_texture(image, prev->overflow); + + gs_matrix_push(); + gs_matrix_mul(&boxTransform); + + obs_sceneitem_crop crop; + obs_sceneitem_get_crop(item, &crop); + + while (gs_effect_loop(solid, "Draw")) { + gs_draw_sprite(prev->overflow, 0, 1, 1); + } + + gs_matrix_pop(); + + UNUSED_PARAMETER(scene); + return true; +} + bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *scene, obs_sceneitem_t *item, void *param) { @@ -1312,6 +1407,37 @@ bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *scene, return true; } +void OBSBasicPreview::DrawOverflow() +{ + if (locked) + return; + + bool hidden = config_get_bool(GetGlobalConfig(), "BasicWindow", + "OverflowHidden"); + + if (hidden) + return; + + if (!overflow) { + std::string path; + GetDataFilePath("images/overflow.png", path); + overflow = gs_texture_create_from_file(path.c_str()); + } + + OBSBasic *main = reinterpret_cast(App()->GetMainWindow()); + + OBSScene scene = main->GetCurrentScene(); + + if (scene) { + gs_matrix_push(); + gs_matrix_scale3f(main->previewScale, main->previewScale, 1.0f); + obs_scene_enum_items(scene, DrawSelectedOverflow, this); + gs_matrix_pop(); + } + + gs_load_vertexbuffer(nullptr); +} + void OBSBasicPreview::DrawSceneEditing() { if (locked) diff --git a/UI/window-basic-preview.hpp b/UI/window-basic-preview.hpp index c177dd9a4..be075fd7f 100644 --- a/UI/window-basic-preview.hpp +++ b/UI/window-basic-preview.hpp @@ -43,6 +43,8 @@ private: matrix4 itemToScreen; matrix4 invGroupTransform; + gs_texture_t *overflow = nullptr; + vec2 startPos; vec2 lastMoveOffset; vec2 scrollingFrom; @@ -58,6 +60,8 @@ private: float scalingAmount = 1.0f; static vec2 GetMouseEventPos(QMouseEvent *event); + static bool DrawSelectedOverflow(obs_scene_t *scene, + obs_sceneitem_t *item, void *param); static bool DrawSelectedItem(obs_scene_t *scene, obs_sceneitem_t *item, void *param); @@ -84,6 +88,7 @@ private: public: OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags = 0); + ~OBSBasicPreview(); virtual void keyPressEvent(QKeyEvent *event) override; virtual void keyReleaseEvent(QKeyEvent *event) override; @@ -94,6 +99,7 @@ public: virtual void mouseReleaseEvent(QMouseEvent *event) override; virtual void mouseMoveEvent(QMouseEvent *event) override; + void DrawOverflow(); void DrawSceneEditing(); inline void SetLocked(bool newLockedVal) {locked = newLockedVal;} diff --git a/UI/window-basic-settings.cpp b/UI/window-basic-settings.cpp index 2e1d45826..708104d39 100644 --- a/UI/window-basic-settings.cpp +++ b/UI/window-basic-settings.cpp @@ -316,6 +316,9 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) HookWidget(ui->centerSnapping, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->sourceSnapping, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->snapDistance, DSCROLL_CHANGED,GENERAL_CHANGED); + HookWidget(ui->overflowHide, CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->overflowAlwaysVisible,CHECK_CHANGED, GENERAL_CHANGED); + HookWidget(ui->overflowSelectionHide,CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->doubleClickSwitch, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->studioPortraitLayout, CHECK_CHANGED, GENERAL_CHANGED); HookWidget(ui->multiviewMouseSwitch, CHECK_CHANGED, GENERAL_CHANGED); @@ -1088,6 +1091,18 @@ void OBSBasicSettings::LoadGeneralSettings() "BasicWindow", "ProjectorAlwaysOnTop"); ui->projectorAlwaysOnTop->setChecked(projectorAlwaysOnTop); + bool overflowHide = config_get_bool(GetGlobalConfig(), + "BasicWindow", "OverflowHidden"); + ui->overflowHide->setChecked(overflowHide); + + bool overflowAlwaysVisible = config_get_bool(GetGlobalConfig(), + "BasicWindow", "OverflowAlwaysVisible"); + ui->overflowAlwaysVisible->setChecked(overflowAlwaysVisible); + + bool overflowSelectionHide = config_get_bool(GetGlobalConfig(), + "BasicWindow", "OverflowSelectionHidden"); + ui->overflowSelectionHide->setChecked(overflowSelectionHide); + bool doubleClickSwitch = config_get_bool(GetGlobalConfig(), "BasicWindow", "TransitionOnDoubleClick"); ui->doubleClickSwitch->setChecked(doubleClickSwitch); @@ -2666,6 +2681,18 @@ void OBSBasicSettings::SaveGeneralSettings() config_set_double(GetGlobalConfig(), "BasicWindow", "SnapDistance", ui->snapDistance->value()); + if (WidgetChanged(ui->overflowAlwaysVisible)) + config_set_bool(GetGlobalConfig(), "BasicWindow", + "OverflowAlwaysVisible", + ui->overflowAlwaysVisible->isChecked()); + if (WidgetChanged(ui->overflowHide)) + config_set_bool(GetGlobalConfig(), "BasicWindow", + "OverflowHidden", + ui->overflowHide->isChecked()); + if (WidgetChanged(ui->overflowSelectionHide)) + config_set_bool(GetGlobalConfig(), "BasicWindow", + "OverflowSelectionHidden", + ui->overflowSelectionHide->isChecked()); if (WidgetChanged(ui->doubleClickSwitch)) config_set_bool(GetGlobalConfig(), "BasicWindow", "TransitionOnDoubleClick", diff --git a/libobs/data/repeat.effect b/libobs/data/repeat.effect new file mode 100644 index 000000000..cdc95baa5 --- /dev/null +++ b/libobs/data/repeat.effect @@ -0,0 +1,55 @@ +uniform float4x4 ViewProj; +uniform float4x4 color_matrix; +uniform float3 color_range_min = {0.0, 0.0, 0.0}; +uniform float3 color_range_max = {1.0, 1.0, 1.0}; +uniform texture2d image; +uniform float2 scale; + +sampler_state def_sampler { + Filter = Linear; + AddressU = Repeat; + AddressV = Repeat; +}; + +struct VertInOut { + float4 pos : POSITION; + float2 uv : TEXCOORD0; +}; + +VertInOut VSDefault(VertInOut vert_in) +{ + VertInOut vert_out; + vert_out.pos = mul(float4(vert_in.pos.xyz, 1.0), ViewProj); + vert_out.uv = vert_in.uv * scale; + return vert_out; +} + +float4 PSDrawBare(VertInOut vert_in) : TARGET +{ + return image.Sample(def_sampler, vert_in.uv); +} + +float4 PSDrawMatrix(VertInOut vert_in) : TARGET +{ + float4 yuv = image.Sample(def_sampler, vert_in.uv); + yuv.xyz = clamp(yuv.xyz, color_range_min, color_range_max); + return saturate(mul(float4(yuv.xyz, 1.0), color_matrix)); +} + +technique Draw +{ + pass + { + vertex_shader = VSDefault(vert_in); + pixel_shader = PSDrawBare(vert_in); + } +} + +technique DrawMatrix +{ + pass + { + vertex_shader = VSDefault(vert_in); + pixel_shader = PSDrawMatrix(vert_in); + } +} diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 3c48893b8..30e48b2af 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -255,6 +255,7 @@ struct obs_core_video { gs_effect_t *default_rect_effect; gs_effect_t *opaque_effect; gs_effect_t *solid_effect; + gs_effect_t *repeat_effect; gs_effect_t *conversion_effect; gs_effect_t *bicubic_effect; gs_effect_t *lanczos_effect; diff --git a/libobs/obs.c b/libobs/obs.c index a61c4b2de..f74f56a81 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -309,6 +309,11 @@ static int obs_init_graphics(struct obs_video_info *ovi) NULL); bfree(filename); + filename = obs_find_data_file("repeat.effect"); + video->repeat_effect = gs_effect_create_from_file(filename, + NULL); + bfree(filename); + filename = obs_find_data_file("format_conversion.effect"); video->conversion_effect = gs_effect_create_from_file(filename, NULL); @@ -1570,6 +1575,8 @@ gs_effect_t *obs_get_base_effect(enum obs_base_effect effect) return obs->video.opaque_effect; case OBS_EFFECT_SOLID: return obs->video.solid_effect; + case OBS_EFFECT_REPEAT: + return obs->video.repeat_effect; case OBS_EFFECT_BICUBIC: return obs->video.bicubic_effect; case OBS_EFFECT_LANCZOS: diff --git a/libobs/obs.h b/libobs/obs.h index e15462c1f..9a54ac075 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -601,6 +601,7 @@ enum obs_base_effect { OBS_EFFECT_LANCZOS, /**< Lanczos downscale */ OBS_EFFECT_BILINEAR_LOWRES, /**< Bilinear low resolution downscale */ OBS_EFFECT_PREMULTIPLIED_ALPHA,/**< Premultiplied alpha */ + OBS_EFFECT_REPEAT, /**< RGB/YUV (repeating) */ }; /** Returns a commonly used base effect */