Rework macro segment list widget

- Allows drag and drop reorder
- Handles selection change
- Handles highlight
- Handles changing collapse change
- Handles add, insert, remove, and clear functionality
This commit is contained in:
WarmUpTill 2022-03-18 22:12:40 +01:00 committed by WarmUpTill
parent 1b63978acb
commit 0a8f279e97
6 changed files with 340 additions and 70 deletions

View File

@ -147,11 +147,13 @@ public slots:
void MinimizeActions();
void MinimizeConditions();
void MacroActionSelectionChanged(int idx);
void MacroActionReorder(int to, int target);
void AddMacroAction(int idx);
void RemoveMacroAction(int idx);
void MoveMacroActionUp(int idx);
void MoveMacroActionDown(int idx);
void MacroConditionSelectionChanged(int idx);
void MacroConditionReorder(int to, int target);
void AddMacroCondition(int idx);
void RemoveMacroCondition(int idx);
void MoveMacroConditionUp(int idx);

View File

@ -1,4 +1,6 @@
#pragma once
#include "macro-segment.hpp"
#include <QWidget>
#include <QScrollArea>
#include <QVBoxLayout>
@ -11,15 +13,35 @@ public:
MacroSegmentList(QWidget *parent = nullptr);
void SetHelpMsg(const QString &msg);
void SetHelpMsgVisible(bool visible);
void Insert(int idx, QWidget *widget);
void Add(QWidget *widget);
void Remove(int idx);
void Clear(int idx = 0); // Clear all elements >= idx
void Highlight(int idx);
void SetCollapsed(bool);
void SetSelection(int idx);
QVBoxLayout *ContentLayout() { return _contentLayout; }
signals:
void SelectionChagned(int idx);
void Reorder(int source, int target);
protected:
bool eventFilter(QObject *object, QEvent *event);
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void dragEnterEvent(QDragEnterEvent *event);
void dropEvent(QDropEvent *event);
private:
int GetIndex(QPoint);
int GetDropIndex(QPoint);
QRect GetContentItemRectWithPadding(int idx);
int _dragPosition = -1;
QVBoxLayout *_layout;
QVBoxLayout *_contentLayout;
QLabel *_helpMsg;
};

View File

@ -172,7 +172,7 @@ void AdvSceneSwitcher::AddMacroAction(int idx)
macro->UpdateActionIndices();
}
clearLayout(actionsList->ContentLayout(), idx);
actionsList->Clear(idx);
PopulateMacroActions(*macro, idx);
HighlightAction(idx);
SetActionData(*macro);
@ -219,7 +219,7 @@ void AdvSceneSwitcher::RemoveMacroAction(int idx)
macro->UpdateActionIndices();
}
clearLayout(actionsList->ContentLayout(), idx);
actionsList->Clear(idx);
PopulateMacroActions(*macro, idx);
SetActionData(*macro);
}
@ -271,16 +271,14 @@ void AdvSceneSwitcher::SwapActions(Macro *m, int pos1, int pos2)
auto a1 = m->Actions().begin() + pos1;
auto a2 = m->Actions().begin() + pos2;
auto item1 = actionsList->ContentLayout()->takeAt(pos1);
auto item2 = actionsList->ContentLayout()->takeAt(pos2 - 1);
deleteLayoutItem(item1);
deleteLayoutItem(item2);
actionsList->Remove(pos1);
actionsList->Remove(pos2 - 1);
auto widget1 = new MacroActionEdit(this, &(*a1), (*a1)->GetId());
auto widget2 = new MacroActionEdit(this, &(*a2), (*a2)->GetId());
ConnectControlSignals(widget1);
ConnectControlSignals(widget2);
actionsList->ContentLayout()->insertWidget(pos1, widget1);
actionsList->ContentLayout()->insertWidget(pos2, widget2);
actionsList->Insert(pos1, widget1);
actionsList->Insert(pos2, widget2);
}
void AdvSceneSwitcher::MoveMacroActionUp(int idx)
@ -331,3 +329,29 @@ void AdvSceneSwitcher::MacroActionSelectionChanged(int idx)
currentConditionIdx = -1;
HighlightControls();
}
void AdvSceneSwitcher::MacroActionReorder(int to, int from)
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
if (from < 0 || from > (int)macro->Actions().size() || to < 0 ||
to > (int)macro->Actions().size()) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto action = macro->Actions().at(from);
macro->Actions().erase(macro->Actions().begin() + from);
macro->Actions().insert(macro->Actions().begin() + to, action);
}
int idx = to > from ? from : to;
macro->UpdateActionIndices();
actionsList->Clear(idx);
PopulateMacroActions(*macro, idx);
HighlightAction(to);
SetActionData(*macro);
}

