mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-03-21 17:34:57 -05:00
618 lines
14 KiB
C++
618 lines
14 KiB
C++
#include "macro-segment-list.hpp"
|
|
#include "layout-helpers.hpp"
|
|
#include "ui-helpers.hpp"
|
|
|
|
#include <QEvent>
|
|
#include <QDrag>
|
|
#include <QGridLayout>
|
|
#include <QMimeData>
|
|
#include <QMouseEvent>
|
|
#include <QScrollBar>
|
|
#include <QSpacerItem>
|
|
#include <QtGlobal>
|
|
|
|
namespace advss {
|
|
|
|
bool MacroSegmentList::_useCache = true;
|
|
|
|
MacroSegmentList::MacroSegmentList(QWidget *parent)
|
|
: QScrollArea(parent),
|
|
_stackedWidget(new QStackedWidget(this)),
|
|
_layout(new QVBoxLayout),
|
|
_contentLayout(new QVBoxLayout),
|
|
_helpMsg(new QLabel(this))
|
|
{
|
|
_helpMsg->setWordWrap(true);
|
|
_helpMsg->setAlignment(Qt::AlignCenter);
|
|
auto helpWidget = new QWidget(this);
|
|
auto helpLayout = new QVBoxLayout(helpWidget);
|
|
helpLayout->addWidget(_helpMsg);
|
|
helpLayout->setAlignment(Qt::AlignCenter);
|
|
|
|
auto contentWidget = new QWidget(this);
|
|
contentWidget->setLayout(_contentLayout);
|
|
_contentLayout->setSpacing(0);
|
|
|
|
auto contentWrapper = new QWidget(this);
|
|
auto wrapperLayout = new QVBoxLayout(contentWrapper);
|
|
wrapperLayout->setContentsMargins(0, 0, 0, 0);
|
|
wrapperLayout->addWidget(contentWidget);
|
|
// Move macro segments to the top of the list
|
|
wrapperLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
|
|
QSizePolicy::Expanding));
|
|
|
|
_stackedWidget->addWidget(helpWidget);
|
|
_stackedWidget->addWidget(contentWrapper);
|
|
|
|
setWidget(_stackedWidget);
|
|
setWidgetResizable(true);
|
|
setAcceptDrops(true);
|
|
|
|
SetHelpMsgVisible(true);
|
|
|
|
connect(verticalScrollBar(), &QScrollBar::valueChanged, this,
|
|
[this]() { SetupVisibleMacroSegmentWidgets(); });
|
|
}
|
|
|
|
static void clearWidgetVector(const std::vector<QWidget *> &widgets)
|
|
{
|
|
for (auto widget : widgets) {
|
|
widget->deleteLater();
|
|
}
|
|
};
|
|
|
|
MacroSegmentList::~MacroSegmentList()
|
|
{
|
|
if (_autoScrollThread.joinable()) {
|
|
_autoScroll = false;
|
|
_autoScrollThread.join();
|
|
}
|
|
|
|
ClearWidgetCache();
|
|
}
|
|
|
|
static bool posIsInScrollbar(const QScrollBar *scrollbar, const QPoint &pos)
|
|
{
|
|
if (!scrollbar) {
|
|
return false;
|
|
}
|
|
if (!scrollbar->isVisible()) {
|
|
return false;
|
|
}
|
|
const auto &geo = scrollbar->geometry();
|
|
const auto globalGeo = QRect(scrollbar->mapToGlobal(geo.topLeft()),
|
|
scrollbar->mapToGlobal(geo.bottomRight()));
|
|
return globalGeo.contains(pos);
|
|
}
|
|
|
|
int MacroSegmentList::GetDragIndex(const QPoint &pos) const
|
|
{
|
|
// Don't drag widget when interacting with the scrollbars
|
|
if (posIsInScrollbar(horizontalScrollBar(), mapTo(this, pos)) ||
|
|
posIsInScrollbar(verticalScrollBar(), mapTo(this, pos))) {
|
|
return -1;
|
|
}
|
|
|
|
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)) {
|
|
return idx;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void MacroSegmentList::SetHelpMsg(const QString &msg) const
|
|
{
|
|
_helpMsg->setText(msg);
|
|
}
|
|
|
|
void MacroSegmentList::SetHelpMsgVisible(bool visible)
|
|
{
|
|
_stackedWidget->setCurrentIndex(visible ? 0 : 1);
|
|
adjustSize();
|
|
updateGeometry();
|
|
}
|
|
|
|
void MacroSegmentList::Insert(int idx, QWidget *widget)
|
|
{
|
|
widget->installEventFilter(this);
|
|
_contentLayout->insertWidget(idx, widget);
|
|
SetupVisibleMacroSegmentWidgets();
|
|
SetHelpMsgVisible(false);
|
|
adjustSize();
|
|
updateGeometry();
|
|
}
|
|
|
|
void MacroSegmentList::Add(QWidget *widget)
|
|
{
|
|
Insert(_contentLayout->count(), widget);
|
|
}
|
|
|
|
void MacroSegmentList::Remove(int idx)
|
|
{
|
|
DeleteLayoutItemWidget(_contentLayout->takeAt(idx));
|
|
adjustSize();
|
|
updateGeometry();
|
|
if (IsEmpty()) {
|
|
SetHelpMsgVisible(true);
|
|
}
|
|
}
|
|
|
|
void MacroSegmentList::Clear(int idx)
|
|
{
|
|
ClearLayout(_contentLayout, idx);
|
|
adjustSize();
|
|
updateGeometry();
|
|
|
|
SetHelpMsgVisible(true);
|
|
}
|
|
|
|
void MacroSegmentList::SetCachingEnabled(bool enable)
|
|
{
|
|
_useCache = enable;
|
|
}
|
|
|
|
void MacroSegmentList::CacheCurrentWidgetsFor(const Macro *macro)
|
|
{
|
|
if (!_useCache) {
|
|
return;
|
|
}
|
|
|
|
std::vector<QWidget *> result;
|
|
int idx = 0;
|
|
QLayoutItem *item;
|
|
while ((item = _contentLayout->takeAt(idx))) {
|
|
if (!item || !item->widget()) {
|
|
continue;
|
|
}
|
|
auto widget = item->widget();
|
|
widget->hide();
|
|
result.emplace_back(widget);
|
|
}
|
|
|
|
_widgetCache[macro] = result;
|
|
}
|
|
|
|
bool MacroSegmentList::PopulateWidgetsFromCache(const Macro *macro)
|
|
{
|
|
if (!_useCache) {
|
|
return false;
|
|
}
|
|
|
|
auto it = _widgetCache.find(macro);
|
|
if (it == _widgetCache.end()) {
|
|
return false;
|
|
}
|
|
|
|
for (auto widget : it->second) {
|
|
_contentLayout->addWidget(widget);
|
|
widget->show();
|
|
}
|
|
|
|
adjustSize();
|
|
updateGeometry();
|
|
return true;
|
|
}
|
|
|
|
void MacroSegmentList::ClearWidgetsFromCacheFor(const Macro *macro)
|
|
{
|
|
auto it = _widgetCache.find(macro);
|
|
if (it == _widgetCache.end()) {
|
|
return;
|
|
}
|
|
clearWidgetVector(it->second);
|
|
_widgetCache.erase(it);
|
|
}
|
|
|
|
void MacroSegmentList::Highlight(int idx, QColor color)
|
|
{
|
|
auto item = _contentLayout->itemAt(idx);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
auto widget = item->widget();
|
|
if (!widget) {
|
|
return;
|
|
}
|
|
HighlightWidget(widget, color, QColor(0, 0, 0, 0), true);
|
|
}
|
|
|
|
void MacroSegmentList::SetCollapsed(bool collapse) const
|
|
{
|
|
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) const
|
|
{
|
|
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)
|
|
{
|
|
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 ||
|
|
event->button() == Qt::RightButton) {
|
|
_dragPosition = GetDragIndex(event->globalPosition().toPoint());
|
|
emit SelectionChanged(_dragPosition);
|
|
} else {
|
|
_dragPosition = -1;
|
|
}
|
|
}
|
|
|
|
void MacroSegmentList::mouseMoveEvent(QMouseEvent *event)
|
|
{
|
|
if (event->buttons() & Qt::LeftButton && _dragPosition != -1) {
|
|
auto item = _contentLayout->itemAt(_dragPosition);
|
|
if (!item) {
|
|
return;
|
|
}
|
|
auto widget = item->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());
|
|
_autoScroll = true;
|
|
_autoScrollThread =
|
|
std::thread(&MacroSegmentList::CheckScroll, this);
|
|
drag->exec();
|
|
_autoScroll = false;
|
|
_autoScrollThread.join();
|
|
}
|
|
}
|
|
|
|
void MacroSegmentList::mouseReleaseEvent(QMouseEvent *)
|
|
{
|
|
_dragPosition = -1;
|
|
}
|
|
|
|
void MacroSegmentList::dragLeaveEvent(QDragLeaveEvent *)
|
|
{
|
|
HideLastDropLine();
|
|
}
|
|
|
|
void MacroSegmentList::dragEnterEvent(QDragEnterEvent *event)
|
|
{
|
|
if (event->mimeData() && event->mimeData()->hasImage()) {
|
|
event->accept();
|
|
} else {
|
|
event->ignore();
|
|
}
|
|
}
|
|
|
|
MacroSegmentEdit *MacroSegmentList::WidgetAt(int idx) const
|
|
{
|
|
if (idx < 0 || idx >= _contentLayout->count()) {
|
|
return nullptr;
|
|
}
|
|
auto item = _contentLayout->itemAt(idx);
|
|
if (!item) {
|
|
return nullptr;
|
|
}
|
|
return static_cast<MacroSegmentEdit *>(item->widget());
|
|
}
|
|
|
|
MacroSegmentEdit *MacroSegmentList::WidgetAt(const QPoint &pos) const
|
|
{
|
|
return WidgetAt(GetSegmentIndexFromPos(mapToGlobal(pos)));
|
|
}
|
|
|
|
int MacroSegmentList::IndexAt(const QPoint &pos) const
|
|
{
|
|
return GetSegmentIndexFromPos(mapToGlobal(pos));
|
|
}
|
|
|
|
void MacroSegmentList::HideLastDropLine()
|
|
{
|
|
if (_dropLineIdx >= 0 && _dropLineIdx < _contentLayout->count()) {
|
|
auto widget = WidgetAt(_dropLineIdx);
|
|
if (widget) {
|
|
widget->ShowDropLine(
|
|
MacroSegmentEdit::DropLineState::NONE);
|
|
}
|
|
}
|
|
_dropLineIdx = -1;
|
|
}
|
|
|
|
void MacroSegmentList::ClearWidgetCache()
|
|
{
|
|
for (const auto &[_, widgets] : _widgetCache) {
|
|
clearWidgetVector(widgets);
|
|
}
|
|
}
|
|
|
|
static bool isInUpperHalfOf(const QPoint &pos, const QRect &rect)
|
|
{
|
|
return QRect(rect.topLeft(),
|
|
QSize(rect.size().width(), rect.size().height() / 2))
|
|
.contains(pos);
|
|
}
|
|
|
|
static bool widgetIsInLayout(QWidget *w, QLayout *l)
|
|
{
|
|
if (w == nullptr) {
|
|
return false;
|
|
}
|
|
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::dragMoveEvent(QDragMoveEvent *event)
|
|
{
|
|
auto widget = qobject_cast<QWidget *>(event->source());
|
|
if (!widgetIsInLayout(widget, _contentLayout)) {
|
|
return;
|
|
}
|
|
|
|
_dragCursorPos = (mapToGlobal(event->position().toPoint()));
|
|
CheckDropLine(_dragCursorPos);
|
|
}
|
|
|
|
QRect MacroSegmentList::GetContentItemRectWithPadding(int idx) const
|
|
{
|
|
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)),
|
|
QSize(itemRect.size().width(),
|
|
itemRect.size().height() + _contentLayout->spacing()));
|
|
return rect;
|
|
}
|
|
|
|
void MacroSegmentList::SetVisibilityCheckEnable(bool enable)
|
|
{
|
|
_checkVisibility = enable;
|
|
|
|
if (_checkVisibility) {
|
|
QTimer::singleShot(0, this, [this]() {
|
|
SetupVisibleMacroSegmentWidgets();
|
|
});
|
|
}
|
|
}
|
|
|
|
bool MacroSegmentList::IsEmpty() const
|
|
{
|
|
return _contentLayout->count() == 0;
|
|
}
|
|
|
|
QSize MacroSegmentList::minimumSizeHint() const
|
|
{
|
|
return _stackedWidget->currentWidget()->minimumSizeHint();
|
|
}
|
|
|
|
QSize MacroSegmentList::sizeHint() const
|
|
{
|
|
const auto contentSize = _stackedWidget->currentWidget()->sizeHint();
|
|
const auto hint =
|
|
QSize(width(), contentSize.height() + 2 * frameWidth());
|
|
return hint;
|
|
}
|
|
|
|
void MacroSegmentList::SetupVisibleMacroSegmentWidgets()
|
|
{
|
|
if (!_checkVisibility) {
|
|
return;
|
|
}
|
|
|
|
const auto viewportRect = viewport()->rect();
|
|
|
|
for (auto segment : widget()->findChildren<MacroSegmentEdit *>()) {
|
|
const auto pos = segment->mapTo(viewport(), QPoint(0, 0));
|
|
const QRect rect(pos, segment->size());
|
|
if (!viewportRect.intersects(rect)) {
|
|
continue;
|
|
}
|
|
|
|
segment->SetupWidgets();
|
|
}
|
|
updateGeometry();
|
|
}
|
|
|
|
int MacroSegmentList::GetSegmentIndexFromPos(const QPoint &pos) const
|
|
{
|
|
int idx = -1;
|
|
for (int i = 0; i < _contentLayout->count(); ++i) {
|
|
if (GetContentItemRectWithPadding(i).contains(pos)) {
|
|
idx = i;
|
|
break;
|
|
}
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
void MacroSegmentList::CheckScroll() const
|
|
{
|
|
while (_autoScroll) {
|
|
const int scrollTrigger = 15;
|
|
const int scrollAmount = 1;
|
|
const QRect rect(mapToGlobal(QPoint(0, 0)), size());
|
|
const QRect upperScrollTrigger(
|
|
QPoint(rect.topLeft().x(),
|
|
rect.topLeft().y() - scrollTrigger),
|
|
QSize(rect.width(), scrollTrigger * 2));
|
|
if (upperScrollTrigger.contains(_dragCursorPos)) {
|
|
verticalScrollBar()->setValue(
|
|
verticalScrollBar()->value() - scrollAmount);
|
|
}
|
|
const QRect lowerScrollTrigger(
|
|
QPoint(rect.bottomLeft().x(),
|
|
rect.bottomLeft().y() - scrollTrigger),
|
|
QSize(rect.width(), scrollTrigger * 2));
|
|
if (lowerScrollTrigger.contains(_dragCursorPos)) {
|
|
verticalScrollBar()->setValue(
|
|
verticalScrollBar()->value() + scrollAmount);
|
|
}
|
|
std::this_thread::sleep_for(std::chrono::microseconds(50));
|
|
}
|
|
}
|
|
|
|
void MacroSegmentList::CheckDropLine(const QPoint &pos)
|
|
{
|
|
int idx = GetSegmentIndexFromPos(pos);
|
|
if (idx == _dragPosition) {
|
|
return;
|
|
}
|
|
auto action = MacroSegmentEdit::DropLineState::ABOVE;
|
|
if (idx == -1) {
|
|
if (IsInListArea(pos)) {
|
|
idx = _contentLayout->count() - 1;
|
|
action = MacroSegmentEdit::DropLineState::BELOW;
|
|
} else {
|
|
HideLastDropLine();
|
|
return;
|
|
}
|
|
} else {
|
|
auto rect = GetContentItemRectWithPadding(idx);
|
|
if (idx == _contentLayout->count() - 1 &&
|
|
!isInUpperHalfOf(pos, rect)) {
|
|
action = MacroSegmentEdit::DropLineState::BELOW;
|
|
} else {
|
|
if (!isInUpperHalfOf(pos, rect)) {
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
if (idx == _dragPosition ||
|
|
(idx - 1 == _dragPosition &&
|
|
action != MacroSegmentEdit::DropLineState::BELOW)) {
|
|
HideLastDropLine();
|
|
return;
|
|
}
|
|
auto widget = WidgetAt(idx);
|
|
if (!widget) {
|
|
HideLastDropLine();
|
|
return;
|
|
}
|
|
widget->ShowDropLine(action);
|
|
if (_dropLineIdx != idx) {
|
|
HideLastDropLine();
|
|
_dropLineIdx = idx;
|
|
}
|
|
}
|
|
|
|
bool MacroSegmentList::IsInListArea(const QPoint &pos) const
|
|
{
|
|
const QRect layoutRect(mapToGlobal(_layout->contentsRect().topLeft()),
|
|
_layout->contentsRect().size());
|
|
return layoutRect.contains(pos);
|
|
}
|
|
|
|
int MacroSegmentList::GetDropIndex(const QPoint &pos) const
|
|
{
|
|
int idx = GetSegmentIndexFromPos(pos);
|
|
if (idx == _dragPosition) {
|
|
return -1;
|
|
}
|
|
if (idx == -1) {
|
|
if (IsInListArea(pos)) {
|
|
return _contentLayout->count() - 1;
|
|
}
|
|
return -1;
|
|
}
|
|
auto rect = GetContentItemRectWithPadding(idx);
|
|
if (idx == _contentLayout->count() - 1 && !isInUpperHalfOf(pos, rect)) {
|
|
return idx;
|
|
} else if (!isInUpperHalfOf(pos, rect)) {
|
|
idx++;
|
|
}
|
|
if (_dragPosition < idx) {
|
|
idx--;
|
|
}
|
|
if (idx == _dragPosition) {
|
|
return -1;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
void MacroSegmentList::dropEvent(QDropEvent *event)
|
|
{
|
|
HideLastDropLine();
|
|
auto widget = qobject_cast<QWidget *>(event->source());
|
|
if (widget &&
|
|
!widget->geometry().contains(event->position().toPoint()) &&
|
|
widgetIsInLayout(widget, _contentLayout)) {
|
|
int dropPosition =
|
|
GetDropIndex(mapToGlobal(event->position().toPoint()));
|
|
if (dropPosition == -1) {
|
|
return;
|
|
}
|
|
emit Reorder(dropPosition, _dragPosition);
|
|
}
|
|
_dragPosition = -1;
|
|
}
|
|
|
|
void MacroSegmentList::resizeEvent(QResizeEvent *event)
|
|
{
|
|
QScrollArea::resizeEvent(event);
|
|
SetupVisibleMacroSegmentWidgets();
|
|
}
|
|
|
|
} // namespace advss
|