Преглед на файлове

Merge pull request #1611 from VodBox/misc-ux-changes

UI: Scene item overflow, handle/outline change and hover outline
Jim преди 6 години
родител
ревизия
bb580c7c03

BIN
UI/data/images/overflow.png


+ 4 - 0
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"

+ 52 - 0
UI/forms/OBSBasicSettings.ui

@@ -540,6 +540,58 @@
                   </layout>
                  </widget>
                 </item>
+                <item>
+                 <widget class="QGroupBox" name="groupBox_18">
+                  <property name="title">
+                   <string>StudioMode.Preview</string>
+                  </property>
+                  <layout class="QFormLayout" name="formLayout_35">
+                   <property name="fieldGrowthPolicy">
+                    <enum>QFormLayout::AllNonFixedFieldsGrow</enum>
+                   </property>
+                   <property name="labelAlignment">
+                    <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+                   </property>
+                   <property name="topMargin">
+                    <number>2</number>
+                   </property>
+                   <item row="1" column="1">
+                    <widget class="QCheckBox" name="overflowAlwaysVisible">
+                     <property name="text">
+                      <string>Basic.Settings.General.OverflowAlwaysVisible</string>
+                     </property>
+                    </widget>
+                   </item>
+                   <item row="0" column="0">
+                    <spacer name="horizontalSpacer_25">
+                     <property name="orientation">
+                      <enum>Qt::Horizontal</enum>
+                     </property>
+                     <property name="sizeHint" stdset="0">
+                      <size>
+                       <width>170</width>
+                       <height>5</height>
+                      </size>
+                     </property>
+                    </spacer>
+                   </item>
+                   <item row="2" column="1">
+                    <widget class="QCheckBox" name="overflowSelectionHide">
+                     <property name="text">
+                      <string>Basic.Settings.General.OverflowSelectionHidden</string>
+                     </property>
+                    </widget>
+                   </item>
+                   <item row="0" column="1">
+                    <widget class="QCheckBox" name="overflowHide">
+                     <property name="text">
+                      <string>Basic.Settings.General.OverflowHidden</string>
+                     </property>
+                    </widget>
+                   </item>
+                  </layout>
+                 </widget>
+                </item>
                 <item>
                  <widget class="QGroupBox" name="groupBox_11">
                   <property name="title">

+ 12 - 0
UI/source-tree.cpp

@@ -35,6 +35,7 @@ SourceTreeItem::SourceTreeItem(SourceTree *tree_, OBSSceneItem sceneitem_)
 	  sceneitem    (sceneitem_)
 {
 	setAttribute(Qt::WA_TranslucentBackground);
+	setMouseTracking(true);
 
 	obs_source_t *source = obs_sceneitem_get_source(sceneitem);
 	const char *name = obs_source_get_name(source);
@@ -911,6 +912,8 @@ SourceTree::SourceTree(QWidget *parent_) : QListView(parent_)
 		"*[bgColor=\"6\"]{background-color:rgba(255,68,255,33%);}" \
 		"*[bgColor=\"7\"]{background-color:rgba(68,68,68,33%);}" \
 		"*[bgColor=\"8\"]{background-color:rgba(255,255,255,33%);}"));
+
+	setMouseTracking(true);
 }
 
 void SourceTree::ResetWidgets()
@@ -1264,6 +1267,15 @@ void SourceTree::dropEvent(QDropEvent *event)
 	QListView::dropEvent(event);
 }
 
+void SourceTree::mouseMoveEvent(QMouseEvent *event)
+{
+	QPoint pos = event->pos();
+	SourceTreeItem *item = qobject_cast<SourceTreeItem *>
+		(childAt(pos));
+
+	currentHover = item;
+}
+
 void SourceTree::selectionChanged(
 		const QItemSelection &selected,
 		const QItemSelection &deselected)

+ 11 - 0
UI/source-tree.hpp

@@ -142,6 +142,8 @@ class SourceTree : public QListView {
 		return reinterpret_cast<SourceTreeModel *>(model());
 	}
 
+	SourceTreeItem *currentHover;
+
 public:
 	inline SourceTreeItem *GetItemWidget(int idx)
 	{
@@ -149,6 +151,14 @@ public:
 		return reinterpret_cast<SourceTreeItem *>(widget);
 	}
 