View File

@ -302,7 +302,7 @@ void AdvSceneSwitcher::AddMacroCondition(int idx)
macro->UpdateConditionIndices();
}
clearLayout(conditionsList->ContentLayout(), idx);
conditionsList->Clear(idx);
PopulateMacroConditions(*macro, idx);
HighlightCondition(idx);
SetConditionData(*macro);
@ -352,7 +352,7 @@ void AdvSceneSwitcher::RemoveMacroCondition(int idx)
}
}
clearLayout(conditionsList->ContentLayout(), idx);
conditionsList->Clear(idx);
PopulateMacroConditions(*macro, idx);
SetConditionData(*macro);
}
@ -413,18 +413,16 @@ void AdvSceneSwitcher::SwapConditions(Macro *m, int pos1, int pos2)
(*c2)->SetLogicType(logic1);
}
auto item1 = conditionsList->ContentLayout()->takeAt(pos1);
auto item2 = conditionsList->ContentLayout()->takeAt(pos2 - 1);
deleteLayoutItem(item1);
deleteLayoutItem(item2);
conditionsList->Remove(pos1);
conditionsList->Remove(pos2 - 1);
auto widget1 =
new MacroConditionEdit(this, &(*c1), (*c1)->GetId(), root);
auto widget2 =
new MacroConditionEdit(this, &(*c2), (*c2)->GetId(), false);
ConnectControlSignals(widget1);
ConnectControlSignals(widget2);
conditionsList->ContentLayout()->insertWidget(pos1, widget1);
conditionsList->ContentLayout()->insertWidget(pos2, widget2);
conditionsList->Insert(pos1, widget1);
conditionsList->Insert(pos2, widget2);
}
void AdvSceneSwitcher::MoveMacroConditionUp(int idx)
@ -475,3 +473,39 @@ void AdvSceneSwitcher::MacroConditionSelectionChanged(int idx)
currentActionIdx = -1;
HighlightControls();
}
void AdvSceneSwitcher::MacroConditionReorder(int to, int from)
{
auto macro = getSelectedMacro();
if (!macro) {
return;
}
if (from < 0 || from > (int)macro->Conditions().size() || to < 0 ||
to > (int)macro->Conditions().size()) {
return;
}
{
std::lock_guard<std::mutex> lock(switcher->m);
auto condition = macro->Conditions().at(from);
if (to == 0) {
condition->SetLogicType(LogicType::ROOT_NONE);
macro->Conditions().at(0)->SetLogicType(LogicType::AND);
}
if (from == 0) {
condition->SetLogicType(LogicType::AND);
macro->Conditions().at(1)->SetLogicType(
LogicType::ROOT_NONE);
}
macro->Conditions().erase(macro->Conditions().begin() + from);
macro->Conditions().insert(macro->Conditions().begin() + to,
condition);
}
int idx = to > from ? from : to;
macro->UpdateConditionIndices();
conditionsList->Clear(idx);
PopulateMacroConditions(*macro, idx);
HighlightCondition(to);
SetConditionData(*macro);
}

View File

