Skip to content

Commit

Permalink
Re-apply Another ChartSpace Layout fix (resolved)
Browse files Browse the repository at this point in the history
.. when a spanning tile moves the layout needs to restart to
   take into account its new position (since it will displace
   other tiles).

.. to enable us to restart the layout we needed to refactor
   the updateGeometry() method to separate out the creation
   of animations from layout changes (because an item may be
   moved several times as spanners take precedence).

.. tile column numbers also need to be renumbered from 0 when
   arranging since it is possible to get out of sync as items
   are dragged around.

.. this refactor should also make it slightly easier to fix
   any other layout tweaks (now the previous issues have been
   resolved).
  • Loading branch information
liversedge committed Oct 31, 2021
1 parent 180af0a commit 5225ef0
Show file tree
Hide file tree
Showing 2 changed files with 137 additions and 63 deletions.
177 changes: 114 additions & 63 deletions src/Gui/ChartSpace.cpp
Expand Up @@ -373,39 +373,45 @@ ChartSpaceItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt, QW

}

static bool ChartSpaceItemSort(const ChartSpaceItem* left, const ChartSpaceItem* right)
{
return (left->column < right->column ? true : (left->column == right->column && left->order < right->order ? true : false));
}

// convenient way to check if an item spans across a particular column
// this is distinct from it being in that column, it must span it to the right
static bool spanned(int column, ChartSpaceItem *item)
static bool spanned(int column, LayoutChartSpaceItem item)
{
if (item->column+1 <= column && (item->column + item->span -1) >= column) return true;
if (item.column+1 <= column && (item.column + item.span -1) >= column) return true;
return false;
}

void
ChartSpace::updateGeometry()
bool LayoutChartSpaceItem::LayoutChartSpaceItemSort(const LayoutChartSpaceItem left, const LayoutChartSpaceItem right) {
return (left.column < right.column ? true : (left.column == right.column && left.order < right.order ? true : false));
}

