diff --git a/UI/CMakeLists.txt b/UI/CMakeLists.txt index 0780cf554..ae007eac0 100644 --- a/UI/CMakeLists.txt +++ b/UI/CMakeLists.txt @@ -236,6 +236,7 @@ set(obs_SOURCES window-remux.cpp auth-base.cpp source-tree.cpp + scene-tree.cpp properties-view.cpp focus-list.cpp menu-button.cpp @@ -288,6 +289,7 @@ set(obs_HEADERS window-remux.hpp auth-base.hpp source-tree.hpp + scene-tree.hpp properties-view.hpp properties-view.moc.hpp display-helpers.hpp diff --git a/UI/data/locale/en-US.ini b/UI/data/locale/en-US.ini index a0afca4cd..7daf8f420 100644 --- a/UI/data/locale/en-US.ini +++ b/UI/data/locale/en-US.ini @@ -530,6 +530,8 @@ Basic.Main.ForceStopStreaming="Stop Streaming (discard delay)" Basic.Main.Group="Group %1" Basic.Main.GroupItems="Group Selected Items" Basic.Main.Ungroup="Ungroup" +Basic.Main.GridMode="Grid Mode" +Basic.Main.ListMode="List Mode" # basic mode file menu Basic.MainMenu.File="&File" diff --git a/UI/data/themes/Acri.qss b/UI/data/themes/Acri.qss index 4fa146dff..ffd972b3c 100644 --- a/UI/data/themes/Acri.qss +++ b/UI/data/themes/Acri.qss @@ -1022,3 +1022,25 @@ OBSBasic { qproperty-sceneIcon: url(./Dark/sources/scene.svg); qproperty-defaultIcon: url(./Dark/sources/default.svg); } + +/* Scene Tree */ + +SceneTree#scenes { + qproperty-gridItemWidth: 180; + qproperty-gridItemHeight: 35; +} + +*[gridMode="true"] SceneTree#scenes { + border-bottom: none; +} + +*[gridMode="false"] SceneTree#scenes { + border-bottom: 2px solid #2f2f2f; +} + +*[gridMode="true"] SceneTree::item { + padding: 4px; + padding-left: 10px; + padding-right: 10px; + margin: 0px; +} diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index 383753025..94819ae70 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -12,6 +12,7 @@ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ /* GNU General Public License for more details. */ /* */ +/* */ /* You should have received a copy of the GNU General Public License */ /* along with this program. If not, see . */ /******************************************************************************/ @@ -774,3 +775,37 @@ OBSBasic { qproperty-sceneIcon: url(./Dark/sources/scene.svg); qproperty-defaultIcon: url(./Dark/sources/default.svg); } + +/* Scene Tree */ + +SceneTree { + qproperty-gridItemWidth: 150; + qproperty-gridItemHeight: 27; +} + +*[gridMode="true"] SceneTree::item { + color: rgb(225,224,225); /* veryLight */ + background-color: rgb(76,76,76); + border: none; + border-radius: 3px; + padding: 4px; + padding-left: 10px; + padding-right: 10px; + margin: 1px; +} + +*[gridMode="true"] SceneTree::item:selected { + background-color: rgb(122,121,122); /* light */ +} + +*[gridMode="true"] SceneTree::item:hover { + background-color: rgb(122,121,122); /* light */ +} + +*[gridMode="true"] SceneTree::item:pressed { + background-color: rgb(31,30,31); /* veryDark */ +} + +*[gridMode="true"] SceneTree::item:checked { + background-color: rgb(122,121,122); /* light */ +} diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index 21f6870d3..2aa304f9b 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -1365,3 +1365,10 @@ OBSBasic { qproperty-sceneIcon: url(./Dark/sources/scene.svg); qproperty-defaultIcon: url(./Dark/sources/default.svg); } + +/* Scene Tree */ + +SceneTree#scenes { + qproperty-gridItemWidth: 150; + qproperty-gridItemHeight: 30; +} diff --git a/UI/data/themes/System.qss b/UI/data/themes/System.qss index 3fe69c885..572946219 100644 --- a/UI/data/themes/System.qss +++ b/UI/data/themes/System.qss @@ -233,3 +233,10 @@ QListWidget::item, SourceTree::item { height: 24px; } + +/* Scene Tree */ + +SceneTree { + qproperty-gridItemWidth: 150; + qproperty-gridItemHeight: 24; +} diff --git a/UI/forms/OBSBasic.ui b/UI/forms/OBSBasic.ui index 114b7aba5..e6ea7ed62 100644 --- a/UI/forms/OBSBasic.ui +++ b/UI/forms/OBSBasic.ui @@ -474,7 +474,7 @@ 0 - + 0 @@ -1818,6 +1818,11 @@ QListView
source-tree.hpp
+ + SceneTree + QListWidget +
scene-tree.hpp
+
OBSDock QDockWidget diff --git a/UI/scene-tree.cpp b/UI/scene-tree.cpp new file mode 100644 index 000000000..120eab674 --- /dev/null +++ b/UI/scene-tree.cpp @@ -0,0 +1,188 @@ +#include "obs.hpp" +#include "scene-tree.hpp" +#include "obs-app.hpp" + +#include +#include +#include +#include + +SceneTree::SceneTree(QWidget *parent_) : QListWidget(parent_) +{ + installEventFilter(this); + setDragDropMode(InternalMove); + setMovement(QListView::Snap); +} + +void SceneTree::SetGridMode(bool grid) +{ + config_set_bool(App()->GlobalConfig(), "BasicWindow", "gridMode", grid); + parent()->setProperty("gridMode", grid); + gridMode = grid; + + if (gridMode) { + setResizeMode(QListView::Adjust); + setViewMode(QListView::IconMode); + setUniformItemSizes(true); + setStyleSheet("*{padding: 0; margin: 0;}"); + } else { + setViewMode(QListView::ListMode); + setResizeMode(QListView::Fixed); + setStyleSheet(""); + } + + resizeEvent(new QResizeEvent(size(), size())); +} + +bool SceneTree::GetGridMode() +{ + return gridMode; +} + +void SceneTree::SetGridItemWidth(int width) +{ + maxWidth = width; +} + +void SceneTree::SetGridItemHeight(int height) +{ + itemHeight = height; +} + +int SceneTree::GetGridItemWidth() +{ + return maxWidth; +} + +int SceneTree::GetGridItemHeight() +{ + return itemHeight; +} + +bool SceneTree::eventFilter(QObject *obj, QEvent *event) +{ + return QObject::eventFilter(obj, event); +} + +void SceneTree::resizeEvent(QResizeEvent *event) +{ + QListWidget::resizeEvent(event); + + if (gridMode) { + int scrollWid = verticalScrollBar()->sizeHint().width(); + int h = visualItemRect(item(count() - 1)).bottom(); + + if (h < height()) { + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollWid = 0; + } else { + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + } + + int wid = contentsRect().width() - scrollWid - 1; + int items = (int)ceil((float)wid / maxWidth); + int itemWidth = wid / items; + + setGridSize(QSize(itemWidth, itemHeight)); + + for (int i = 0; i < count(); i++) { + item(i)->setSizeHint(QSize(itemWidth, itemHeight)); + } + } else { + setGridSize(QSize()); + setSpacing(0); + for (int i = 0; i < count(); i++) { + item(i)->setData(Qt::SizeHintRole, QVariant()); + } + } +} + +void SceneTree::startDrag(Qt::DropActions supportedActions) +{ + QListWidget::startDrag(supportedActions); +} + +void SceneTree::dropEvent(QDropEvent *event) +{ + QListWidget::dropEvent(event); + if (event->source() == this && gridMode) { + int scrollWid = verticalScrollBar()->sizeHint().width(); + int h = visualItemRect(item(count() - 1)).bottom(); + + if (h < height()) { + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollWid = 0; + } else { + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + } + + float wid = contentsRect().width() - scrollWid - 1; + + QPoint point = event->pos(); + + int x = (float)point.x() / wid * ceil(wid / maxWidth); + int y = point.y() / itemHeight; + + int r = x + y * ceil(wid / maxWidth); + + QListWidgetItem *item = takeItem(selectedIndexes()[0].row()); + insertItem(r, item); + setCurrentItem(item); + resize(size()); + } +} + +void SceneTree::dragMoveEvent(QDragMoveEvent *event) +{ + if (gridMode) { + int scrollWid = verticalScrollBar()->sizeHint().width(); + int h = visualItemRect(item(count() - 1)).bottom(); + + if (h < height()) { + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollWid = 0; + } else { + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + } + + float wid = contentsRect().width() - scrollWid - 1; + + QPoint point = event->pos(); + + int x = (float)point.x() / wid * ceil(wid / maxWidth); + int y = point.y() / itemHeight; + + int r = x + y * ceil(wid / maxWidth); + int orig = selectedIndexes()[0].row(); + + for (int i = 0; i < count(); i++) { + auto *wItem = item(i); + + if (wItem->isSelected()) + continue; + + QModelIndex index = indexFromItem(wItem); + + int off = (i >= r ? 1 : 0) - + (i > orig && i > r ? 1 : 0) - + (i > orig && i == r ? 2 : 0); + + int xPos = (i + off) % (int)ceil(wid / maxWidth); + int yPos = (i + off) / (int)ceil(wid / maxWidth); + QSize g = gridSize(); + + QPoint position(xPos * g.width(), yPos * g.height()); + setPositionForIndex(position, index); + } + } else { + QListWidget::dragMoveEvent(event); + } +} + +void SceneTree::rowsInserted(const QModelIndex &parent, int start, int end) +{ + QListWidget::rowsInserted(parent, start, end); + + QResizeEvent *event = new QResizeEvent(size(), size()); + SceneTree::resizeEvent(event); +} diff --git a/UI/scene-tree.hpp b/UI/scene-tree.hpp new file mode 100644 index 000000000..4b04dda54 --- /dev/null +++ b/UI/scene-tree.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +class SceneTree : public QListWidget { + Q_OBJECT + Q_PROPERTY(int gridItemWidth READ GetGridItemWidth WRITE + SetGridItemWidth DESIGNABLE true) + Q_PROPERTY(int gridItemHeight READ GetGridItemHeight WRITE + SetGridItemHeight DESIGNABLE true) + + bool gridMode = false; + int maxWidth = 150; + int itemHeight = 24; + +public: + void SetGridMode(bool grid); + bool GetGridMode(); + + void SetGridItemWidth(int width); + void SetGridItemHeight(int height); + int GetGridItemWidth(); + int GetGridItemHeight(); + + explicit SceneTree(QWidget *parent = nullptr); + +protected: + virtual bool eventFilter(QObject *obj, QEvent *event) override; + virtual void resizeEvent(QResizeEvent *event) override; + virtual void startDrag(Qt::DropActions supportedActions) override; + virtual void dropEvent(QDropEvent *event) override; + virtual void dragMoveEvent(QDragMoveEvent *event) override; + virtual void rowsInserted(const QModelIndex &parent, int start, + int end) override; +}; diff --git a/UI/window-basic-main.cpp b/UI/window-basic-main.cpp index f5972b559..51a0d539b 100644 --- a/UI/window-basic-main.cpp +++ b/UI/window-basic-main.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -94,7 +95,6 @@ template struct SignalContainer { OBSRef ref; vector> handlers; }; - } extern volatile long insideEventLoop; @@ -105,6 +105,18 @@ Q_DECLARE_METATYPE(OBSSource); Q_DECLARE_METATYPE(obs_order_movement); Q_DECLARE_METATYPE(SignalContainer); +QDataStream &operator<<(QDataStream &out, const SignalContainer &v) +{ + out << v.ref; + return out; +} + +QDataStream &operator>>(QDataStream &in, SignalContainer &v) +{ + in >> v.ref; + return in; +} + template static T GetOBSRef(QListWidgetItem *item) { return item->data(static_cast(QtDataRole::OBSRef)).value(); @@ -195,6 +207,9 @@ extern void RegisterRestreamAuth(); OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow(parent), ui(new Ui::OBSBasic) { + qRegisterMetaTypeStreamOperators>( + "SignalContainer"); + setAttribute(Qt::WA_NativeWindow); #if TWITCH_ENABLED @@ -252,6 +267,10 @@ OBSBasic::OBSBasic(QWidget *parent) ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); + bool sceneGrid = config_get_bool(App()->GlobalConfig(), "BasicWindow", + "gridMode"); + ui->scenes->SetGridMode(sceneGrid); + ui->scenes->setItemDelegate(new SceneRenameDelegate(ui->scenes)); auto displayResize = [this]() { @@ -4051,6 +4070,7 @@ void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) QMenu popup(this); QMenu order(QTStr("Basic.MainMenu.Edit.Order"), this); + popup.addAction(QTStr("Add"), this, SLOT(on_actionAddScene_triggered())); @@ -4132,9 +4152,26 @@ void OBSBasic::on_scenes_customContextMenuRequested(const QPoint &pos) std::bind(showInMultiview, data)); } + popup.addSeparator(); + + bool grid = ui->scenes->GetGridMode(); + + QAction *gridAction = new QAction(grid ? QTStr("Basic.Main.ListMode") + : QTStr("Basic.Main.GridMode"), + this); + connect(gridAction, SIGNAL(triggered()), this, + SLOT(on_actionGridMode_triggered())); + popup.addAction(gridAction); + popup.exec(QCursor::pos()); } +void OBSBasic::on_actionGridMode_triggered() +{ + bool gridMode = !ui->scenes->GetGridMode(); + ui->scenes->SetGridMode(gridMode); +} + void OBSBasic::on_actionAddScene_triggered() { string name; diff --git a/UI/window-basic-main.hpp b/UI/window-basic-main.hpp index a92da91d3..83e0ca08c 100644 --- a/UI/window-basic-main.hpp +++ b/UI/window-basic-main.hpp @@ -816,6 +816,7 @@ private slots: void on_scenes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *prev); void on_scenes_customContextMenuRequested(const QPoint &pos); + void on_actionGridMode_triggered(); void on_actionAddScene_triggered(); void on_actionRemoveScene_triggered(); void on_actionSceneUp_triggered();