@ -1,13 +1,20 @@
#include "headers/macro-segment-list.hpp"
#include "headers/utility.hpp"
#include <QGridLayout>
#include <QSpacerItem>
#include <QEvent>
#include <QMouseEvent>
#include <QDrag>
#include <QMimeData>
#include <QScrollBar>
MacroSegmentList::MacroSegmentList(QWidget *parent) : QScrollArea(parent)
MacroSegmentList::MacroSegmentList(QWidget *parent)
: QScrollArea(parent),
_layout(new QVBoxLayout),
_contentLayout(new QVBoxLayout),
_helpMsg(new QLabel)
{
_contentLayout = new QVBoxLayout;
_helpMsg = new QLabel;
_helpMsg->setWordWrap(true);
_helpMsg->setAlignment(Qt::AlignCenter);
@ -16,14 +23,37 @@ MacroSegmentList::MacroSegmentList(QWidget *parent) : QScrollArea(parent)
Qt::AlignHCenter | Qt::AlignVCenter);
helperLayout->addLayout(_contentLayout, 0, 0);
helperLayout->setSizeConstraint(QLayout::SetMinAndMaxSize);
auto layout = new QVBoxLayout;
layout->addLayout(helperLayout, 10);
layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
QSizePolicy::Expanding));
_layout->addLayout(helperLayout, 10);
_layout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
QSizePolicy::Expanding));
auto wrapper = new QWidget;
wrapper->setLayout(layout);
wrapper->setLayout(_layout);
setWidget(wrapper);
setWidgetResizable(true);
setAcceptDrops(true);
}
int MacroSegmentList::GetIndex(QPoint pos)
{
for (int idx = 0; idx < _contentLayout->count(); ++idx) {
auto item = _contentLayout->itemAt(idx);
if (!item) {
continue;
}
const auto geo = item->geometry();
int scrollOffset = 0;
if (verticalScrollBar()) {
scrollOffset = verticalScrollBar()->value();
}
const QRect rect(
mapToGlobal(QPoint(geo.topLeft().x(),
geo.topLeft().y() - scrollOffset)),
geo.size());
if (rect.contains(pos) && idx != _dragPosition) {
return idx;
}
}
return -1;
}
void MacroSegmentList::SetHelpMsg(const QString &msg)
@ -36,9 +66,197 @@ void MacroSegmentList::SetHelpMsgVisible(bool visible)
_helpMsg->setVisible(visible);
}
void MacroSegmentList::Insert(int idx, QWidget *widget)
{
widget->installEventFilter(this);
_contentLayout->insertWidget(idx, widget);
}
void MacroSegmentList::Add(QWidget *widget)
{
widget->installEventFilter(this);
_contentLayout->addWidget(widget);
}
void MacroSegmentList::Remove(int idx)
{
deleteLayoutItemWidget(_contentLayout->takeAt(idx));
}
void MacroSegmentList::Clear(int idx)
{
clearLayout(_contentLayout, idx);
}
void MacroSegmentList::Highlight(int idx)
{
auto item = _contentLayout->itemAt(idx);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), true);
}
void MacroSegmentList::SetCollapsed(bool collapse)
{
QLayoutItem *item = nullptr;
for (int i = 0; i < _contentLayout->count(); i++) {
item = _contentLayout->itemAt(i);
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
if (segment) {
segment->SetCollapsed(collapse);
}
}
}
void MacroSegmentList::SetSelection(int idx)
{
for (int i = 0; i < _contentLayout->count(); ++i) {
auto widget = static_cast<MacroSegmentEdit *>(
_contentLayout->itemAt(i)->widget());
if (widget) {
widget->SetSelected(i == idx);
}
}
}
bool MacroSegmentList::eventFilter(QObject *object, QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress) {
}
switch (event->type()) {
case QEvent::MouseButtonPress:
mousePressEvent(static_cast<QMouseEvent *>(event));
break;
case QEvent::MouseMove:
mouseMoveEvent(static_cast<QMouseEvent *>(event));
break;
case QEvent::MouseButtonRelease:
mouseReleaseEvent(static_cast<QMouseEvent *>(event));
break;
default:
break;
}
return QWidget::eventFilter(object, event);
}
void MacroSegmentList::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
emit SelectionChagned(-1);
_dragPosition = GetIndex(event->globalPos());
emit SelectionChagned(_dragPosition);
} else {
_dragPosition = -1;
}
}
void MacroSegmentList::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton && _dragPosition != -1) {
QWidget *widget = nullptr;
if (_contentLayout->itemAt(_dragPosition)) {
widget =
_contentLayout->itemAt(_dragPosition)->widget();
}
if (!widget) {
return;
}
QDrag *drag = new QDrag(widget);
auto img = widget->grab();
auto mimedata = new QMimeData();
mimedata->setImageData(img);
drag->setMimeData(mimedata);
drag->setPixmap(img);
drag->setHotSpot(event->pos());
drag->exec();
}
}
void MacroSegmentList::mouseReleaseEvent(QMouseEvent *)
{
_dragPosition = -1;
}
void MacroSegmentList::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData() && event->mimeData()->hasImage()) {
event->accept();
} else {
event->ignore();
}
}
QRect MacroSegmentList::GetContentItemRectWithPadding(int idx)
{
auto item = _contentLayout->itemAt(idx);
if (!item) {
return {};
}
int scrollOffset = 0;
if (verticalScrollBar()) {
scrollOffset = verticalScrollBar()->value();
}
const QRect itemRect = item->geometry().marginsAdded(
_contentLayout->contentsMargins());
const QRect rect(mapToGlobal(QPoint(itemRect.topLeft().x(),
itemRect.topLeft().y() -
_contentLayout->spacing() -
scrollOffset)),
itemRect.size());
return rect;
}
int MacroSegmentList::GetDropIndex(QPoint pos)
{
const QRect layoutRect(mapToGlobal(_layout->contentsRect().topLeft()),
_layout->contentsRect().size());
if (!layoutRect.contains(pos)) {
return -1;
}
int idx = -1;
for (int i = 0; i < _contentLayout->count(); ++i) {
if (GetContentItemRectWithPadding(i).contains(pos)) {
idx = i;
break;
}
}
if (idx == -1) {
idx = _contentLayout->count() - 1;
}
if (idx != _dragPosition) {
return idx;
}
return -1;
}
bool widgetIsInLayout(QWidget *w, QLayout *l)
{
for (int i = 0; i < l->count(); ++i) {
auto item = l->itemAt(i);
if (!item) {
continue;
}
if (item->widget() == w) {
return true;
}
}
return false;
}
void MacroSegmentList::dropEvent(QDropEvent *event)
{
auto widget = qobject_cast<QWidget *>(event->source());
if (widget && !widget->geometry().contains(event->pos()) &&
widgetIsInLayout(widget, _contentLayout)) {
int dropPosition = GetDropIndex(mapToGlobal(event->pos()));
if (dropPosition == -1) {
return;
}
emit Reorder(dropPosition, _dragPosition);
}
_dragPosition = -1;
}