// Layout items, refactored out of old updateGeometry code
// to isolate the before and after positioning of items from
// the animations.
QList<LayoutChartSpaceItem> ChartSpace::layoutItems()
{
// can't update geom if nothing to see.
if (items.count() == 0) return;
// remembering items that span columns
QList<QRectF> spanners;

bool animated=false;
// make a list of items to layout
QList<LayoutChartSpaceItem> items;
foreach(ChartSpaceItem *item, this->items) items << LayoutChartSpaceItem(item);

// keep a temporary list of spanning items
// so we can check overlapping as we go
QList<QRectF> spanners;
//fprintf(stderr, "BEFORE: ");
//foreach(LayoutChartSpaceItem item, items) fprintf(stderr, "%d:%d ", item.column, item.order);
//fprintf(stderr, "\n"); fflush(stderr);

// prevent a memory leak
group->stop();
delete group;
group = new QParallelAnimationGroup(this);
// we iterate when a spanner moves, its the simplest way
// to redo layout code without lots of looping code
repeatlayout:

// order the items to their positions
std::sort(items.begin(), items.end(), ChartSpaceItemSort);
std::sort(items.begin(), items.end(), LayoutChartSpaceItem::LayoutChartSpaceItemSort);

// whatever we had, we need to start again
spanners.clear();

// starting from the top
int y=SPACING;
int maxy = y;
int column=-1;
Expand All @@ -416,25 +422,30 @@ ChartSpace::updateGeometry()
// can get out of whack when last entry
// from column 0 is dragged across to the right
// bit of a hack but easier to fix here
if (items.count() > 0 && items[0]->column == 1) {
for(int i=0; i<items.count(); i++) items[i]->column--;
for(int i=0; i<columns.count()-1; i++) columns[i]=columns[i+1];
int nextcol=-1;
int lastcol=-99;
for(int i=0; i<items.count(); i++) {
if (items[i].column != lastcol) nextcol++;
lastcol=items[i].column;
items[i].column = nextcol;
}

//fprintf(stderr, "RENUMBER: ");
//foreach(LayoutChartSpaceItem item, items) fprintf(stderr, "%d:%d ", item.column, item.order);
//fprintf(stderr, "\n"); fflush(stderr);

// just set their geometry for now, no interaction
for(int i=0; i<items.count(); i++) {

if (!items[i]->isVisible()) continue; // not clear if this does anything

repeat:
// move on to next column, check if first item too
if (items[i]->column > column) {
if (items[i].column > column) {

// once past the first column we need to update x
if (column >= 0) {

// the next column is contiguous so just move on
if (items[i]->column == column+1) x+= columns[column] + SPACING; // onto next column then
if (items[i].column == column+1) x+= columns[column] + SPACING; // onto next column then
else {

// there are some empty columns, are they really empty
Expand All @@ -459,17 +470,17 @@ ChartSpace::updateGeometry()
}

// we missed some columns, there is a gap
int diff = items[i]->column - column - 1;
int diff = items[i].column - column - 1;
if (diff > 0) {
// there are empty columns so shift the cols to the right
// to the left to fill the gap left and all the column
// widths also need to move down too
for(int j=items[i]->column-1; j < 8; j++) columns[j]=columns[j+1];
for(int j=i; j<items.count();j++) items[j]->column -= diff;
for(int j=items[i].column-1; j < 8; j++) columns[j]=columns[j+1];
for(int j=i; j<items.count();j++) items[j].column -= diff;
}
}
}
y=SPACING; column = items[i]->column;
y=SPACING; column = items[i].column;

}

Expand All @@ -479,9 +490,9 @@ ChartSpace::updateGeometry()

// tile width is for the column, or for the columns it spans
int twidth = columns[column];
for(int c=1; c<items[i]->span && (c+column)<columns.count(); c++) twidth += columns[column+c] + SPACING;
for(int c=1; c<items[i].span && (c+column)<columns.count(); c++) twidth += columns[column+c] + SPACING;

int theight = items[i]->deep * ROWHEIGHT;
int theight = items[i].deep * ROWHEIGHT;

// make em smaller when configuring visual cue stolen from Windows Start Menu
int add = 0; //XXX PERFORMANCE ISSSE XXX (state == DRAG) ? (ROWHEIGHT/2) : 0;
Expand All @@ -499,46 +510,35 @@ ChartSpace::updateGeometry()
if (maxy < ty+theight+SPACING) maxy = ty+theight+SPACING;

// add to scene if new
if (!items[i]->onscene) {
scene->addItem(items[i]);
items[i]->setGeometry(tx, ty, twidth, theight);
items[i]->onscene = true;

} else if (items[i]->invisible == false &&
(items[i]->geometry().x() != tx+add ||
items[i]->geometry().y() != ty+add ||
items[i]->geometry().width() != twidth-(add*2) ||
items[i]->geometry().height() != theight-(add*2))) {

// we've got an animation to perform -- because we are moving an item
animated = true;

// add an animation for this movement
QPropertyAnimation *animation = new QPropertyAnimation(items[i], "geometry");
animation->setDuration(300);
animation->setStartValue(items[i]->geometry());
animation->setEndValue(QRect(tx+add,ty+add,twidth-(add*2),theight-(add*2))); // moving to here

// when placing a little feedback helps
if (items[i]->placing) {
animation->setEasingCurve(QEasingCurve(QEasingCurve::OutBack));
items[i]->placing = false;
} else animation->setEasingCurve(QEasingCurve(QEasingCurve::OutQuint));

group->addAnimation(animation);
if (!items[i].onscene) items[i].geometry = QRectF(tx, ty, twidth, theight);
else if ((items[i].geometry.x() != tx+add ||
items[i].geometry.y() != ty+add ||
items[i].geometry.width() != twidth-(add*2) ||
items[i].geometry.height() != theight-(add*2))) {

items[i].geometry = QRect(tx+add,ty+add,twidth-(add*2),theight-(add*2));

// when we move a spanner the impact needs to be
// addressed against all items, so sadly we need
// to start from scratch.
if (items[i].span > 1) goto repeatlayout;
}

// add us to spanners, so next tiles interaction
if (items[i]->span > 1) {
if (items[i]->drag) spanners << QRectF(tx,ty,twidth-1,theight-1);
else spanners << items[i]->geometry();
if (items[i].span > 1) {
if (items[i].drag) spanners << QRectF(tx,ty,twidth-1,theight-1);
else spanners << items[i].geometry;
}

// set spot for next tile
y = ty + theight + SPACING;

}

//fprintf(stderr, "RESIZED: ");
//foreach(LayoutChartSpaceItem item, items) fprintf(stderr, "%d:%d ", item.column, item.order);
//fprintf(stderr, "\n"); fflush(stderr);

// set the scene rectangle, columns start at 0
// bearing in mind we may have a spanner that extends across
// columns, so lets check that too?
Expand All @@ -548,6 +548,57 @@ ChartSpace::updateGeometry()
}
sceneRect = QRectF(0, 0, x + SPACING, maxy);

return items;
}

void
ChartSpace::updateGeometry()
{
// can't update geom if nothing to see.
if (items.count() == 0) return;

bool animated=false;

// prevent a memory leak
group->stop();
delete group;
group = new QParallelAnimationGroup(this);

foreach(LayoutChartSpaceItem item, layoutItems()) {

item.item->column = item.column;

// add to scene if new
if (!item.onscene) {
scene->addItem(item.item);
item.item->setGeometry(item.geometry);
item.item->onscene = true;

} else if (item.invisible == false && item.geometry != item.item->geometry()) {

// we've got an animation to perform -- because we are moving an item
animated = true;

// add an animation for this movement
QPropertyAnimation *animation = new QPropertyAnimation(item.item, "geometry");
animation->setDuration(300);
animation->setStartValue(item.item->geometry());
animation->setEndValue(item.geometry); // moving to here

// when placing a little feedback helps
if (item.item->placing) {
animation->setEasingCurve(QEasingCurve(QEasingCurve::OutBack));
item.item->placing = false;
} else animation->setEasingCurve(QEasingCurve(QEasingCurve::OutQuint));

group->addAnimation(animation);
}
}

//fprintf(stderr, "AFTER: ");
//foreach(ChartSpaceItem *item, items) fprintf(stderr, "%d:%d ", item->column, item->order);
//fprintf(stderr, "\n"); fflush(stderr);

if (animated) group->start();
}

Expand Down
23 changes: 23 additions & 0 deletions src/Gui/ChartSpace.h
Expand Up @@ -157,6 +157,28 @@ class ChartSpaceItem : public QGraphicsWidget
void geometryChanged();
};

// we copy the current items and manage their layout (which is an iterative process)
// and once the layout is done we create an animation to move from the current positions
// to the new positions using this class to temporarily store new co-ordinates
class LayoutChartSpaceItem {

public:
LayoutChartSpaceItem(ChartSpaceItem *from) :
column(from->column), span(from->span), order (from->order ), deep(from->deep), onscene(from->onscene),
placing(from->placing), drag(from->drag), incorner(from->incorner), invisible(from->invisible),
item(from), geometry(from->geometry()) {}

static bool LayoutChartSpaceItemSort(const LayoutChartSpaceItem left, const LayoutChartSpaceItem right);

int column, span, order, deep;
bool onscene, placing, drag;
bool incorner;
bool invisible;

ChartSpaceItem *item;
QRectF geometry;
};

class ChartSpace : public QWidget
{
Q_OBJECT
Expand Down Expand Up @@ -228,6 +250,7 @@ class ChartSpace : public QWidget

// set geometry on the widgets (size and pos)
void updateGeometry();
QList<LayoutChartSpaceItem> layoutItems(); // moves geom and lays out items

// set scale, zoom etc appropriately
void updateView();
Expand Down

0 comments on commit 5225ef0

Please sign in to comment.