+	inline SourceTreeItem *GetHoveredItem()
+	{
+		if (underMouse()) {
+			return currentHover;
+		}
+		return nullptr;
+	}
+
 	explicit SourceTree(QWidget *parent = nullptr);
 
 	inline bool IgnoreReorder() const {return ignoreReorder;}
@@ -175,6 +185,7 @@ public slots:
 protected:
 	virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
 	virtual void dropEvent(QDropEvent *event) override;
+	virtual void mouseMoveEvent(QMouseEvent *event) override;
 
 	virtual void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
 };

+ 11 - 3
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,

+ 261 - 36
UI/window-basic-preview.cpp

@@ -3,11 +3,13 @@
 
 #include <algorithm>
 #include <cmath>
+#include <string>
 #include <graphics/vec4.h>
 #include <graphics/matrix4.h>
 #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<OBSBasic*>(App()->GetMainWindow());
@@ -603,6 +612,9 @@ void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event)
 		mouseDown    = false;
 		mouseMoved   = false;
 		cropping     = false;
+
+		OBSSceneItem item = GetItemAtPos(pos, true);
+		hovered = item;
 	}
 }
 
@@ -1149,6 +1161,8 @@ void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event)
 		return;
 
 	if (mouseDown) {
+		hovered = nullptr;
+
 		vec2 pos = GetMouseEventPos(event);
 
 		if (!mouseMoved && !mouseOverItems &&
@@ -1185,10 +1199,22 @@ void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event)
 		}
 
 		mouseMoved = true;
+	} else {
+		vec2 pos = GetMouseEventPos(event);
+		OBSSceneItem item = GetItemAtPos(pos, true);
+
+		hovered = item;
 	}
 }
 
-static void DrawCircleAtPos(float x, float y)
+void OBSBasicPreview::leaveEvent(QEvent *event)
+{
+	hovered = nullptr;
+
+	UNUSED_PARAMETER(event);
+}
+
+static void DrawSquareAtPos(float x, float y)
 {
 	struct vec3 pos;
 	vec3_set(&pos, x, y, 0.0f);
@@ -1200,11 +1226,73 @@ static void DrawCircleAtPos(float x, float y)
 	gs_matrix_push();
 	gs_matrix_identity();
 	gs_matrix_translate(&pos);
-	gs_matrix_scale3f(HANDLE_RADIUS, HANDLE_RADIUS, 1.0f);
-	gs_draw(GS_LINESTRIP, 0, 0);
+
+	gs_matrix_translate3f(-HANDLE_RADIUS, -HANDLE_RADIUS, 0.0f);
+	gs_matrix_scale3f(HANDLE_RADIUS*2, HANDLE_RADIUS*2, 1.0f);
+	gs_draw(GS_TRISTRIP, 0, 0);
 	gs_matrix_pop();
 }
 