View File

@ -244,7 +244,7 @@ void AdvSceneSwitcher::PopulateMacroActions(Macro &m, uint32_t afterIdx)
auto newEntry = new MacroActionEdit(this, &actions[afterIdx],
actions[afterIdx]->GetId());
ConnectControlSignals(newEntry);
actionsList->ContentLayout()->addWidget(newEntry);
actionsList->Add(newEntry);
}
actionsList->SetHelpMsgVisible(actions.size() == 0);
}
@ -258,7 +258,7 @@ void AdvSceneSwitcher::PopulateMacroConditions(Macro &m, uint32_t afterIdx)
this, &conditions[afterIdx],
conditions[afterIdx]->GetId(), root);
ConnectControlSignals(newEntry);
conditionsList->ContentLayout()->addWidget(newEntry);
conditionsList->Add(newEntry);
root = false;
}
conditionsList->SetHelpMsgVisible(conditions.size() == 0);
@ -307,8 +307,8 @@ void AdvSceneSwitcher::SetEditMacro(Macro &m)
ui->runMacroInParallel->setChecked(m.RunInParallel());
ui->runMacroOnChange->setChecked(m.MatchOnChange());
}
clearLayout(conditionsList->ContentLayout());
clearLayout(actionsList->ContentLayout());
conditionsList->Clear();
actionsList->Clear();
m.ResetUIHelpers();
@ -323,28 +323,12 @@ void AdvSceneSwitcher::SetEditMacro(Macro &m)
void AdvSceneSwitcher::HighlightAction(int idx)
{
auto item = actionsList->ContentLayout()->itemAt(idx);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), true);
actionsList->Highlight(idx);
}
void AdvSceneSwitcher::HighlightCondition(int idx)
{
auto item = conditionsList->ContentLayout()->itemAt(idx);
if (!item) {
return;
}
auto widget = item->widget();
if (!widget) {
return;
}
PulseWidget(widget, QColor(Qt::green), QColor(0, 0, 0, 0), true);
conditionsList->Highlight(idx);
}
void AdvSceneSwitcher::ConnectControlSignals(MacroActionEdit *a)
@ -466,6 +450,8 @@ void AdvSceneSwitcher::setupMacroTab()
obs_module_text("AdvSceneSwitcher.macroTab.editConditionHelp"));
connect(conditionsList, &MacroSegmentList::SelectionChagned, this,
&AdvSceneSwitcher::MacroConditionSelectionChanged);
connect(conditionsList, &MacroSegmentList::Reorder, this,
&AdvSceneSwitcher::MacroConditionReorder);
ui->macroConditionsLayout->insertWidget(0, conditionsList);
delete actionsList;
@ -474,6 +460,8 @@ void AdvSceneSwitcher::setupMacroTab()
obs_module_text("AdvSceneSwitcher.macroTab.editActionHelp"));
connect(actionsList, &MacroSegmentList::SelectionChagned, this,
&AdvSceneSwitcher::MacroActionSelectionChanged);
connect(actionsList, &MacroSegmentList::Reorder, this,
&AdvSceneSwitcher::MacroActionReorder);
ui->macroActionsLayout->insertWidget(0, actionsList);
ui->macros->setContextMenuPolicy(Qt::CustomContextMenu);
@ -571,25 +559,13 @@ void AdvSceneSwitcher::CopyMacro()
ui->macros->setCurrentItem(item);
}
void setCollapsedStateOfSegmentsIn(QLayout *layout, bool collapse)
{
QLayoutItem *item = nullptr;
for (int i = 0; i < layout->count(); i++) {
item = layout->itemAt(i);
auto segment = dynamic_cast<MacroSegmentEdit *>(item->widget());
if (segment) {
segment->SetCollapsed(collapse);
}
}
}
void AdvSceneSwitcher::ExpandAllActions()
{
auto m = getSelectedMacro();
if (!m) {
return;
}
setCollapsedStateOfSegmentsIn(actionsList->ContentLayout(), false);
actionsList->SetCollapsed(false);
}
void AdvSceneSwitcher::ExpandAllConditions()
@ -598,7 +574,7 @@ void AdvSceneSwitcher::ExpandAllConditions()
if (!m) {
return;
}
setCollapsedStateOfSegmentsIn(conditionsList->ContentLayout(), false);
conditionsList->SetCollapsed(false);
}
void AdvSceneSwitcher::CollapseAllActions()
@ -607,7 +583,7 @@ void AdvSceneSwitcher::CollapseAllActions()
if (!m) {
return;
}
setCollapsedStateOfSegmentsIn(actionsList->ContentLayout(), true);
actionsList->SetCollapsed(true);
}
void AdvSceneSwitcher::CollapseAllConditions()
@ -616,7 +592,7 @@ void AdvSceneSwitcher::CollapseAllConditions()
if (!m) {
return;
}
setCollapsedStateOfSegmentsIn(conditionsList->ContentLayout(), true);
conditionsList->SetCollapsed(true);
}
void AdvSceneSwitcher::MinimizeActions()
@ -641,13 +617,7 @@ void AdvSceneSwitcher::MinimizeConditions()
void AdvSceneSwitcher::SetSelection(MacroSegmentList *list, int idx)
{
for (int i = 0; i < list->ContentLayout()->count(); ++i) {
auto widget = static_cast<MacroSegmentEdit *>(
list->ContentLayout()->itemAt(i)->widget());
if (widget) {
widget->SetSelected(i == idx);
}
}
list->SetSelection(idx);
}
void AdvSceneSwitcher::DeleteMacroSegementHotkey()