UI: Add grouping

master
jp9000 2018-06-02 09:45:01 -07:00
parent 46cce067f0
commit 88b6c63964
19 changed files with 1769 additions and 245 deletions

View File

@ -152,6 +152,7 @@ set(obs_SOURCES
window-log-reply.cpp
window-projector.cpp
window-remux.cpp
source-tree.cpp
properties-view.cpp
focus-list.cpp
menu-button.cpp
@ -198,6 +199,7 @@ set(obs_HEADERS
window-log-reply.hpp
window-projector.hpp
window-remux.hpp
source-tree.hpp
properties-view.hpp
properties-view.moc.hpp
display-helpers.hpp
@ -211,6 +213,7 @@ set(obs_HEADERS
visibility-checkbox.hpp
locked-checkbox.hpp
horizontal-scroll-area.hpp
expand-checkbox.hpp
vertical-scroll-area.hpp
visibility-item-widget.hpp
slider-absoluteset-style.hpp

View File

@ -84,6 +84,7 @@ StudioMode.Preview="Preview"
StudioMode.Program="Program"
ShowInMultiview="Show in Multiview"
VerticalLayout="Vertical Layout"
Group="Group"
# warning if program already open
AlreadyRunning.Title="OBS is already running"
@ -458,6 +459,9 @@ Basic.Main.StoppingReplayBuffer="Stopping Replay Buffer..."
Basic.Main.StopStreaming="Stop Streaming"
Basic.Main.StoppingStreaming="Stopping Stream..."
Basic.Main.ForceStopStreaming="Stop Streaming (discard delay)"
Basic.Main.Group="Group %1"
Basic.Main.GroupItems="Group Selected Items"
Basic.Main.Ungroup="Ungroup"
# basic mode file menu
Basic.MainMenu.File="&File"

View File

@ -94,7 +94,8 @@ QMenuBar::item:selected {
}
/* Listbox item */
QListWidget::item {
QListWidget::item,
SourceTree::item {
padding: 4px 2px;
margin-bottom: 2px;
margin-top: 0px;
@ -110,11 +111,6 @@ QListWidget QLineEdit {
border-radius: none;
}
SourceListWidget::item {
margin-bottom: 1px;
padding: -4px 2px;
}
/* Dock stuff */
QDockWidget {
background: transparent;
@ -157,6 +153,23 @@ SourceListWidget {
border-bottom: 2px solid #2f2f2f;
}
SourceTree {
border: none;
border-bottom: 1px solid #2f2f2f;
}
SourceTree QLabel {
padding: 2px 0px;
margin: -2px 4px -2px;
}
SourceTree QLineEdit {
background-color: #0c101e;
padding: 2px;
margin: -2px 6px -2px 3px;
font-size: 12px;
}
#scenesFrame,
#sourcesFrame {
margin-left: -7px;
@ -179,13 +192,15 @@ SourceListWidget {
}
/* Listbox item selected, unfocused */
QListWidget::item:hover {
QListWidget::item:hover,
SourceTree::item:hover {
background-color: #212121;
border: 1px solid #333336;
}
/* Listbox item selected */
QListWidget::item:selected {
QListWidget::item:selected,
SourceTree::item:selected {
background-color: #131a30;
border: 1px solid #252a45;
}
@ -727,6 +742,30 @@ OBSHotkeyLabel[hotkeyPairHover=true] {
}
/* Group Collapse Checkbox */
SourceTreeSubItemCheckBox {
background: transparent;
outline: none;
padding: 0px;
}
SourceTreeSubItemCheckBox::indicator {
width: 12px;
height: 12px;
}
SourceTreeSubItemCheckBox::indicator:checked,
SourceTreeSubItemCheckBox::indicator:checked:hover {
image: url(./Dark/expand.png);
}
SourceTreeSubItemCheckBox::indicator:unchecked,
SourceTreeSubItemCheckBox::indicator:unchecked:hover {
image: url(./Dark/collapse.png);
}
/* Label warning/error */
QLabel#warningLabel {
@ -753,10 +792,6 @@ OBSBasicProperties,
background: #101010;
}
#OBSBasicSourceSelect #sourceList {
border-bottom: 2px solid #333336;
}
FocusList::item {
padding: 0px 2px;
}

View File

@ -551,6 +551,27 @@ OBSHotkeyLabel[hotkeyPairHover=true] {
}
/* Group Collapse Checkbox */
SourceTreeSubItemCheckBox {
background: transparent;
outline: none;
}
SourceTreeSubItemCheckBox::indicator {
width: 10px;
height: 10px;
}
SourceTreeSubItemCheckBox::indicator:checked {
image: url(./Dark/expand.png);
}
SourceTreeSubItemCheckBox::indicator:unchecked {
image: url(./Dark/collapse.png);
}
/* Label warning/error */
QLabel#warningLabel {

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -51,6 +51,24 @@ MuteCheckBox::indicator:unchecked {
image: url(:/res/images/unmute.png);
}
SourceTreeSubItemCheckBox {
background: transparent;
outline: none;
}
SourceTreeSubItemCheckBox::indicator {
width: 10px;
height: 10px;
}
SourceTreeSubItemCheckBox::indicator:checked {
image: url(:/res/images/expand.png);
}
SourceTreeSubItemCheckBox::indicator:unchecked {
image: url(:/res/images/collapse.png);
}
OBSHotkeyLabel[hotkeyPairHover=true] {
color: red;
}

View File

@ -701,6 +701,30 @@ MuteCheckBox::indicator:unchecked:disabled {
image: url(./Dark/unmute.png);
}
/****************************/
/* --- Group Checkboxes --- */
/****************************/
SourceTreeSubItemCheckBox {
background: transparent;
outline: none;
}
SourceTreeSubItemCheckBox::indicator {
width: 10px;
height: 10px;
}
SourceTreeSubItemCheckBox::indicator:checked,
SourceTreeSubItemCheckBox::indicator:checked:hover {
image: url(./Dark/expand.png);
}
SourceTreeSubItemCheckBox::indicator:unchecked,
SourceTreeSubItemCheckBox::indicator:unchecked:hover {
image: url(./Dark/collapse.png);
}
/*************************/
/* --- Progress bars --- */
/*************************/

5
UI/expand-checkbox.hpp Normal file
View File

@ -0,0 +1,5 @@
#include <QCheckBox>
class ExpandCheckBox : public QCheckBox {
Q_OBJECT
};

View File

@ -518,7 +518,7 @@
<number>0</number>
</property>
<item>
<widget class="SourceListWidget" name="sources">
<widget class="SourceTree" name="sources">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -1682,9 +1682,9 @@
<container>1</container>
</customwidget>
<customwidget>
<class>SourceListWidget</class>
<extends>QListWidget</extends>
<header>source-list-widget.hpp</header>
<class>SourceTree</class>
<extends>QListView</extends>
<header>source-tree.hpp</header>
</customwidget>
</customwidgets>
<resources>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
UI/forms/images/expand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -17,6 +17,8 @@
<file>images/tray_active.png</file>
<file>images/locked_mask.png</file>
<file>images/unlocked_mask.png</file>
<file>images/collapse.png</file>
<file>images/expand.png</file>
</qresource>
<qresource prefix="/settings">
<file>images/settings/advanced.png</file>

1286
UI/source-tree.cpp Normal file

File diff suppressed because it is too large Load Diff

171
UI/source-tree.hpp Normal file
View File

@ -0,0 +1,171 @@
#pragma once
#include <QList>
#include <QVector>
#include <QPointer>
#include <QListView>
#include <QCheckBox>
#include <QAbstractListModel>
class QLabel;
class QCheckBox;
class QLineEdit;
class SourceTree;
class QSpacerItem;
class QHBoxLayout;
class LockedCheckBox;
class VisibilityCheckBox;
class VisibilityItemWidget;
class SourceTreeSubItemCheckBox : public QCheckBox {
Q_OBJECT
};
class SourceTreeItem : public QWidget {
Q_OBJECT
friend class SourceTree;
friend class SourceTreeModel;
void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual bool eventFilter(QObject *object, QEvent *event) override;
void Update(bool force);
enum class Type {
Unknown,
Item,
Group,
SubItem,
};
void DisconnectSignals();
void ReconnectSignals();
Type type = Type::Unknown;
public:
explicit SourceTreeItem(SourceTree *tree, OBSSceneItem sceneitem);
private:
QSpacerItem *spacer = nullptr;
QCheckBox *expand = nullptr;
VisibilityCheckBox *vis = nullptr;
LockedCheckBox *lock = nullptr;
QHBoxLayout *boxLayout = nullptr;
QLabel *label = nullptr;
QLineEdit *editor = nullptr;
SourceTree *tree;
OBSSceneItem sceneitem;
OBSSignal sceneRemoveSignal;
OBSSignal itemRemoveSignal;
OBSSignal visibleSignal;
OBSSignal renameSignal;
OBSSignal removeSignal;
private slots:
void EnterEditMode();
void ExitEditMode(bool save);
void VisibilityChanged(bool visible);
void Renamed(const QString &name);
void ExpandClicked(bool checked);
};
class SourceTreeModel : public QAbstractListModel {
Q_OBJECT
friend class SourceTree;
friend class SourceTreeItem;
SourceTree *st;
QVector<OBSSceneItem> items;
bool hasGroups = false;
static void OBSFrontendEvent(enum obs_frontend_event event, void *ptr);
void Clear();
void SceneChanged();
void ReorderItems();
void Add(obs_sceneitem_t *item);
void Remove(obs_sceneitem_t *item);
OBSSceneItem Get(int idx);
QString GetNewGroupName();
void AddGroup();
void GroupSelectedItems(QModelIndexList &indices);
void UngroupSelectedGroups(QModelIndexList &indices);
void ExpandGroup(obs_sceneitem_t *item);
void CollapseGroup(obs_sceneitem_t *item);
void UpdateGroupState(bool update);
public:
explicit SourceTreeModel(SourceTree *st);
~SourceTreeModel();
virtual int rowCount(const QModelIndex &parent) const override;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
virtual Qt::DropActions supportedDropActions() const override;
};
class SourceTree : public QListView {
Q_OBJECT
bool ignoreReorder = false;
friend class SourceTreeModel;
friend class SourceTreeItem;
void ResetWidgets();
void UpdateWidget(const QModelIndex &idx, obs_sceneitem_t *item);
void UpdateWidgets(bool force = false);
inline SourceTreeModel *GetStm() const
{
return reinterpret_cast<SourceTreeModel *>(model());
}
inline SourceTreeItem *GetItemWidget(int idx)
{
QWidget *widget = indexWidget(GetStm()->createIndex(idx, 0));
return reinterpret_cast<SourceTreeItem *>(widget);
}
public:
explicit SourceTree(QWidget *parent = nullptr);
inline bool IgnoreReorder() const {return ignoreReorder;}
inline void ReorderItems() {GetStm()->ReorderItems();}
inline void Clear() {GetStm()->Clear();}
inline void Add(obs_sceneitem_t *item) {GetStm()->Add(item);}
inline void Remove(obs_sceneitem_t *item) {GetStm()->Remove(item);}
inline OBSSceneItem Get(int idx) {return GetStm()->Get(idx);}
inline QString GetNewGroupName() {return GetStm()->GetNewGroupName();}
void SelectItem(obs_sceneitem_t *sceneitem, bool select);
bool MultipleBaseSelected() const;
bool GroupsSelected() const;
bool GroupedItemsSelected() const;
public slots:
void GroupSelectedItems();
void UngroupSelectedGroups();
void AddGroup();
void Edit(int idx);
protected:
virtual void mouseDoubleClickEvent(QMouseEvent *event) override;
virtual void dropEvent(QDropEvent *event) override;
virtual void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
};

View File

@ -54,6 +54,7 @@
#include "display-helpers.hpp"
#include "volume-control.hpp"
#include "remote-text.hpp"
#include "source-tree.hpp"
#ifdef _WIN32
#include "win-update/win-update.hpp"
@ -148,8 +149,6 @@ OBSBasic::OBSBasic(QWidget *parent)
copyActionsDynamicProperties();
ui->sources->setItemDelegate(new VisibilityItemDelegate(ui->sources));
char styleSheetPath[512];
int ret = GetProfilePath(styleSheetPath, sizeof(styleSheetPath),
"stylesheet.qss");
@ -201,13 +200,6 @@ OBSBasic::OBSBasic(QWidget *parent)
SLOT(SceneNameEdited(QWidget*,
QAbstractItemDelegate::EndEditHint)));
connect(ui->sources->itemDelegate(),
SIGNAL(closeEditor(QWidget*,
QAbstractItemDelegate::EndEditHint)),
this,
SLOT(SceneItemNameEdited(QWidget*,
QAbstractItemDelegate::EndEditHint)));
cpuUsageInfo = os_cpu_usage_info_start();
cpuUsageTimer = new QTimer(this);
connect(cpuUsageTimer, SIGNAL(timeout()),
@ -2024,7 +2016,7 @@ OBSSceneItem OBSBasic::GetSceneItem(QListWidgetItem *item)
OBSSceneItem OBSBasic::GetCurrentSceneItem()
{
return GetSceneItem(GetTopSelectedSourceItem());
return ui->sources->Get(GetTopSelectedSourceItem());
}
void OBSBasic::UpdatePreviewScalingMenu()
@ -2047,32 +2039,6 @@ void OBSBasic::UpdatePreviewScalingMenu()
scalingAmount == float(ovi.output_width) / float(ovi.base_width));
}
void OBSBasic::UpdateSources(OBSScene scene)
{
ClearListItems(ui->sources);
obs_scene_enum_items(scene,
[] (obs_scene_t *scene, obs_sceneitem_t *item, void *p)
{
OBSBasic *window = static_cast<OBSBasic*>(p);
window->InsertSceneItem(item);
UNUSED_PARAMETER(scene);
return true;
}, this);
}
void OBSBasic::InsertSceneItem(obs_sceneitem_t *item)
{
QListWidgetItem *listItem = new QListWidgetItem();
SetOBSRef(listItem, OBSSceneItem(item));
ui->sources->insertItem(0, listItem);
ui->sources->setCurrentRow(0, QItemSelectionModel::ClearAndSelect);
SetupVisibilityItem(ui->sources, listItem, item);
}
void OBSBasic::CreateInteractionWindow(obs_source_t *source)
{
if (interaction)
@ -2199,7 +2165,7 @@ void OBSBasic::RemoveScene(OBSSource source)
if (sel != nullptr) {
if (sel == ui->scenes->currentItem())
ClearListItems(ui->sources);
ui->sources->Clear();
delete sel;
}
@ -2221,7 +2187,7 @@ void OBSBasic::AddSceneItem(OBSSceneItem item)
obs_scene_t *scene = obs_sceneitem_get_scene(item);
if (GetCurrentScene() == scene)
InsertSceneItem(item);
ui->sources->Add(item);
SaveProject();
@ -2237,14 +2203,7 @@ void OBSBasic::AddSceneItem(OBSSceneItem item)
void OBSBasic::RemoveSceneItem(OBSSceneItem item)
{
for (int i = 0; i < ui->sources->count(); i++) {
QListWidgetItem *listItem = ui->sources->item(i);
if (GetOBSRef<OBSSceneItem>(listItem) == item) {
DeleteListItem(ui->sources, listItem);
break;
}
}
ui->sources->Remove(item);
SaveProject();
@ -2276,8 +2235,6 @@ void OBSBasic::UpdateSceneSelection(OBSSource source)
ui->scenes->setCurrentItem(items.first());
sceneChanging = false;
UpdateSources(scene);
OBSScene curScene =
GetOBSRef<OBSScene>(ui->scenes->currentItem());
if (api && scene != curScene)
@ -2322,19 +2279,7 @@ void OBSBasic::SelectSceneItem(OBSScene scene, OBSSceneItem item, bool select)
if (scene != GetCurrentScene() || ignoreSelectionUpdate)
return;
for (int i = 0; i < ui->sources->count(); i++) {
QListWidgetItem *witem = ui->sources->item(i);
QVariant data =
witem->data(static_cast<int>(QtDataRole::OBSRef));
if (!data.canConvert<OBSSceneItem>())
continue;
if (item != data.value<OBSSceneItem>())
continue;
witem->setSelected(select);
break;
}
ui->sources->SelectItem(item, select);
}
static inline bool SourceMixerHidden(obs_source_t *source)
@ -2664,7 +2609,8 @@ void OBSBasic::DeactivateAudioSource(OBSSource source)
bool OBSBasic::QueryRemoveSource(obs_source_t *source)
{
if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE) {
if (obs_source_get_type(source) == OBS_SOURCE_TYPE_SCENE &&
!obs_sceneitem_group_from_source(source)) {
int count = ui->scenes->count();
if (count == 1) {
@ -2830,62 +2776,12 @@ void OBSBasic::RemoveSelectedSceneItem()
}
}
struct ReorderInfo {
int idx = 0;
OBSBasic *window;
inline ReorderInfo(OBSBasic *window_) : window(window_) {}
};
void OBSBasic::ReorderSceneItem(obs_sceneitem_t *item, size_t idx)
{
int count = ui->sources->count();
int idx_inv = count - (int)idx - 1;
for (int i = 0; i < count; i++) {
QListWidgetItem *listItem = ui->sources->item(i);
OBSSceneItem sceneItem = GetOBSRef<OBSSceneItem>(listItem);
if (sceneItem == item) {
if ((int)idx_inv != i) {
bool sel = (ui->sources->currentRow() == i);
listItem = TakeListItem(ui->sources, i);
if (listItem) {
ui->sources->insertItem(idx_inv,
listItem);
SetupVisibilityItem(ui->sources,
listItem, item);
if (sel)
ui->sources->setCurrentRow(
idx_inv);
}
}
break;
}
}
}
void OBSBasic::ReorderSources(OBSScene scene)
{
ReorderInfo info(this);
if (scene != GetCurrentScene() || ui->sources->IgnoreReorder())
return;
obs_scene_enum_items(scene,
[] (obs_scene_t*, obs_sceneitem_t *item, void *p)
{
ReorderInfo *info =
reinterpret_cast<ReorderInfo*>(p);
info->window->ReorderSceneItem(item,
info->idx++);
return true;
}, &info);
ui->sources->ReorderItems();
SaveProject();
}
@ -3410,7 +3306,7 @@ void OBSBasic::ClearSceneData()
ClearVolumeControls();
ClearListItems(ui->scenes);
ClearListItems(ui->sources);
ui->sources->Clear();
ClearQuickTransitions();
ui->transitions->clear();
@ -3800,44 +3696,10 @@ void OBSBasic::MoveSceneToBottom()
ui->scenes->count() - 1);
}
void OBSBasic::on_sources_itemSelectionChanged()
{
SignalBlocker sourcesSignalBlocker(ui->sources);
auto updateItemSelection = [&]()
{
ignoreSelectionUpdate = true;
for (int i = 0; i < ui->sources->count(); i++)
{
QListWidgetItem *wItem = ui->sources->item(i);
OBSSceneItem item = GetOBSRef<OBSSceneItem>(wItem);
obs_sceneitem_select(item, wItem->isSelected());
}
ignoreSelectionUpdate = false;
};
using updateItemSelection_t = decltype(updateItemSelection);
obs_scene_atomic_update(GetCurrentScene(),
[](void *data, obs_scene_t *)
{
(*static_cast<updateItemSelection_t*>(data))();
}, static_cast<void*>(&updateItemSelection));
}
void OBSBasic::EditSceneItemName()
{
QListWidgetItem *item = GetTopSelectedSourceItem();
Qt::ItemFlags flags = item->flags();
OBSSceneItem sceneItem= GetOBSRef<OBSSceneItem>(item);
obs_source_t *source = obs_sceneitem_get_source(sceneItem);
const char *name = obs_source_get_name(source);
item->setText(QT_UTF8(name));
item->setFlags(flags | Qt::ItemIsEditable);
ui->sources->removeItemWidget(item);
ui->sources->editItem(item);
item->setFlags(flags);
int idx = GetTopSelectedSourceItem();
ui->sources->Edit(idx);
}
void OBSBasic::SetDeinterlacingMode()
@ -3937,7 +3799,7 @@ QMenu *OBSBasic::AddScaleFilteringMenu(obs_sceneitem_t *item)
return menu;
}
void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview)
void OBSBasic::CreateSourcePopupMenu(int idx, bool preview)
{
QMenu popup(this);
QPointer<QMenu> previewProjector;
@ -3978,6 +3840,17 @@ void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview)
ui->actionCopyFilters->setEnabled(false);
ui->actionCopySource->setEnabled(false);
if (ui->sources->MultipleBaseSelected()) {
popup.addSeparator();
popup.addAction(QTStr("Basic.Main.GroupItems"),
ui->sources, SLOT(GroupSelectedItems()));
} else if (ui->sources->GroupsSelected()) {
popup.addSeparator();
popup.addAction(QTStr("Basic.Main.Ungroup"),
ui->sources, SLOT(UngroupSelectedGroups()));
}
popup.addSeparator();
popup.addAction(ui->actionCopySource);
popup.addAction(ui->actionPasteRef);
@ -3989,11 +3862,11 @@ void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview)
popup.addAction(ui->actionPasteFilters);
popup.addSeparator();
if (item) {
if (idx != -1) {
if (addSourceMenu)
popup.addSeparator();
OBSSceneItem sceneItem = GetSceneItem(item);
OBSSceneItem sceneItem = ui->sources->Get(idx);
obs_source_t *source = obs_sceneitem_get_source(sceneItem);
uint32_t flags = obs_source_get_output_flags(source);
bool isAsyncVideo = (flags & OBS_SOURCE_ASYNC_VIDEO) ==
@ -4064,20 +3937,10 @@ void OBSBasic::CreateSourcePopupMenu(QListWidgetItem *item, bool preview)
void OBSBasic::on_sources_customContextMenuRequested(const QPoint &pos)
{
if (ui->scenes->count())
CreateSourcePopupMenu(ui->sources->itemAt(pos), false);
}
void OBSBasic::on_sources_itemDoubleClicked(QListWidgetItem *witem)
{
if (!witem)
return;
OBSSceneItem item = GetSceneItem(witem);
OBSSource source = obs_sceneitem_get_source(item);
if (source)
CreatePropertiesWindow(source);
if (ui->scenes->count()) {
QModelIndex idx = ui->sources->indexAt(pos);
CreateSourcePopupMenu(idx.row(), false);
}
}
void OBSBasic::on_scenes_itemDoubleClicked(QListWidgetItem *witem)
@ -4161,6 +4024,12 @@ QMenu *OBSBasic::CreateAddSourcePopupMenu()
addSource(popup, "scene", Str("Basic.Scene"));
popup->addSeparator();
QAction *addGroup = new QAction(QTStr("Group"), this);
connect(addGroup, SIGNAL(triggered(bool)),
ui->sources, SLOT(AddGroup()));
popup->addAction(addGroup);
if (!foundDeprecated) {
delete deprecated;
deprecated = nullptr;
@ -4171,6 +4040,7 @@ QMenu *OBSBasic::CreateAddSourcePopupMenu()
popup = nullptr;
} else if (foundDeprecated) {
popup->addSeparator();
popup->addMenu(deprecated);
}
@ -4206,20 +4076,24 @@ void OBSBasic::on_actionAddSource_triggered()
AddSourcePopupMenu(QCursor::pos());
}
static bool remove_items(obs_scene_t *, obs_sceneitem_t *item, void *param)
{
vector<OBSSceneItem> &items =
*reinterpret_cast<vector<OBSSceneItem>*>(param);
if (obs_sceneitem_selected(item)) {
items.emplace_back(item);
} else if (obs_sceneitem_is_group(item)) {
obs_sceneitem_group_enum_items(item, remove_items, &items);
}
return true;
};
void OBSBasic::on_actionRemoveSource_triggered()
{
vector<OBSSceneItem> items;
auto func = [] (obs_scene_t *, obs_sceneitem_t *item, void *param)
{
vector<OBSSceneItem> &items =
*reinterpret_cast<vector<OBSSceneItem>*>(param);
if (obs_sceneitem_selected(item))
items.emplace_back(item);
return true;
};
obs_scene_enum_items(GetCurrentScene(), func, &items);
obs_scene_enum_items(GetCurrentScene(), remove_items, &items);
if (!items.size())
return;
@ -4484,26 +4358,6 @@ void OBSBasic::SceneNameEdited(QWidget *editor,
UNUSED_PARAMETER(endHint);
}
void OBSBasic::SceneItemNameEdited(QWidget *editor,
QAbstractItemDelegate::EndEditHint endHint)
{
OBSSceneItem item = GetCurrentSceneItem();
QLineEdit *edit = qobject_cast<QLineEdit*>(editor);
string text = QT_TO_UTF8(edit->text().trimmed());
if (!item)
return;
obs_source_t *source = obs_sceneitem_get_source(item);
RenameListItem(this, ui->sources, source, text);
QListWidgetItem *listItem = ui->sources->currentItem();
listItem->setText(QString());
SetupVisibilityItem(ui->sources, listItem, item);
UNUSED_PARAMETER(endHint);
}
void OBSBasic::OpenFilters()
{
OBSSceneItem item = GetCurrentSceneItem();
@ -5150,13 +5004,11 @@ void OBSBasic::on_actionShowProfileFolder_triggered()
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
}
QListWidgetItem *OBSBasic::GetTopSelectedSourceItem()
int OBSBasic::GetTopSelectedSourceItem()
{
QList<QListWidgetItem*> selectedItems = ui->sources->selectedItems();
QListWidgetItem *topItem = nullptr;
if (selectedItems.size() != 0)
topItem = selectedItems[0];
return topItem;
QModelIndexList selectedItems =
ui->sources->selectionModel()->selectedIndexes();
return selectedItems.count() ? selectedItems[0].row() : -1;
}
void OBSBasic::on_preview_customContextMenuRequested(const QPoint &pos)
@ -5613,6 +5465,22 @@ static bool nudge_callback(obs_scene_t*, obs_sceneitem_t *item, void *param)
struct vec2 pos;
if (!obs_sceneitem_selected(item)) {
if (obs_sceneitem_is_group(item)) {
struct vec3 offset3;
vec3_set(&offset3, offset.x, offset.y, 0.0f);
struct matrix4 matrix;
obs_sceneitem_get_draw_transform(item, &matrix);
vec4_set(&matrix.t, 0.0f, 0.0f, 0.0f, 1.0f);
matrix4_inv(&matrix, &matrix);
vec3_transform(&offset3, &offset3, &matrix);
struct vec2 new_offset;
vec2_set(&new_offset, offset3.x, offset3.y);
obs_sceneitem_group_enum_items(item, nudge_callback,
&new_offset);
}
return true;
}
@ -6188,12 +6056,13 @@ bool OBSBasic::sysTrayMinimizeToTray()
void OBSBasic::on_actionCopySource_triggered()
{
on_actionCopyTransform_triggered();
OBSSceneItem item = GetCurrentSceneItem();
if (!item)
return;
if (!!obs_sceneitem_is_group(item))
return;
on_actionCopyTransform_triggered();
OBSSource source = obs_sceneitem_get_source(item);

View File

@ -232,9 +232,6 @@ private:
void UpdatePreviewScalingMenu();
void UpdateSources(OBSScene scene);
void InsertSceneItem(obs_sceneitem_t *item);
void LoadSceneListOrder(obs_data_array_t *array);
obs_data_array_t *SaveSceneListOrder();
void ChangeSceneIndex(bool relative, int idx, int invalidIdx);
@ -243,10 +240,6 @@ private:
void TempStreamOutput(const char *url, const char *key,
int vBitrate, int aBitrate);
void CreateInteractionWindow(obs_source_t *source);
void CreatePropertiesWindow(obs_source_t *source);
void CreateFiltersWindow(obs_source_t *source);
void CloseDialogs();
void ClearSceneData();
@ -275,7 +268,7 @@ private:
void SaveProjectNow();
QListWidgetItem *GetTopSelectedSourceItem();
int GetTopSelectedSourceItem();
obs_hotkey_pair_id streamingHotkeys, recordingHotkeys,
replayBufHotkeys;
@ -550,11 +543,9 @@ public:
}
}
void ReorderSceneItem(obs_sceneitem_t *item, size_t idx);
QMenu *AddDeinterlacingMenu(obs_source_t *source);
QMenu *AddScaleFilteringMenu(obs_sceneitem_t *item);
void CreateSourcePopupMenu(QListWidgetItem *item, bool preview);
void CreateSourcePopupMenu(int idx, bool preview);
void UpdateTitleBar();
void UpdateSceneSelection(OBSSource source);
@ -564,6 +555,10 @@ public:
void OpenSavedProjectors();
void CreateInteractionWindow(obs_source_t *source);
void CreatePropertiesWindow(obs_source_t *source);
void CreateFiltersWindow(obs_source_t *source);
protected:
virtual void closeEvent(QCloseEvent *event) override;
virtual void changeEvent(QEvent *event) override;
@ -605,9 +600,7 @@ private slots:
void on_actionRemoveScene_triggered();
void on_actionSceneUp_triggered();
void on_actionSceneDown_triggered();
void on_sources_itemSelectionChanged();
void on_sources_customContextMenuRequested(const QPoint &pos);
void on_sources_itemDoubleClicked(QListWidgetItem *item);
void on_scenes_itemDoubleClicked(QListWidgetItem *item);
void on_actionAddSource_triggered();
void on_actionRemoveSource_triggered();
@ -689,8 +682,6 @@ private slots:
void SceneNameEdited(QWidget *editor,
QAbstractItemDelegate::EndEditHint endHint);
void SceneItemNameEdited(QWidget *editor,
QAbstractItemDelegate::EndEditHint endHint);
void OpenSceneFilters();
void OpenFilters();

View File

@ -39,6 +39,8 @@ struct SceneFindData {
OBSSceneItem item;
bool selectBelow;
obs_sceneitem_t *group = nullptr;
SceneFindData(const SceneFindData &) = delete;
SceneFindData(SceneFindData &&) = delete;
SceneFindData& operator=(const SceneFindData &) = delete;
@ -214,11 +216,26 @@ static bool CheckItemSelected(obs_scene_t *scene, obs_sceneitem_t *item,
if (!SceneItemHasVideo(item))
return true;
if (obs_sceneitem_is_group(item)) {
data->group = item;
obs_sceneitem_group_enum_items(item, CheckItemSelected, param);
data->group = nullptr;
if (data->item) {
return false;
}
}
vec3_set(&pos3, data->pos.x, data->pos.y, 0.0f);
obs_sceneitem_get_box_transform(item, &transform);
if (data->group) {
matrix4 parent_transform;
obs_sceneitem_get_draw_transform(data->group, &parent_transform);
matrix4_mul(&transform, &transform, &parent_transform);
}
matrix4_inv(&transform, &transform);
vec3_transform(&transformedPos, &pos3, &transform);
@ -268,10 +285,35 @@ struct HandleFindData {
static bool FindHandleAtPos(obs_scene_t *scene, obs_sceneitem_t *item,
void *param)
{
if (!obs_sceneitem_selected(item))
return true;
HandleFindData *data = reinterpret_cast<HandleFindData*>(param);
if (!obs_sceneitem_selected(item)) {
if (obs_sceneitem_is_group(item)) {
matrix4 transform;
vec3 new_pos3;
vec3_set(&new_pos3, data->pos.x, data->pos.y, 0.0f);
vec3_divf(&new_pos3, &new_pos3, data->scale);
obs_sceneitem_get_draw_transform(item, &transform);
matrix4_inv(&transform, &transform);
vec3_transform(&new_pos3, &new_pos3, &transform);
vec2 new_pos;
vec2_set(&new_pos, new_pos3.x, new_pos3.y);
HandleFindData findData(new_pos, 1.0f);
findData.item = data->item;
findData.handle = data->handle;
obs_sceneitem_group_enum_items(item, FindHandleAtPos,
&findData);
data->item = findData.item;
data->handle = findData.handle;
}
return true;
}
matrix4 transform;
vec3 pos3;
float closestHandle = HANDLE_SEL_RADIUS;
@ -377,6 +419,15 @@ void OBSBasicPreview::GetStretchHandleData(const vec2 &pos)
startCrop.left - startCrop.right);
cropSize.y = float(obs_source_get_height(source) -
startCrop.top - startCrop.bottom);
stretchGroup = obs_sceneitem_get_group(stretchItem);
if (stretchGroup) {
obs_sceneitem_get_draw_transform(stretchGroup,
&invGroupTransform);
matrix4_inv(&invGroupTransform,
&invGroupTransform);
obs_sceneitem_defer_group_resize_begin(stretchGroup);
}
}
}
@ -482,6 +533,9 @@ static bool select_one(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
{
obs_sceneitem_t *selectedItem =
reinterpret_cast<obs_sceneitem_t*>(param);
if (obs_sceneitem_is_group(item))
obs_sceneitem_group_enum_items(item, select_one, param);
obs_sceneitem_select(item, (selectedItem == item));
UNUSED_PARAMETER(scene);
@ -534,10 +588,15 @@ void OBSBasicPreview::mouseReleaseEvent(QMouseEvent *event)
if (!mouseMoved)
ProcessClick(pos);
stretchItem = nullptr;
mouseDown = false;
mouseMoved = false;
cropping = false;
if (stretchGroup) {
obs_sceneitem_defer_group_resize_end(stretchGroup);
}
stretchItem = nullptr;
stretchGroup = nullptr;
mouseDown = false;
mouseMoved = false;
cropping = false;
}
}
@ -692,9 +751,22 @@ static bool move_items(obs_scene_t *scene, obs_sceneitem_t *item, void *param)
if (obs_sceneitem_locked(item))
return true;
bool selected = obs_sceneitem_selected(item);
vec2 *offset = reinterpret_cast<vec2*>(param);
if (obs_sceneitem_selected(item)) {
if (obs_sceneitem_is_group(item) && !selected) {
matrix4 transform;
vec3 new_offset;
vec3_set(&new_offset, offset->x, offset->y, 0.0f);
obs_sceneitem_get_draw_transform(item, &transform);
vec4_set(&transform.t, 0.0f, 0.0f, 0.0f, 1.0f);
matrix4_inv(&transform, &transform);
vec3_transform(&new_offset, &new_offset, &transform);
obs_sceneitem_group_enum_items(item, move_items, &new_offset);
}
if (selected) {
vec2 pos;
obs_sceneitem_get_pos(item, &pos);
vec2_add(&pos, &pos, offset);
@ -1063,6 +1135,17 @@ void OBSBasicPreview::mouseMoveEvent(QMouseEvent *event)
pos.y = std::round(pos.y);
if (stretchHandle != ItemHandle::None) {
obs_sceneitem_t *group = obs_sceneitem_get_group(
stretchItem);
if (group) {
vec3 group_pos;
vec3_set(&group_pos, pos.x, pos.y, 0.0f);
vec3_transform(&group_pos, &group_pos,
&invGroupTransform);
pos.x = group_pos.x;
pos.y = group_pos.y;
}
if (cropping)
CropItem(pos);
else
@ -1110,6 +1193,16 @@ bool OBSBasicPreview::DrawSelectedItem(obs_scene_t *scene,
if (!SceneItemHasVideo(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, DrawSelectedItem, param);
gs_matrix_pop();
}
if (!obs_sceneitem_selected(item))
return true;

View File

@ -35,11 +35,13 @@ private:
obs_sceneitem_crop startCrop;
vec2 startItemPos;
vec2 cropSize;
OBSSceneItem stretchGroup;
OBSSceneItem stretchItem;
ItemHandle stretchHandle = ItemHandle::None;
vec2 stretchItemSize;
matrix4 screenToItem;
matrix4 itemToScreen;
matrix4 invGroupTransform;
vec2 startPos;
vec2 lastMoveOffset;