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:
WarmUpTill 2022-03-20 22:05:49 +01:00 committed by WarmUpTill
parent e0763a4957
commit 6a8066795b
4 changed files with 225 additions and 50 deletions

View File

@ -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;

View File

@ -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 {

View File

@ -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)) {

View File

@ -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;
}
}