mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-04-21 01:27:24 -05:00
Improve drag & drop behaviour of macro segments
- Depending on the drop position you can either drop before or after a widget - Added a visual indicator where the macro segment will be dropped
This commit is contained in:
parent
e0763a4957
commit
6a8066795b
|
|
@ -31,15 +31,22 @@ protected:
|
|||
void mousePressEvent(QMouseEvent *event);
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
void mouseReleaseEvent(QMouseEvent *event);
|
||||
void dragLeaveEvent(QDragLeaveEvent *event);
|
||||
void dragEnterEvent(QDragEnterEvent *event);
|
||||
void dragMoveEvent(QDragMoveEvent *event);
|
||||
void dropEvent(QDropEvent *event);
|
||||
|
||||
private:
|
||||
int GetIndex(QPoint);
|
||||
int GetDropIndex(QPoint);
|
||||
int GetDragIndex(const QPoint &);
|
||||
int GetDropIndex(const QPoint &);
|
||||
int GetWidgetIdx(const QPoint &);
|
||||
bool IsInListArea(const QPoint &);
|
||||
QRect GetContentItemRectWithPadding(int idx);
|
||||
void HideLastDropLine();
|
||||
MacroSegmentEdit *WidgetAt(int idx);
|
||||
|
||||
int _dragPosition = -1;
|
||||
int _dropLineIdx = -1;
|
||||
|
||||
QVBoxLayout *_layout;
|
||||
QVBoxLayout *_contentLayout;
|
||||
|
|
|
|||
|
|
@ -63,15 +63,38 @@ protected:
|
|||
Section *_section;
|
||||
QLabel *_headerInfo;
|
||||
QWidget *_frame;
|
||||
QFrame *_noBorderframe;
|
||||
QFrame *_borderFrame;
|
||||
QVBoxLayout *_contentLayout;
|
||||
|
||||
private:
|
||||
enum class DropLineState {
|
||||
NONE,
|
||||
ABOVE,
|
||||
BELOW,
|
||||
};
|
||||
|
||||
virtual MacroSegment *Data() = 0;
|
||||
void ShowDropLine(DropLineState);
|
||||
|
||||
// The reason for using two separate frame widget each with their own
|
||||
// stylesheet and changing their visibility vs. using a single frame
|
||||
// and changing the stylesheet at runtime is that the operation of
|
||||
// adjusting the stylesheet is very expensive and can take multiple
|
||||
// hundred milliseconds per widget.
|
||||
// This performance impact would hurt in areas like drag and drop or
|
||||
// emitting the "SelectionChanged" signal.
|
||||
QFrame *_noBorderframe;
|
||||
QFrame *_borderFrame;
|
||||
|
||||
// In most cases the line above the widget will be used.
|
||||
// The lower one will only be used if the segment is the last one in
|
||||
// the list.
|
||||
QFrame *_dropLineAbove;
|
||||
QFrame *_dropLineBelow;
|
||||
|
||||
bool _showHighlight;
|
||||
QTimer _timer;
|
||||
|
||||
friend class MacroSegmentList;
|
||||
};
|
||||
|
||||
class MouseWheelWidgetAdjustmentGuard : public QObject {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ MacroSegmentList::MacroSegmentList(QWidget *parent)
|
|||
{
|
||||
_helpMsg->setWordWrap(true);
|
||||
_helpMsg->setAlignment(Qt::AlignCenter);
|
||||
|
||||
_contentLayout->setSpacing(0);
|
||||
auto helperLayout = new QGridLayout();
|
||||
helperLayout->addWidget(_helpMsg, 0, 0,
|
||||
Qt::AlignHCenter | Qt::AlignVCenter);
|
||||
|
|
@ -33,7 +33,7 @@ MacroSegmentList::MacroSegmentList(QWidget *parent)
|
|||
setAcceptDrops(true);
|
||||
}
|
||||
|
||||
int MacroSegmentList::GetIndex(QPoint pos)
|
||||
int MacroSegmentList::GetDragIndex(const QPoint &pos)
|
||||
{
|
||||
for (int idx = 0; idx < _contentLayout->count(); ++idx) {
|
||||
auto item = _contentLayout->itemAt(idx);
|
||||
|
|
@ -49,7 +49,7 @@ int MacroSegmentList::GetIndex(QPoint pos)
|
|||
mapToGlobal(QPoint(geo.topLeft().x(),
|
||||
geo.topLeft().y() - scrollOffset)),
|
||||
geo.size());
|
||||
if (rect.contains(pos) && idx != _dragPosition) {
|
||||
if (rect.contains(pos)) {
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
|
@ -147,7 +147,7 @@ bool MacroSegmentList::eventFilter(QObject *object, QEvent *event)
|
|||
void MacroSegmentList::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
_dragPosition = GetIndex(event->globalPos());
|
||||
_dragPosition = GetDragIndex(event->globalPos());
|
||||
emit SelectionChagned(_dragPosition);
|
||||
} else {
|
||||
_dragPosition = -1;
|
||||
|
|
@ -181,6 +181,11 @@ void MacroSegmentList::mouseReleaseEvent(QMouseEvent *)
|
|||
_dragPosition = -1;
|
||||
}
|
||||
|
||||
void MacroSegmentList::dragLeaveEvent(QDragLeaveEvent *)
|
||||
{
|
||||
HideLastDropLine();
|
||||
}
|
||||
|
||||
void MacroSegmentList::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if (event->mimeData() && event->mimeData()->hasImage()) {
|
||||
|
|
@ -190,6 +195,103 @@ void MacroSegmentList::dragEnterEvent(QDragEnterEvent *event)
|
|||
}
|
||||
}
|
||||
|
||||
MacroSegmentEdit *MacroSegmentList::WidgetAt(int idx)
|
||||
{
|
||||
if (idx < 0 || idx >= _contentLayout->count()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto item = _contentLayout->itemAt(idx);
|
||||
if (!item) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<MacroSegmentEdit *>(item->widget());
|
||||
}
|
||||
|
||||
void MacroSegmentList::HideLastDropLine()
|
||||
{
|
||||
if (_dropLineIdx >= 0 && _dropLineIdx < _contentLayout->count()) {
|
||||
auto widget = WidgetAt(_dropLineIdx);
|
||||
if (widget) {
|
||||
widget->ShowDropLine(
|
||||
MacroSegmentEdit::DropLineState::NONE);
|
||||
}
|
||||
}
|
||||
_dropLineIdx = -1;
|
||||
}
|
||||
|
||||
bool isInUpperHalfOf(const QPoint &pos, const QRect &rect)
|
||||
{
|
||||
return QRect(rect.topLeft(),
|
||||
QSize(rect.size().width(), rect.size().height() / 2))
|
||||
.contains(pos);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
const QPoint pos(mapToGlobal(event->pos()));
|
||||
int idx = GetWidgetIdx(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;
|
||||
}
|
||||
}
|
||||
|
||||
QRect MacroSegmentList::GetContentItemRectWithPadding(int idx)
|
||||
{
|
||||
auto item = _contentLayout->itemAt(idx);
|
||||
|
|
@ -202,21 +304,18 @@ QRect MacroSegmentList::GetContentItemRectWithPadding(int idx)
|
|||
}
|
||||
const QRect itemRect = item->geometry().marginsAdded(
|
||||
_contentLayout->contentsMargins());
|
||||
const QRect rect(mapToGlobal(QPoint(itemRect.topLeft().x(),
|
||||
itemRect.topLeft().y() -
|
||||
_contentLayout->spacing() -
|
||||
scrollOffset)),
|
||||
itemRect.size());
|
||||
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;
|
||||
}
|
||||
|
||||
int MacroSegmentList::GetDropIndex(QPoint pos)
|
||||
int MacroSegmentList::GetWidgetIdx(const 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)) {
|
||||
|
|
@ -224,31 +323,46 @@ int MacroSegmentList::GetDropIndex(QPoint pos)
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (idx == -1) {
|
||||
idx = _contentLayout->count() - 1;
|
||||
}
|
||||
if (idx != _dragPosition) {
|
||||
return idx;
|
||||
}
|
||||
return -1;
|
||||
return idx;
|
||||
}
|
||||
|
||||
bool widgetIsInLayout(QWidget *w, QLayout *l)
|
||||
bool MacroSegmentList::IsInListArea(const QPoint &pos)
|
||||
{
|
||||
for (int i = 0; i < l->count(); ++i) {
|
||||
auto item = l->itemAt(i);
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
if (item->widget() == w) {
|
||||
return true;
|
||||
}
|
||||
const QRect layoutRect(mapToGlobal(_layout->contentsRect().topLeft()),
|
||||
_layout->contentsRect().size());
|
||||
return layoutRect.contains(pos);
|
||||
}
|
||||
|
||||
int MacroSegmentList::GetDropIndex(const QPoint &pos)
|
||||
{
|
||||
int idx = GetWidgetIdx(pos);
|
||||
if (idx == _dragPosition) {
|
||||
return -1;
|
||||
}
|
||||
return false;
|
||||
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->pos()) &&
|
||||
widgetIsInLayout(widget, _contentLayout)) {
|
||||
|
|
|
|||
|
|
@ -63,15 +63,15 @@ MacroSegmentEdit::MacroSegmentEdit(bool highlight, QWidget *parent)
|
|||
_contentLayout(new QVBoxLayout),
|
||||
_frame(new QWidget),
|
||||
_borderFrame(new QFrame),
|
||||
_noBorderframe(new QFrame)
|
||||
_noBorderframe(new QFrame),
|
||||
_dropLineAbove(new QFrame),
|
||||
_dropLineBelow(new QFrame)
|
||||
{
|
||||
// The reason for using two separate frame widget each with their own
|
||||
// stylesheet and changing their visibility vs. using a single frame
|
||||
// and changing the stylesheet at runtime is that the operation of
|
||||
// adjusting the stylesheet is very expensive and can take multiple
|
||||
// hundred milliseconds per widget.
|
||||
// This performance impact would hurt in areas like drag and drop or
|
||||
// emitting the "SelectionChanged" signal.
|
||||
_dropLineAbove->setLineWidth(3);
|
||||
_dropLineAbove->setFixedHeight(11);
|
||||
_dropLineBelow->setLineWidth(3);
|
||||
_dropLineBelow->setFixedHeight(11);
|
||||
|
||||
_borderFrame->setObjectName("border");
|
||||
_borderFrame->setStyleSheet("#border {"
|
||||
"border-color: rgba(0, 0, 0, 255);"
|
||||
|
|
@ -105,7 +105,6 @@ MacroSegmentEdit::MacroSegmentEdit(bool highlight, QWidget *parent)
|
|||
// the edit areas
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
// Signal handling
|
||||
QWidget::connect(_section, &Section::Collapsed, this,
|
||||
&MacroSegmentEdit::Collapsed);
|
||||
// Macro signals
|
||||
|
|
@ -127,14 +126,21 @@ MacroSegmentEdit::MacroSegmentEdit(bool highlight, QWidget *parent)
|
|||
SIGNAL(SceneGroupRenamed(const QString &, const QString)), this,
|
||||
SIGNAL(SceneGroupRenamed(const QString &, const QString)));
|
||||
|
||||
// Frame layout
|
||||
auto layout = new QGridLayout;
|
||||
auto frameLayout = new QGridLayout;
|
||||
frameLayout->setContentsMargins(0, 0, 0, 0);
|
||||
frameLayout->addLayout(_contentLayout, 0, 0);
|
||||
frameLayout->addWidget(_noBorderframe, 0, 0);
|
||||
frameLayout->addWidget(_borderFrame, 0, 0);
|
||||
auto layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addLayout(_contentLayout, 0, 0);
|
||||
layout->addWidget(_noBorderframe, 0, 0);
|
||||
layout->addWidget(_borderFrame, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(_dropLineAbove);
|
||||
layout->addLayout(frameLayout);
|
||||
layout->addWidget(_dropLineBelow);
|
||||
_frame->setLayout(layout);
|
||||
|
||||
SetSelected(false);
|
||||
ShowDropLine(DropLineState::NONE);
|
||||
|
||||
_timer.setInterval(1500);
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(Highlight()));
|
||||
|
|
@ -194,3 +200,28 @@ void MacroSegmentEdit::SetSelected(bool selected)
|
|||
_borderFrame->setVisible(selected);
|
||||
_noBorderframe->setVisible(!selected);
|
||||
}
|
||||
|
||||
void MacroSegmentEdit::ShowDropLine(DropLineState state)
|
||||
{
|
||||
switch (state) {
|
||||
case MacroSegmentEdit::DropLineState::NONE:
|
||||
_dropLineAbove->setFrameShadow(QFrame::Plain);
|
||||
_dropLineAbove->setFrameShape(QFrame::NoFrame);
|
||||
_dropLineBelow->hide();
|
||||
break;
|
||||
case MacroSegmentEdit::DropLineState::ABOVE:
|
||||
_dropLineAbove->setFrameShadow(QFrame::Sunken);
|
||||
_dropLineAbove->setFrameShape(QFrame::Panel);
|
||||
_dropLineBelow->hide();
|
||||
break;
|
||||
case MacroSegmentEdit::DropLineState::BELOW:
|
||||
_dropLineAbove->setFrameShadow(QFrame::Plain);
|
||||
_dropLineAbove->setFrameShape(QFrame::NoFrame);
|
||||
_dropLineBelow->setFrameShadow(QFrame::Sunken);
|
||||
_dropLineBelow->setFrameShape(QFrame::Panel);
|
||||
_dropLineBelow->show();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user