Refactored the placement of items in the ChartSpace (#4549)

Fixes #4467:
* Tiles are dragged to a column, inserted into the columns tile order
* Placing tiles left or right of the existing columns creates a new
  column (up to a fixed limit)
* As there is no concept of lines, the placement must
  * always respect the users choice of a column
  * never move a tile horizontally without user interaction
* Shift-resizing a tile allows spanning multiple columns
* Improved change detection
* Preventing superfluous layouting requests and order increments
* Added a visual indicator where the item will be placed when dropped
* Fixed a glitch when creating new columns on the right, when dragging
  beyond a newly created column on the right, the visual indicator was
  hidden and pseudo changes where detected.
This commit is contained in:
Joachim Kohlhammer
2024-10-13 23:33:08 +02:00
committed by GitHub
parent 62ef8abe42
commit a30b96cc8a
2 changed files with 150 additions and 97 deletions

View File

@@ -609,6 +609,18 @@ ChartSpace::updateGeometry()
} else animation->setEasingCurve(QEasingCurve(QEasingCurve::OutQuint));
group->addAnimation(animation);
} else if (item.invisible) {
if (landingzone == nullptr) {
QColor bgColor = GColor(CCARDBACKGROUND);
bgColor.setAlpha(200);
QPen pen(bgColor);
pen.setWidth(10 * dpiXFactor);
bgColor.setAlpha(100);
QBrush brush(bgColor);
landingzone = scene->addRect(item.geometry, pen, brush);
} else if (landingzone->rect() != item.geometry) {
landingzone->setRect(item.geometry);
}
}
}
@@ -1012,10 +1024,15 @@ ChartSpace::eventFilter(QObject *, QEvent *event)
}
}
} else if (event->type() == QEvent::GraphicsSceneMouseRelease) {
} else if (event->type() == QEvent::GraphicsSceneMouseRelease) {
// stop dragging
if (state == DRAG || state == YRESIZE || state == SPAN || state == XRESIZE) {
if (landingzone != nullptr) {
scene->removeItem(landingzone);
delete landingzone;
landingzone = nullptr;
}
// we want this one
event->accept();
@@ -1114,126 +1131,161 @@ ChartSpace::eventFilter(QObject *, QEvent *event)
}
} else if (state == DRAG && !scrolling) { // dragging?
// whilst mouse moves, only update geom when changed
bool changed = false;
// move the ChartSpaceItem being dragged
stateData.drag.item->setPos(pos.x()-stateData.drag.offx, pos.y()-stateData.drag.offy);
stateData.drag.item->setPos(pos.x() - stateData.drag.offx, pos.y() - stateData.drag.offy);
// should I move?
QList<QGraphicsItem *> overlaps;
foreach(QGraphicsItem *p, scene->items(pos))
if(items.contains(static_cast<ChartSpaceItem*>(p)))
overlaps << p;
// we always overlap with ourself, so see if more
if (overlaps.count() > 1) {
ChartSpaceItem *over = static_cast<ChartSpaceItem*>(overlaps[1]);
if (pos.y()-over->geometry().y() > over->geometry().height()/2) {
// place below the one its over
stateData.drag.item->column = over->column;
stateData.drag.item->order = over->order+1;
for(int i=items.indexOf(over); i< items.count(); i++) {
if (i>=0 && items[i]->column == over->column && items[i]->order > over->order && items[i] != stateData.drag.item) {
items[i]->order += 1;
changed = true;
}
}
} else {
// place above the one its over
stateData.drag.item->column = over->column;
stateData.drag.item->order = over->order;
for(int i=0; i< items.count(); i++) {
if (i>=0 && items[i]->column == over->column && items[i]->order >= (over->order) && items[i] != stateData.drag.item) {
items[i]->order += 1;
changed = true;
}
}
}
} else {
// columns are now variable width
// create a new column to the right?
int x=SPACING;
// Nothing to do when mouse is over the landingzone
if ( landingzone == nullptr
|| ! landingzone->contains(pos)) {
// What is my target column? Because of spans we can't rely only on overlaps
int x = SPACING;
int targetcol = -1;
for(int i=0; i<10; i++) {
if (pos.x() > x && pos.x() < (x+columns[i]+SPACING)) {
for (int i = 0; i < 10; i++) {
if (pos.x() > x && pos.x() < (x + columns[i] + SPACING)) {
targetcol = i;
break;
}
x += columns[i]+SPACING;
x += columns[i] + SPACING;
}
if (items.last()->column < 9 && targetcol < 0) {
// don't keep moving - if we're already alone in column 0 then no move is needed
if (stateData.drag.item->column != 0 || (items.count()>1 && items[1]->column == 0)) {
// new col to left
for(int i=0; i< items.count(); i++) items[i]->column += 1;
stateData.drag.item->column = 0;
stateData.drag.item->order = 0;
// shift columns widths to the right
for(int i=9; i>0; i--) columns[i] = columns[i-1];
columns[0] = stateData.drag.width;
changed = true;
// Do we overlap?
ChartSpaceItem *over = nullptr;
for (QGraphicsItem *p : scene->items(pos)) {
if (p != landingzone) {
ChartSpaceItem *testItem = static_cast<ChartSpaceItem*>(p);
if (items.contains(testItem) && testItem != stateData.drag.item && testItem->column == targetcol) {
over = testItem;
break;
}
}
}
} else if (targetcol > 0 && targetcol <= 9 && columnCount(targetcol) == 0) {
// Find
// * the highest column in active use
// * all items currently placed in targetcol (excluding the dragged item)
// This excludes items "only" spanning over this column
QList<ChartSpaceItem*> columnItems;
int highestcol = -1;
for (ChartSpaceItem *item : items) {
if (item->column == targetcol && item != stateData.drag.item) {
columnItems << item;
}
highestcol = std::max(highestcol, item->column + item->span - 1);
}
std::sort(columnItems.begin(), columnItems.end(), LayoutChartSpaceItem::LayoutChartSpaceItemSort);
// we are over an empty column
stateData.drag.item->column = targetcol;
stateData.drag.item->order = 0;
// make column width same as source width
columns[stateData.drag.item->column] = stateData.drag.width;
changed = true;
} else if (items.last()->column < 9 && items.last() && items.last()->column < targetcol) {
// new col to the right
stateData.drag.item->column = items.last()->column + 1;
stateData.drag.item->order = 0;
// make column width same as source width
columns[stateData.drag.item->column] = stateData.drag.width;
changed = true;
// Correct targetcol if it exceeds highestcol with a (then) empty highestcol
targetcol = std::min(targetcol, highestcol + 1);
if ( highestcol >= 0
&& targetcol > highestcol
&& columnCount(highestcol) == 1
&& stateData.drag.item->column == highestcol) {
targetcol = highestcol;
}
if (over != nullptr) {
if (pos.y() - over->geometry().y() > over->geometry().height() / 2) {
// place below the one its over
if ( stateData.drag.item->column != over->column
|| stateData.drag.item->order <= over->order) {
stateData.drag.item->column = over->column;
stateData.drag.item->order = over->order + 1;
changed = true;
for (int i = columnItems.indexOf(over); i < columnItems.count(); i++) {
if ( columnItems[i]->order > over->order
&& columnItems[i] != stateData.drag.item) {
columnItems[i]->order += 1;
}
}
}
} else {
// place above the one its over
if ( stateData.drag.item->column != over->column
|| stateData.drag.item->order >= over->order) {
stateData.drag.item->column = over->column;
stateData.drag.item->order = over->order;
changed = true;
for (ChartSpaceItem *colItem : columnItems) {
if ( colItem->order >= (over->order)
&& colItem != stateData.drag.item) {
colItem->order += 1;
}
}
}
}
} else {
if (targetcol < 0) {
// create a new column to the right?
if (highestcol < 9 && columnCount(0) > 1) {
// Drop as new first column
// don't keep moving - if we're already alone in column 0 then no move is needed
// new col to left
for (ChartSpaceItem *item : items) {
item->column += 1;
}
stateData.drag.item->column = 0;
stateData.drag.item->order = 0;
// add to the end of the column
int last = -1;
for(int i=0; i<items.count() && items[i]->column <= targetcol; i++) {
if (items[i]->column == targetcol) last=i;
// shift columns widths to the right
for (int i = 9; i > 0; i--) {
columns[i] = columns[i - 1];
}
columns[0] = stateData.drag.width;
changed = true;
}
} else if (targetcol <= 9) {
if (columnItems.count() > 0) {
if (pos.y() > columnItems.last()->geometry().bottom()) {
// Append below last
if ( stateData.drag.item->column != targetcol
|| ( stateData.drag.item->column == targetcol
&& stateData.drag.item->order <= columnItems.last()->order)) {
stateData.drag.item->column = targetcol;
stateData.drag.item->order = columnItems.last()->order + 1;
changed = true;
}
} else {
// Insert above item
for (ChartSpaceItem *colItem : columnItems) {
if (! changed && pos.y() < colItem->geometry().top()) {
if ( stateData.drag.item->column != targetcol
|| ( stateData.drag.item->column == targetcol
&& stateData.drag.item->order >= colItem->order)) {
stateData.drag.item->column = targetcol;
stateData.drag.item->order = colItem->order;
changed = true;
}
if (! changed) {
break;
}
}
if (changed) {
++colItem->order;
}
}
}
} else {
// Add as first item
if ( stateData.drag.item->column != targetcol
|| stateData.drag.item->order != 0) {
stateData.drag.item->column = targetcol;
stateData.drag.item->order = 0;
changed = true;
}
}
}
// so long as its been dragged below the last entry on the column !
if (last >= 0 && pos.y() > items[last]->geometry().bottom()) {
stateData.drag.item->column = targetcol;
stateData.drag.item->order = items[last]->order+1;
}
changed = true;
}
}
if (changed) {
if (changed || landingzone == nullptr) {
// drop it down
updateGeometry();
updateView();
}
} else if (state == YRESIZE) {
// resize in rows, so in 75px units

View File

@@ -323,7 +323,8 @@ class ChartSpace : public QWidget
// content
QVector<int> columns; // column widths
QList<ChartSpaceItem*> items; // tiles
QList<ChartSpaceItem*> items; // tiles
QGraphicsRectItem *landingzone = nullptr; // marker where the dragged ChartSpaceItem will be placed
// state data
bool yresizecursor; // is the cursor set to resize?