+static void DrawLine(float x1, float y1, float x2, float y2, float thickness)
+{
+	struct matrix4 matrix;
+	gs_matrix_get(&matrix);
+
+	float ySide = (y1 == y2) ? (y1 < 0.5f ? 1.0f : -1.0f) : 0.0f;
+	float xSide = (x1 == x2) ? (x1 < 0.5f ? 1.0f : -1.0f) : 0.0f;
+
+	gs_render_start(true);
+
+	gs_vertex2f(x1, y1);
+	gs_vertex2f(x1 + (xSide * (thickness / matrix.x.x)),
+		y1 + (ySide * (thickness / matrix.y.y)));
+	gs_vertex2f(x2 + (xSide * (thickness / matrix.x.x)),
+		y2 + (ySide * (thickness / matrix.y.y)));
+	gs_vertex2f(x2, y2);
+	gs_vertex2f(x1, y1);
+
+	gs_vertbuffer_t *line = gs_render_save();
+
+	gs_load_vertexbuffer(line);
+	gs_draw(GS_TRISTRIP, 0, 0);
+	gs_vertexbuffer_destroy(line);
+}
+
+static void DrawRect(float thickness)
+{
+	struct matrix4 matrix;
+	gs_matrix_get(&matrix);
+
+	gs_render_start(true);
+
+	gs_vertex2f(0.0f, 0.0f);
+	gs_vertex2f(0.0f + (thickness / matrix.x.x), 0.0f);
+	gs_vertex2f(0.0f + (thickness / matrix.x.x), 1.0f);
+	gs_vertex2f(0.0f, 1.0f);
+	gs_vertex2f(0.0f, 0.0f);
+	gs_vertex2f(0.0f, 1.0f);
+	gs_vertex2f(0.0f, 1.0f - (thickness / matrix.y.y));
+	gs_vertex2f(1.0f, 1.0f - (thickness / matrix.y.y));
+	gs_vertex2f(1.0f, 1.0f);
+	gs_vertex2f(0.0f, 1.0f);
+	gs_vertex2f(1.0f, 1.0f);
+	gs_vertex2f(1.0f - (thickness / matrix.x.x), 1.0f);
+	gs_vertex2f(1.0f - (thickness / matrix.x.x), 0.0f);
+	gs_vertex2f(1.0f, 0.0f);
+	gs_vertex2f(1.0f, 1.0f);
+	gs_vertex2f(1.0f, 0.0f);
+	gs_vertex2f(1.0f, 0.0f + (thickness / matrix.y.y));
+	gs_vertex2f(0.0f, 0.0f + (thickness / matrix.y.y));
+	gs_vertex2f(0.0f, 0.0f);
+	gs_vertex2f(1.0f, 0.0f);
+
+	gs_vertbuffer_t *rect = gs_render_save();
+
+	gs_load_vertexbuffer(rect);
+	gs_draw(GS_TRISTRIP, 0, 0);
+	gs_vertexbuffer_destroy(rect);
+}
+
 static inline bool crop_enabled(const obs_sceneitem_crop *crop)
 {
 	return crop->left > 0  ||
@@ -1213,6 +1301,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<OBSBasicPreview*>(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)
 {
@@ -1232,10 +1406,17 @@ bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *scene,
 		gs_matrix_pop();
 	}
 
-	if (!obs_sceneitem_selected(item))
-		return true;
-
 	OBSBasic *main = reinterpret_cast<OBSBasic*>(App()->GetMainWindow());
+	OBSBasicPreview *prev = reinterpret_cast<OBSBasicPreview*>(param);
+
+	SourceTreeItem *hovItem = main->ui->sources->GetHoveredItem();
+	SourceTreeItem *curItem = main->GetItemWidgetFromSceneItem(item);
+
+	bool hover = (curItem && hovItem == curItem) || prev->hovered == item;
+	bool selected = obs_sceneitem_selected(item);
+
+	if (!selected && !hover)
+		return true;
 
 	matrix4 boxTransform;
 	matrix4 invBoxTransform;
@@ -1249,6 +1430,14 @@ bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *scene,
 		{{{1.f, 1.f, 0.f}}},
 	};
 
