From c0e2e7f12ebc99ff3c2c64b633c29989692f9912 Mon Sep 17 00:00:00 2001 From: VodBox Date: Tue, 27 Aug 2019 20:08:43 +1200 Subject: [PATCH] UI: Add Grid Mode to Scenes Widget Adds an option to the right click menu in the scenes widget to switch modes. When in regular list mode, it'll let you select grid mode, and in grid mode, it'll let you select list mode. Grid mode changes the scenes widget to have a grid of buttons for scenes rather than a list, much like XSplit. --- UI/CMakeLists.txt | 2 + UI/data/locale/en-US.ini | 2 + UI/data/themes/Acri.qss | 22 +++++ UI/data/themes/Dark.qss | 35 +++++++ UI/data/themes/Rachni.qss | 7 ++ UI/data/themes/System.qss | 7 ++ UI/forms/OBSBasic.ui | 7 +- UI/scene-tree.cpp | 188 ++++++++++++++++++++++++++++++++++++++ UI/scene-tree.hpp | 37 ++++++++ UI/window-basic-main.cpp | 39 +++++++- UI/window-basic-main.hpp | 1 + 11 files changed, 345 insertions(+), 2 deletions(-) create mode 100644 UI/scene-tree.cpp create mode 100644 UI/scene-tree.hpp 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();