diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt
index 129351497..6e04bbd21 100644
--- a/UI/CMakeLists.txt
+++ b/UI/CMakeLists.txt
@@ -161,6 +161,7 @@ set(obs_SOURCES
adv-audio-control.cpp
item-widget-helpers.cpp
visibility-checkbox.cpp
+ locked-checkbox.cpp
vertical-scroll-area.cpp
visibility-item-widget.cpp
slider-absoluteset-style.cpp
@@ -208,6 +209,7 @@ set(obs_HEADERS
adv-audio-control.hpp
item-widget-helpers.hpp
visibility-checkbox.hpp
+ locked-checkbox.hpp
vertical-scroll-area.hpp
visibility-item-widget.hpp
slider-absoluteset-style.hpp
diff --git a/UI/forms/images/locked_mask.png b/UI/forms/images/locked_mask.png
new file mode 100644
index 000000000..282cac222
Binary files /dev/null and b/UI/forms/images/locked_mask.png differ
diff --git a/UI/forms/images/unlocked_mask.png b/UI/forms/images/unlocked_mask.png
new file mode 100644
index 000000000..c13e5622c
Binary files /dev/null and b/UI/forms/images/unlocked_mask.png differ
diff --git a/UI/forms/obs.qrc b/UI/forms/obs.qrc
index 361fef4ae..b6b6e86fa 100644
--- a/UI/forms/obs.qrc
+++ b/UI/forms/obs.qrc
@@ -14,6 +14,8 @@
images/up.png
images/obs.png
images/tray_active.png
+ images/locked_mask.png
+ images/unlocked_mask.png
images/settings/advanced.png
diff --git a/UI/locked-checkbox.cpp b/UI/locked-checkbox.cpp
new file mode 100644
index 000000000..91a049fac
--- /dev/null
+++ b/UI/locked-checkbox.cpp
@@ -0,0 +1,36 @@
+#include
+#include
+#include
+#include "locked-checkbox.hpp"
+
+#include
+
+LockedCheckBox::LockedCheckBox() : QCheckBox()
+{
+ lockedImage =
+ QPixmap::fromImage(QImage(":/res/images/locked_mask.png"));
+ unlockedImage =
+ QPixmap::fromImage(QImage(":/res/images/unlocked_mask.png"));
+ setMinimumSize(16, 16);
+
+ setStyleSheet("outline: none;");
+}
+
+void LockedCheckBox::paintEvent(QPaintEvent *event)
+{
+ UNUSED_PARAMETER(event);
+
+ QPixmap &pixmap = isChecked() ? lockedImage : unlockedImage;
+ QImage image(pixmap.size(), QImage::Format_ARGB32);
+
+ QPainter draw(&image);
+ draw.setCompositionMode(QPainter::CompositionMode_Source);
+ draw.drawPixmap(0, 0, pixmap.width(), pixmap.height(), pixmap);
+ draw.setCompositionMode(QPainter::CompositionMode_SourceIn);
+ draw.fillRect(QRectF(QPointF(0.0f, 0.0f), pixmap.size()),
+ palette().color(foregroundRole()));
+
+ QPainter p(this);
+ p.drawPixmap(0, 0, image.width(), image.height(),
+ QPixmap::fromImage(image));
+}
diff --git a/UI/locked-checkbox.hpp b/UI/locked-checkbox.hpp
new file mode 100644
index 000000000..017470f72
--- /dev/null
+++ b/UI/locked-checkbox.hpp
@@ -0,0 +1,17 @@
+#include
+#include
+
+class QPaintEvernt;
+
+class LockedCheckBox : public QCheckBox {
+ Q_OBJECT
+
+ QPixmap lockedImage;
+ QPixmap unlockedImage;
+
+public:
+ LockedCheckBox();
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+};
diff --git a/UI/visibility-item-widget.cpp b/UI/visibility-item-widget.cpp
index f42d193df..c856a781b 100644
--- a/UI/visibility-item-widget.cpp
+++ b/UI/visibility-item-widget.cpp
@@ -1,5 +1,6 @@
#include "visibility-item-widget.hpp"
#include "visibility-checkbox.hpp"
+#include "locked-checkbox.hpp"
#include "qt-wrappers.hpp"
#include "obs-app.hpp"
#include
@@ -49,6 +50,7 @@ VisibilityItemWidget::VisibilityItemWidget(obs_sceneitem_t *item_)
{
const char *name = obs_source_get_name(source);
bool enabled = obs_sceneitem_visible(item);
+ bool locked = obs_sceneitem_locked(item);
obs_scene_t *scene = obs_sceneitem_get_scene(item);
obs_source_t *sceneSource = obs_scene_get_source(scene);
@@ -60,13 +62,23 @@ VisibilityItemWidget::VisibilityItemWidget(obs_sceneitem_t *item_)
#endif
vis->setChecked(enabled);
+ lock = new LockedCheckBox();
+ lock->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
+ /* Fix for non-apple systems where the spacing would be too big */
+#ifndef __APPLE__
+ lock->setMaximumSize(16, 16);
+#endif
+ lock->setChecked(locked);
+
label = new QLabel(QT_UTF8(name));
label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
QHBoxLayout *itemLayout = new QHBoxLayout();
itemLayout->addWidget(vis);
+ itemLayout->addWidget(lock);
itemLayout->addWidget(label);
itemLayout->setContentsMargins(5, 2, 5, 2);
+ itemLayout->setSpacing(2);
setLayout(itemLayout);
setStyleSheet("background-color: rgba(255, 255, 255, 0);");
@@ -80,6 +92,9 @@ VisibilityItemWidget::VisibilityItemWidget(obs_sceneitem_t *item_)
connect(vis, SIGNAL(clicked(bool)),
this, SLOT(VisibilityClicked(bool)));
+
+ connect(lock, SIGNAL(clicked(bool)),
+ this, SLOT(LockClicked(bool)));
}
VisibilityItemWidget::~VisibilityItemWidget()
@@ -137,6 +152,18 @@ void VisibilityItemWidget::OBSSceneItemVisible(void *param, calldata_t *data)
Q_ARG(bool, enabled));
}
+void VisibilityItemWidget::OBSSceneItemLocked(void *param, calldata_t *data)
+{
+ VisibilityItemWidget *window =
+ reinterpret_cast(param);
+ obs_sceneitem_t *curItem = (obs_sceneitem_t*)calldata_ptr(data, "item");
+ bool locked = calldata_bool(data, "locked");
+
+ if (window->item == curItem)
+ QMetaObject::invokeMethod(window, "SourceLocked",
+ Q_ARG(bool, locked));
+}
+
void VisibilityItemWidget::OBSSourceEnabled(void *param, calldata_t *data)
{
VisibilityItemWidget *window =
@@ -165,12 +192,24 @@ void VisibilityItemWidget::VisibilityClicked(bool visible)
obs_source_set_enabled(source, visible);
}
+void VisibilityItemWidget::LockClicked(bool locked)
+{
+ if (item)
+ obs_sceneitem_set_locked(item, locked);
+}
+
void VisibilityItemWidget::SourceEnabled(bool enabled)
{
if (vis->isChecked() != enabled)
vis->setChecked(enabled);
}
+void VisibilityItemWidget::SourceLocked(bool locked)
+{
+ if (lock->isChecked() != locked)
+ lock->setChecked(locked);
+}
+
void VisibilityItemWidget::SourceRenamed(QString name)
{
if (label && name != label->text())
diff --git a/UI/visibility-item-widget.hpp b/UI/visibility-item-widget.hpp
index 439cfd373..80c46db5d 100644
--- a/UI/visibility-item-widget.hpp
+++ b/UI/visibility-item-widget.hpp
@@ -9,6 +9,7 @@ class QLineEdit;
class QListWidget;
class QListWidgetItem;
class VisibilityCheckBox;
+class LockedCheckBox;
class VisibilityItemWidget : public QWidget {
Q_OBJECT
@@ -18,6 +19,7 @@ private:
OBSSource source;
QLabel *label = nullptr;
VisibilityCheckBox *vis = nullptr;
+ LockedCheckBox *lock = nullptr;
QString oldName;
OBSSignal sceneRemoveSignal;
@@ -31,6 +33,7 @@ private:
static void OBSSceneRemove(void *param, calldata_t *data);
static void OBSSceneItemRemove(void *param, calldata_t *data);
static void OBSSceneItemVisible(void *param, calldata_t *data);
+ static void OBSSceneItemLocked(void *param, calldata_t *data);
static void OBSSourceEnabled(void *param, calldata_t *data);
static void OBSSourceRenamed(void *param, calldata_t *data);
@@ -38,8 +41,10 @@ private:
private slots:
void VisibilityClicked(bool visible);
+ void LockClicked(bool locked);
void SourceEnabled(bool enabled);
void SourceRenamed(QString name);
+ void SourceLocked(bool locked);
public:
VisibilityItemWidget(obs_source_t *source);
diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp
index e328d4a12..6ee2d4a83 100644
--- a/UI/window-basic-main.cpp
+++ b/UI/window-basic-main.cpp
@@ -5004,6 +5004,9 @@ void OBSBasic::Nudge(int dist, MoveDir dir)
auto func = [] (obs_scene_t*, obs_sceneitem_t *item, void *param)
{
+ if (obs_sceneitem_locked(item))
+ return true;
+
MoveInfo *info = reinterpret_cast(param);
struct vec2 dir;
struct vec2 pos;
diff --git a/UI/window-basic-preview.cpp b/UI/window-basic-preview.cpp
index 6cba59bba..43213f970 100644
--- a/UI/window-basic-preview.cpp
+++ b/UI/window-basic-preview.cpp
@@ -75,6 +75,8 @@ static bool FindItemAtPos(obs_scene_t *scene, obs_sceneitem_t *item,
if (!SceneItemHasVideo(item))
return true;
+ if (obs_sceneitem_locked(item))
+ return true;
vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f);
@@ -674,6 +676,9 @@ void OBSBasicPreview::SnapItemMovement(vec2 &offset)
static bool move_items(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
{
+ if (obs_sceneitem_locked(item))
+ return true;
+
vec2 *offset = reinterpret_cast(param);
if (obs_sceneitem_selected(item)) {
@@ -1084,6 +1089,9 @@ static inline bool crop_enabled(const obs_sceneitem_crop *crop)
bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *scene,
obs_sceneitem_t *item, void *param)
{
+ if (obs_sceneitem_locked(item))
+ return true;
+
if (!obs_sceneitem_selected(item))
return true;
@@ -1183,6 +1191,7 @@ void OBSBasicPreview::DrawSceneEditing()
gs_technique_begin_pass(tech, 0);
OBSScene scene = main->GetCurrentScene();
+
if (scene)
obs_scene_enum_items(scene, DrawSelectedItem, this);
diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c
index 32940215b..dc6b4d858 100644
--- a/libobs/obs-scene.c
+++ b/libobs/obs-scene.c
@@ -590,6 +590,7 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data)
const char *scale_filter_str;
struct obs_scene_item *item;
bool visible;
+ bool lock;
if (!source) {
blog(LOG_WARNING, "[scene_load_item] Source %s not found!",
@@ -616,10 +617,12 @@ static void scene_load_item(struct obs_scene *scene, obs_data_t *item_data)
item->rot = (float)obs_data_get_double(item_data, "rot");
item->align = (uint32_t)obs_data_get_int(item_data, "align");
visible = obs_data_get_bool(item_data, "visible");
+ lock = obs_data_get_bool(item_data, "locked");
obs_data_get_vec2(item_data, "pos", &item->pos);
obs_data_get_vec2(item_data, "scale", &item->scale);
set_visibility(item, visible);
+ obs_sceneitem_set_locked(item, lock);
item->bounds_type =
(enum obs_bounds_type)obs_data_get_int(item_data,
@@ -697,6 +700,7 @@ static void scene_save_item(obs_data_array_t *array,
obs_data_set_string(item_data, "name", name);
obs_data_set_bool (item_data, "visible", item->user_visible);
+ obs_data_set_bool (item_data, "locked", item->locked);
obs_data_set_double(item_data, "rot", item->rot);
obs_data_set_vec2 (item_data, "pos", &item->pos);
obs_data_set_vec2 (item_data, "scale", &item->scale);
@@ -1347,6 +1351,7 @@ obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source)
item->align = OBS_ALIGN_TOP | OBS_ALIGN_LEFT;
item->actions_mutex = mutex;
item->user_visible = true;
+ item->locked = false;
os_atomic_set_long(&item->active_refs, 1);
vec2_set(&item->scale, 1.0f, 1.0f);
matrix4_identity(&item->draw_transform);
@@ -1773,6 +1778,27 @@ bool obs_sceneitem_set_visible(obs_sceneitem_t *item, bool visible)
return true;
}
+bool obs_sceneitem_locked(const obs_sceneitem_t *item)
+{
+ return item ? item->locked : false;
+}
+
+bool obs_sceneitem_set_locked(obs_sceneitem_t *item, bool lock)
+{
+ if (!item)
+ return false;
+
+ if (item->locked == lock)
+ return false;
+
+ if (!item->parent)
+ return false;
+
+ item->locked = lock;
+
+ return true;
+}
+
static bool sceneitems_match(obs_scene_t *scene, obs_sceneitem_t * const *items,
size_t size, bool *order_matches)
{
diff --git a/libobs/obs-scene.h b/libobs/obs-scene.h
index d46022123..530334857 100644
--- a/libobs/obs-scene.h
+++ b/libobs/obs-scene.h
@@ -41,6 +41,7 @@ struct obs_scene_item {
bool user_visible;
bool visible;
bool selected;
+ bool locked;
gs_texrender_t *item_render;
struct obs_sceneitem_crop crop;
diff --git a/libobs/obs.h b/libobs/obs.h
index f9a55404b..cd4d2efd1 100644
--- a/libobs/obs.h
+++ b/libobs/obs.h
@@ -1251,8 +1251,12 @@ EXPORT obs_scene_t *obs_sceneitem_get_scene(const obs_sceneitem_t *item);
/** Gets the source of a scene item. */
EXPORT obs_source_t *obs_sceneitem_get_source(const obs_sceneitem_t *item);
+/* FIXME: The following functions should be deprecated and replaced with a way
+ * to specify savable private user data. -Jim */
EXPORT void obs_sceneitem_select(obs_sceneitem_t *item, bool select);
EXPORT bool obs_sceneitem_selected(const obs_sceneitem_t *item);
+EXPORT bool obs_sceneitem_locked(const obs_sceneitem_t *item);
+EXPORT bool obs_sceneitem_set_locked(obs_sceneitem_t *item, bool lock);
/* Functions for getting/setting specific orientation of a scene item */
EXPORT void obs_sceneitem_set_pos(obs_sceneitem_t *item, const struct vec2 *pos);