+	vec4 red;
+	vec4 green;
+	vec4 blue;
+
+	vec4_set(&red, 1.0f, 0.0f, 0.0f, 1.0f);
+	vec4_set(&green, 0.0f, 1.0f, 0.0f, 1.0f);
+	vec4_set(&blue, 0.0f, 0.5f, 1.0f, 1.0f);
+
 	bool visible = std::all_of(std::begin(bounds), std::end(bounds),
 			[&](const vec3 &b)
 	{
@@ -1264,45 +1453,50 @@ bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *scene,
 	obs_transform_info info;
 	obs_sceneitem_get_info(item, &info);
 
-	gs_load_vertexbuffer(main->circle);
-
 	gs_matrix_push();
 	gs_matrix_mul(&boxTransform);
 
-	DrawCircleAtPos(0.0f, 0.0f);
-	DrawCircleAtPos(0.0f, 1.0f);
-	DrawCircleAtPos(1.0f, 0.0f);
-	DrawCircleAtPos(1.0f, 1.0f);
-	DrawCircleAtPos(0.5f, 0.0f);
-	DrawCircleAtPos(0.0f, 0.5f);
-	DrawCircleAtPos(0.5f, 1.0f);
-	DrawCircleAtPos(1.0f, 0.5f);
-
 	obs_sceneitem_crop crop;
 	obs_sceneitem_get_crop(item, &crop);
 
+	gs_effect_t *eff = gs_get_effect();
+	gs_eparam_t *colParam = gs_effect_get_param_by_name(eff, "color");
+
 	if (info.bounds_type == OBS_BOUNDS_NONE && crop_enabled(&crop)) {
-		vec4 color;
-		gs_effect_t *eff = gs_get_effect();
-		gs_eparam_t *param = gs_effect_get_param_by_name(eff, "color");
-
-#define DRAW_SIDE(side, vb) \
-		if (crop.side > 0) \
-			vec4_set(&color, 0.0f, 1.0f, 0.0f, 1.0f); \
-		else \
-			vec4_set(&color, 1.0f, 0.0f, 0.0f, 1.0f); \
-		gs_effect_set_vec4(param, &color); \
-		gs_load_vertexbuffer(main->vb); \
-		gs_draw(GS_LINESTRIP, 0, 0);
-
-		DRAW_SIDE(left,   boxLeft);
-		DRAW_SIDE(top,    boxTop);
-		DRAW_SIDE(right,  boxRight);
-		DRAW_SIDE(bottom, boxBottom);
+#define DRAW_SIDE(side, x1, y1, x2, y2) \
+		if (hover && !selected) \
+			gs_effect_set_vec4(colParam, &blue); \
+		else if (crop.side > 0) \
+			gs_effect_set_vec4(colParam, &green); \
+		DrawLine(x1, y1, x2, y2, HANDLE_RADIUS / 2); \
+		gs_effect_set_vec4(colParam, &red);
+
+		DRAW_SIDE(left,   0.0f, 0.0f, 0.0f, 1.0f);
+		DRAW_SIDE(top,    0.0f, 0.0f, 1.0f, 0.0f);
+		DRAW_SIDE(right,  1.0f, 0.0f, 1.0f, 1.0f);
+		DRAW_SIDE(bottom, 0.0f, 1.0f, 1.0f, 1.0f);
 #undef DRAW_SIDE
 	} else {
-		gs_load_vertexbuffer(main->box);
-		gs_draw(GS_LINESTRIP, 0, 0);
+		if (!selected) {
+			gs_effect_set_vec4(colParam, &blue);
+			DrawRect(HANDLE_RADIUS / 2);
+		} else {
+			DrawRect(HANDLE_RADIUS / 2);
+		}
+	}
+
+	gs_load_vertexbuffer(main->box);
+	gs_effect_set_vec4(colParam, &red);
+
+	if (selected) {
+		DrawSquareAtPos(0.0f, 0.0f);
+		DrawSquareAtPos(0.0f, 1.0f);
+		DrawSquareAtPos(1.0f, 0.0f);
+		DrawSquareAtPos(1.0f, 1.0f);
+		DrawSquareAtPos(0.5f, 0.0f);
+		DrawSquareAtPos(0.0f, 0.5f);
+		DrawSquareAtPos(0.5f, 1.0f);
+		DrawSquareAtPos(1.0f, 0.5f);
 	}
 
 	gs_matrix_pop();
@@ -1312,6 +1506,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<OBSBasic*>(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)

+ 8 - 0
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;
@@ -54,10 +56,13 @@ private:
 	bool         locked         = false;
 	bool         scrollMode     = false;
 	bool         fixedScaling   = false;
+	OBSSceneItem hovered        = nullptr;
 	int32_t      scalingLevel   = 0;
 	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 +89,7 @@ private:
 
 public:
 	OBSBasicPreview(QWidget *parent, Qt::WindowFlags flags = 0);
+	~OBSBasicPreview();
 
 	virtual void keyPressEvent(QKeyEvent *event) override;
 	virtual void keyReleaseEvent(QKeyEvent *event) override;
@@ -93,7 +99,9 @@ public:
 	virtual void mousePressEvent(QMouseEvent *event) override;
 	virtual void mouseReleaseEvent(QMouseEvent *event) override;
 	virtual void mouseMoveEvent(QMouseEvent *event) override;
+	virtual void leaveEvent(QEvent *event) override;
 
+	void DrawOverflow();
 	void DrawSceneEditing();
 
 	inline void SetLocked(bool newLockedVal) {locked = newLockedVal;}

+ 27 - 0
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",

+ 55 - 0
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);
+	}
+}

+ 1 - 0
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;

+ 7 - 0
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:

+ 1 - 0
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 */