Skip to content

Commit

Permalink
Refactor|UI|Client: GridLayout can give out column left/right edge rules
Browse files Browse the repository at this point in the history
This allows a popup menu with multiple to columns to set the item
hit rules and highlight rectangles appropriately.
  • Loading branch information
skyjake committed Aug 28, 2013
1 parent 86cea87 commit dbf8aa0
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 30 deletions.
16 changes: 15 additions & 1 deletion doomsday/client/include/ui/widgets/gridlayout.h
Expand Up @@ -44,10 +44,12 @@ class GridLayout

void clear();

void setLeftTop(de::Rule const &left, de::Rule const &top);
void setMode(Mode mode);
void setGridSize(int numCols, int numRows);
void setModeAndGridSize(Mode mode, int numCols, int numRows);
void setColumnAlignment(int column, ui::Alignment cellAlign);

void setLeftTop(de::Rule const &left, de::Rule const &top);
void setOverrideWidth(de::Rule const &width);
void setOverrideHeight(de::Rule const &height);
void setColumnPadding(de::Rule const &gap);
Expand Down Expand Up @@ -75,8 +77,20 @@ class GridLayout
*/
de::Vector2i gridSize() const;

/**
* Determines the cell coordinates of a particular widget. The widget
* must be among the widgets previously added to the layout.
*
* @param widget Widget to look up.
*
* @return Cell coordinates.
*/
de::Vector2i widgetPos(GuiWidget &widget) const;

de::Rule const &width() const;
de::Rule const &height() const;
de::Rule const &columnLeft(int col) const;
de::Rule const &columnRight(int col) const;
de::Rule const &columnWidth(int col) const;
de::Rule const &rowHeight(int row) const;
de::Rule const &overrideWidth() const;
Expand Down
14 changes: 13 additions & 1 deletion doomsday/client/include/ui/widgets/menuwidget.h
Expand Up @@ -69,9 +69,11 @@ class MenuWidget : public ScrollAreaWidget
* @param columnPolicy Policy for sizing columns.
* @param rows Number of rows in the grid.
* @param rowPolicy Policy for sizing rows.
* @param layoutMode Layout mode (column or row first).
*/
void setGridSize(int columns, ui::SizePolicy columnPolicy,
int rows, ui::SizePolicy rowPolicy);
int rows, ui::SizePolicy rowPolicy,
GridLayout::Mode layoutMode = GridLayout::ColumnFirst);

ui::Data &items();

Expand All @@ -93,6 +95,16 @@ class MenuWidget : public ScrollAreaWidget
*/
int count() const;

/**
* Determines if a widget is included in the menu. Hidden widgets are not
* part of the menu.
*
* @param widget Widget.
*
* @return @c true, if the widget is laid out as part of the menu.
*/
bool isWidgetPartOfMenu(de::Widget const &widget) const;

/**
* Lays out children of the menu according to the grid setup. This should
* be called if children are manually added or removed from the menu.
Expand Down
113 changes: 95 additions & 18 deletions doomsday/client/src/ui/widgets/gridlayout.cpp
Expand Up @@ -40,17 +40,22 @@ DENG2_PIMPL(GridLayout)
struct Metric {
Rule const *current; ///< Current size of column/row (replaced many times).
IndirectRule *final; ///< Final size of column/row (for others to use).
Rule const *accum; ///< Sum of sizes of previous columns/rows (for others to use).
Rule const *accumulatedLengths; ///< Sum of sizes of previous columns/rows (for others to use).
Rule const *minEdge; ///< Rule for the left/top edge.
Rule const *maxEdge; ///< Rule for the right/bottom edge.
ui::Alignment cellAlign;///< Cell alignment affecting the entire column/row.

Metric() : current(0), final(new IndirectRule), accum(0), cellAlign(ui::AlignLeft)
Metric() : current(0), final(new IndirectRule), accumulatedLengths(0), minEdge(0), maxEdge(0),
cellAlign(ui::AlignLeft)
{}

~Metric()
{
releaseRef(current);
releaseRef(final);
releaseRef(accum);
releaseRef(accumulatedLengths);
releaseRef(minEdge);
releaseRef(maxEdge);
}
};
typedef QList<Metric *> Metrics;
Expand Down Expand Up @@ -158,7 +163,7 @@ DENG2_PIMPL(GridLayout)
Metric *m = new Metric;
for(int i = 0; i < list.size(); ++i)
{
sumInto(m->accum, *list[i]->final);
sumInto(m->accumulatedLengths, *list[i]->final);
}
list << m;
}
Expand All @@ -180,35 +185,48 @@ DENG2_PIMPL(GridLayout)
metric.final->setSource(*metric.current);
}

Rule const &columnLeftX(int col) const // refless
Rule const &columnLeftX(int col)
{
Rule const *base = holdRef(initialX);
if(col > 0)
if(!cols.at(col)->minEdge)
{
if(colPad) changeRef(base, *base + *colPad * col);
sumInto(base, *cols.at(col)->accum);
Rule const *base = holdRef(initialX);
if(col > 0)
{
if(colPad) changeRef(base, *base + *colPad * col);
sumInto(base, *cols.at(col)->accumulatedLengths);
}
cols[col]->minEdge = base;
}
return *refless(base);
return *cols.at(col)->minEdge;
}

Rule const &columnRightX(int col) const // refless
Rule const &columnRightX(int col)
{
if(col < cols.size() - 1)
{
return columnLeftX(col + 1);
}
return columnLeftX(col) + *cols.last()->final;

if(!cols.at(col)->maxEdge)
{
cols[col]->maxEdge = holdRef(columnLeftX(col) + *cols.last()->final);
}
return *cols.at(col)->maxEdge;
}

Rule const &rowTopY(int row) const // refless
Rule const &rowTopY(int row) const
{
Rule const *base = holdRef(initialY);
if(row > 0)
if(!rows.at(row)->minEdge)
{
if(rowPad) changeRef(base, *base + *rowPad * row);
sumInto(base, *rows.at(row)->accum);
Rule const *base = holdRef(initialY);
if(row > 0)
{
if(rowPad) changeRef(base, *base + *rowPad * row);
sumInto(base, *rows.at(row)->accumulatedLengths);
}
rows[row]->minEdge = base;
}
return *refless(base);
return *rows.at(row)->minEdge;
}

/**
Expand Down Expand Up @@ -402,6 +420,14 @@ void GridLayout::clear()
d->clear();
}

void GridLayout::setMode(GridLayout::Mode mode)
{
DENG2_ASSERT(isEmpty());

d->mode = mode;
d->setup(d->maxCols, d->maxRows);
}

void GridLayout::setLeftTop(Rule const &left, Rule const &top)
{
DENG2_ASSERT(isEmpty());
Expand All @@ -422,6 +448,14 @@ void GridLayout::setGridSize(int numCols, int numRows)
d->setup(numCols, numRows);
}

void GridLayout::setModeAndGridSize(GridLayout::Mode mode, int numCols, int numRows)
{
DENG2_ASSERT(isEmpty());

d->mode = mode;
setGridSize(numCols, numRows);
}

void GridLayout::setColumnAlignment(int column, ui::Alignment cellAlign)
{
DENG2_ASSERT(column >= 0 && column < d->cols.size());
Expand All @@ -440,11 +474,13 @@ void GridLayout::setOverrideHeight(Rule const &height)

void GridLayout::setColumnPadding(Rule const &gap)
{
DENG2_ASSERT(isEmpty());
changeRef(d->colPad, gap);
}

void GridLayout::setRowPadding(Rule const &gap)
{
DENG2_ASSERT(isEmpty());
changeRef(d->rowPad, gap);
}

Expand Down Expand Up @@ -493,6 +529,35 @@ Vector2i GridLayout::gridSize() const
return d->gridSize();
}

Vector2i GridLayout::widgetPos(GuiWidget &widget) const
{
Vector2i pos;
foreach(Widget *w, d->widgets)
{
if(w == &widget) return pos;

switch(d->mode)
{
case ColumnFirst:
if(++pos.x >= d->maxCols)
{
pos.x = 0;
++pos.y;
}
break;

case RowFirst:
if(++pos.y >= d->maxRows)
{
pos.y = 0;
++pos.x;
}
break;
}
}
return Vector2i(-1, -1);
}

Rule const &GridLayout::width() const
{
d->updateTotal();
Expand All @@ -505,6 +570,18 @@ Rule const &GridLayout::height() const
return *d->totalHeight;
}

Rule const &GridLayout::columnLeft(int col) const
{
DENG2_ASSERT(col >= 0 && col < d->cols.size());
return d->columnLeftX(col);
}

Rule const &GridLayout::columnRight(int col) const
{
DENG2_ASSERT(col >= 0 && col < d->cols.size());
return d->columnRightX(col);
}

Rule const &GridLayout::columnWidth(int col) const
{
DENG2_ASSERT(col >= 0 && col < d->cols.size());
Expand Down
11 changes: 9 additions & 2 deletions doomsday/client/src/ui/widgets/menuwidget.cpp
Expand Up @@ -261,10 +261,11 @@ MenuWidget::MenuWidget(String const &name)
{}

void MenuWidget::setGridSize(int columns, ui::SizePolicy columnPolicy,
int rows, ui::SizePolicy rowPolicy)
int rows, ui::SizePolicy rowPolicy,
GridLayout::Mode layoutMode)
{
d->layout.clear();
d->layout.setGridSize(columns, rows);
d->layout.setModeAndGridSize(layoutMode, columns, rows);
d->layout.setLeftTop(contentRule().left(), contentRule().top());

d->colPolicy = columnPolicy;
Expand Down Expand Up @@ -305,6 +306,12 @@ int MenuWidget::count() const
return d->countVisible();
}

bool MenuWidget::isWidgetPartOfMenu(Widget const &widget) const
{
if(widget.parent() != this) return false;
return d->isVisibleItem(&widget);
}

void MenuWidget::updateLayout()
{
d->relayout();
Expand Down
33 changes: 25 additions & 8 deletions doomsday/client/src/ui/widgets/popupmenuwidget.cpp
Expand Up @@ -47,11 +47,6 @@ DENG2_OBSERVES(ContextWidgetOrganizer, WidgetUpdate)
return;
}

// We want items to be hittable throughout the width of the menu.
widget.hitRule()
.setInput(Rule::Left, self.rule().left())
.setInput(Rule::Right, self.rule().right());

// Customize buttons for use in the popup. We will observe the button
// state for highlighting and possibly close the popup when an action
// gets triggered.
Expand Down Expand Up @@ -88,6 +83,30 @@ DENG2_OBSERVES(ContextWidgetOrganizer, WidgetUpdate)
}
}

void updateItemHitRules()
{
GridLayout const &layout = self.menu().layout();

foreach(Widget *child, self.menu().childWidgets())
{
GuiWidget &widget = child->as<GuiWidget>();

if(self.menu().isWidgetPartOfMenu(widget))
{
Vector2i cell = layout.widgetPos(widget);
DENG2_ASSERT(cell.x >= 0 && cell.y >= 0);

// We want items to be hittable throughout the width of the menu,
// however restrict this to the item's column if there are multiple.
widget.hitRule()
.setInput(Rule::Left, (!cell.x? self.rule().left() :
layout.columnLeft(cell.x)))
.setInput(Rule::Right, (cell.x == layout.gridSize().x - 1? self.rule().right() :
layout.columnRight(cell.x)));
}
}
}

void buttonStateChanged(ButtonWidget &button, ButtonWidget::State state)
{
// Update button style.
Expand Down Expand Up @@ -169,11 +188,9 @@ void PopupMenuWidget::preparePopupForOpening()
{
// Redo the layout.
menu().updateLayout();
d->updateItemHitRules();

PopupWidget::preparePopupForOpening();

//menu().rule().setInput(Rule::Width, menu().layout().width() + 2 * margin());
//menu().rule().setInput(Rule::Height, menu().layout().height() + 2 * margin());
}

void PopupMenuWidget::popupClosing()
Expand Down

0 comments on commit dbf8aa0

Please sign in to comment.