mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-29 01:20:17 +00:00
575 lines
14 KiB
C++
575 lines
14 KiB
C++
// Aseprite UI Library
|
|
// Copyright (C) 2001-2017 David Capello
|
|
//
|
|
// This file is released under the terms of the MIT license.
|
|
// Read LICENSE.txt for more information.
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "base/memory.h"
|
|
#include "gfx/size.h"
|
|
#include "ui/grid.h"
|
|
#include "ui/message.h"
|
|
#include "ui/size_hint_event.h"
|
|
#include "ui/resize_event.h"
|
|
#include "ui/theme.h"
|
|
#include "ui/widget.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
namespace ui {
|
|
|
|
using namespace gfx;
|
|
|
|
Grid::Cell::Cell()
|
|
{
|
|
parent = NULL;
|
|
child = NULL;
|
|
hspan = 0;
|
|
vspan = 0;
|
|
align = 0;
|
|
w = 0;
|
|
h = 0;
|
|
}
|
|
|
|
Grid::Grid(int columns, bool same_width_columns)
|
|
: Widget(kGridWidget)
|
|
, m_colstrip(columns)
|
|
{
|
|
ASSERT(columns > 0);
|
|
|
|
m_same_width_columns = same_width_columns;
|
|
|
|
for (std::size_t col=0; col<m_colstrip.size(); ++col) {
|
|
m_colstrip[col].size = 0;
|
|
m_colstrip[col].expand_count = 0;
|
|
}
|
|
|
|
initTheme();
|
|
}
|
|
|
|
Grid::~Grid()
|
|
{
|
|
// Delete all cells.
|
|
for (std::size_t row=0; row<m_cells.size(); ++row)
|
|
for (std::size_t col=0; col<m_cells[row].size(); ++col)
|
|
delete m_cells[row][col];
|
|
}
|
|
|
|
/**
|
|
* Adds a child widget in the specified grid.
|
|
*
|
|
* @param widget The grid widget.
|
|
* @param child The child widget.
|
|
* @param hspan
|
|
* @param vspan
|
|
* @param align
|
|
* It's a combination of the following values:
|
|
*
|
|
* - HORIZONTAL: The widget'll get excess horizontal space.
|
|
* - VERTICAL: The widget'll get excess vertical space.
|
|
*
|
|
* - LEFT: Sets horizontal alignment to the beginning of cell.
|
|
* - CENTER: Sets horizontal alignment to the center of cell.
|
|
* - RIGHT: Sets horizontal alignment to the end of cell.
|
|
* - None: Uses the whole horizontal space of the cell.
|
|
*
|
|
* - TOP: Sets vertical alignment to the beginning of the cell.
|
|
* - MIDDLE: Sets vertical alignment to the center of the cell.
|
|
* - BOTTOM: Sets vertical alignment to the end of the cell.
|
|
* - None: Uses the whole vertical space of the cell.
|
|
*/
|
|
void Grid::addChildInCell(Widget* child, int hspan, int vspan, int align)
|
|
{
|
|
ASSERT(hspan > 0);
|
|
ASSERT(vspan > 0);
|
|
|
|
addChild(child);
|
|
|
|
if (!putWidgetInCell(child, hspan, vspan, align)) {
|
|
expandRows(m_rowstrip.size()+1);
|
|
putWidgetInCell(child, hspan, vspan, align);
|
|
}
|
|
}
|
|
|
|
Grid::Info Grid::getChildInfo(Widget* child)
|
|
{
|
|
Info info;
|
|
for (int row=0; row<(int)m_rowstrip.size(); ++row) {
|
|
for (int col=0; col<(int)m_colstrip.size(); ++col) {
|
|
Cell* cell = m_cells[row][col];
|
|
|
|
if (cell->child == child) {
|
|
info.col = col;
|
|
info.row = row;
|
|
info.hspan = cell->hspan;
|
|
info.vspan = cell->vspan;
|
|
info.grid_cols = m_colstrip.size();
|
|
info.grid_rows = m_rowstrip.size();
|
|
return info;
|
|
}
|
|
}
|
|
}
|
|
return info;
|
|
}
|
|
|
|
void Grid::onResize(ResizeEvent& ev)
|
|
{
|
|
gfx::Rect rect = ev.bounds();
|
|
int pos_x, pos_y;
|
|
Size reqSize;
|
|
int x, y, w, h;
|
|
int col, row;
|
|
|
|
setBoundsQuietly(rect);
|
|
|
|
calculateSize();
|
|
distributeSize(rect);
|
|
|
|
pos_y = rect.y + border().top();
|
|
for (row=0; row<(int)m_rowstrip.size(); ++row) {
|
|
pos_x = rect.x + border().left();
|
|
|
|
for (col=0; col<(int)m_colstrip.size(); ++col) {
|
|
Cell* cell = m_cells[row][col];
|
|
|
|
if (cell->child != NULL &&
|
|
cell->parent == NULL &&
|
|
!(cell->child->hasFlags(HIDDEN))) {
|
|
x = pos_x;
|
|
y = pos_y;
|
|
|
|
calculateCellSize(col, cell->hspan, m_colstrip, w);
|
|
calculateCellSize(row, cell->vspan, m_rowstrip, h);
|
|
|
|
reqSize = cell->child->sizeHint();
|
|
|
|
if (cell->align & LEFT) {
|
|
w = reqSize.w;
|
|
}
|
|
else if (cell->align & CENTER) {
|
|
x += w/2 - reqSize.w/2;
|
|
w = reqSize.w;
|
|
}
|
|
else if (cell->align & RIGHT) {
|
|
x += w - reqSize.w;
|
|
w = reqSize.w;
|
|
}
|
|
|
|
if (cell->align & TOP) {
|
|
h = reqSize.h;
|
|
}
|
|
else if (cell->align & MIDDLE) {
|
|
y += h/2 - reqSize.h/2;
|
|
h = reqSize.h;
|
|
}
|
|
else if (cell->align & BOTTOM) {
|
|
y += h - reqSize.h;
|
|
h = reqSize.h;
|
|
}
|
|
|
|
if (x+w > rect.x+rect.w-border().right())
|
|
w = rect.x+rect.w-border().right()-x;
|
|
if (y+h > rect.y+rect.h-border().bottom())
|
|
h = rect.y+rect.h-border().bottom()-y;
|
|
|
|
cell->child->setBounds(Rect(x, y, w, h));
|
|
}
|
|
|
|
if (m_colstrip[col].size > 0)
|
|
pos_x += m_colstrip[col].size + childSpacing();
|
|
}
|
|
|
|
if (m_rowstrip[row].size > 0)
|
|
pos_y += m_rowstrip[row].size + childSpacing();
|
|
}
|
|
}
|
|
|
|
void Grid::onSizeHint(SizeHintEvent& ev)
|
|
{
|
|
calculateSize();
|
|
|
|
// Calculate the total
|
|
gfx::Size sz(0, 0);
|
|
sumStripSize(m_colstrip, sz.w);
|
|
sumStripSize(m_rowstrip, sz.h);
|
|
|
|
sz.w += border().width();
|
|
sz.h += border().height();
|
|
|
|
ev.setSizeHint(sz);
|
|
}
|
|
|
|
void Grid::sumStripSize(const std::vector<Strip>& strip, int& size)
|
|
{
|
|
int i, j;
|
|
|
|
size = 0;
|
|
for (i=j=0; i<(int)strip.size(); ++i) {
|
|
if (strip[i].size > 0) {
|
|
size += strip[i].size;
|
|
if (++j > 1)
|
|
size += this->childSpacing();
|
|
}
|
|
}
|
|
}
|
|
|
|
void Grid::calculateCellSize(int start, int span, const std::vector<Strip>& strip, int& size)
|
|
{
|
|
int i, j;
|
|
|
|
size = 0;
|
|
|
|
for (i=start, j=0; i<start+span; ++i) {
|
|
if (strip[i].size > 0) {
|
|
size += strip[i].size;
|
|
if (++j > 1)
|
|
size += this->childSpacing();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculates the size of each strip (rows and columns) in the grid.
|
|
void Grid::calculateSize()
|
|
{
|
|
if (m_rowstrip.size() == 0)
|
|
return;
|
|
|
|
calculateStripSize(m_colstrip, m_rowstrip, HORIZONTAL);
|
|
calculateStripSize(m_rowstrip, m_colstrip, VERTICAL);
|
|
|
|
expandStrip(m_colstrip, m_rowstrip, &Grid::incColSize);
|
|
expandStrip(m_rowstrip, m_colstrip, &Grid::incRowSize);
|
|
|
|
// Same width in all columns
|
|
if (m_same_width_columns) {
|
|
int max_w = 0;
|
|
for (int col=0; col<(int)m_colstrip.size(); ++col)
|
|
max_w = MAX(max_w, m_colstrip[col].size);
|
|
|
|
for (int col=0; col<(int)m_colstrip.size(); ++col)
|
|
m_colstrip[col].size = max_w;
|
|
}
|
|
}
|
|
|
|
void Grid::calculateStripSize(std::vector<Strip>& colstrip,
|
|
std::vector<Strip>& rowstrip, int align)
|
|
{
|
|
Cell* cell;
|
|
|
|
// For each column
|
|
for (int col=0; col<(int)colstrip.size(); ++col) {
|
|
// A counter of widgets that want more space in this column
|
|
int expand_count = 0;
|
|
|
|
// For each row
|
|
for (int row=0; row<(int)rowstrip.size(); ++row) {
|
|
// For each cell
|
|
if (&colstrip == &m_colstrip)
|
|
cell = m_cells[row][col];
|
|
else
|
|
cell = m_cells[col][row]; // Transposed
|
|
|
|
if (cell->child != NULL) {
|
|
if (cell->parent == NULL) {
|
|
// If the widget isn't hidden then we can request its size
|
|
if (!(cell->child->hasFlags(HIDDEN))) {
|
|
Size reqSize = cell->child->sizeHint();
|
|
cell->w = reqSize.w - (cell->hspan-1) * this->childSpacing();
|
|
cell->h = reqSize.h - (cell->vspan-1) * this->childSpacing();
|
|
|
|
if ((cell->align & align) == align)
|
|
++expand_count;
|
|
}
|
|
else
|
|
cell->w = cell->h = 0;
|
|
}
|
|
else {
|
|
if (!(cell->child->hasFlags(HIDDEN))) {
|
|
if ((cell->parent->align & align) == align)
|
|
++expand_count;
|
|
}
|
|
}
|
|
|
|
if (&colstrip == &m_colstrip)
|
|
row += cell->vspan-1;
|
|
else
|
|
row += cell->hspan-1; // Transposed
|
|
}
|
|
}
|
|
|
|
colstrip[col].size = 0;
|
|
colstrip[col].expand_count = expand_count;
|
|
}
|
|
}
|
|
|
|
void Grid::expandStrip(std::vector<Strip>& colstrip,
|
|
std::vector<Strip>& rowstrip,
|
|
void (Grid::*incCol)(int, int))
|
|
{
|
|
bool more_span;
|
|
int i, current_span = 1;
|
|
|
|
do {
|
|
more_span = false;
|
|
for (int col=0; col<(int)colstrip.size(); ++col) {
|
|
for (int row=0; row<(int)rowstrip.size(); ++row) {
|
|
int cell_size;
|
|
int cell_span;
|
|
Cell* cell;
|
|
|
|
// For each cell
|
|
if (&colstrip == &m_colstrip) {
|
|
cell = m_cells[row][col];
|
|
cell_size = cell->w;
|
|
cell_span = cell->hspan;
|
|
}
|
|
else {
|
|
cell = m_cells[col][row]; // Transposed
|
|
cell_size = cell->h;
|
|
cell_span = cell->vspan;
|
|
}
|
|
|
|
if (cell->child != NULL &&
|
|
cell->parent == NULL &&
|
|
cell_size > 0) {
|
|
ASSERT(cell_span > 0);
|
|
|
|
if (cell_span == current_span) {
|
|
// Calculate the maximum (expand_count) in cell's columns.
|
|
int max_expand_count = 0;
|
|
for (i=col; i<col+cell_span; ++i)
|
|
max_expand_count = MAX(max_expand_count,
|
|
colstrip[i].expand_count);
|
|
|
|
int expand = 0; // How many columns have the maximum value of "expand_count"
|
|
int last_expand = 0; // This variable is used to add the remainder space to the last column
|
|
for (i=col; i<col+cell_span; ++i) {
|
|
if (colstrip[i].expand_count == max_expand_count) {
|
|
++expand;
|
|
last_expand = i;
|
|
}
|
|
}
|
|
|
|
// Divide the available size of the cell in the number of columns which are expandible
|
|
int size = cell_size / expand;
|
|
for (i=col; i<col+cell_span; ++i) {
|
|
if (colstrip[i].expand_count == max_expand_count) {
|
|
// For the last column, use all the available space in the column
|
|
if (last_expand == i) {
|
|
if (&colstrip == &m_colstrip)
|
|
size = cell->w;
|
|
else
|
|
size = cell->h; // Transposed
|
|
}
|
|
(this->*incCol)(i, size);
|
|
}
|
|
}
|
|
}
|
|
else if (cell_span > current_span) {
|
|
more_span = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
++current_span;
|
|
} while (more_span);
|
|
}
|
|
|
|
void Grid::distributeSize(const gfx::Rect& rect)
|
|
{
|
|
if (m_rowstrip.size() == 0)
|
|
return;
|
|
|
|
distributeStripSize(m_colstrip, rect.w, border().width(), m_same_width_columns);
|
|
distributeStripSize(m_rowstrip, rect.h, border().height(), false);
|
|
}
|
|
|
|
void Grid::distributeStripSize(std::vector<Strip>& colstrip,
|
|
int rect_size, int border_size, bool same_width)
|
|
{
|
|
int i, j;
|
|
|
|
int max_expand_count = 0;
|
|
for (i=0; i<(int)colstrip.size(); ++i)
|
|
max_expand_count = MAX(max_expand_count,
|
|
colstrip[i].expand_count);
|
|
|
|
int total_req = 0;
|
|
int wantmore_count = 0;
|
|
for (i=j=0; i<(int)colstrip.size(); ++i) {
|
|
if (colstrip[i].size > 0) {
|
|
total_req += colstrip[i].size;
|
|
if (++j > 1)
|
|
total_req += this->childSpacing();
|
|
}
|
|
|
|
if (colstrip[i].expand_count == max_expand_count || same_width) {
|
|
++wantmore_count;
|
|
}
|
|
}
|
|
total_req += border_size;
|
|
|
|
int extra_total = (rect_size - total_req);
|
|
|
|
// Expand or reduce "expandable" strip
|
|
if ((wantmore_count > 0) &&
|
|
((extra_total > 0 && (max_expand_count > 0 || same_width)) ||
|
|
(extra_total < 0))) {
|
|
// If a expandable column-strip was empty (size=0) then we have
|
|
// to reduce the extra_total size because a new child-spacing is
|
|
// added by this column
|
|
for (i=0; i<(int)colstrip.size(); ++i) {
|
|
if ((colstrip[i].size == 0) &&
|
|
(colstrip[i].expand_count == max_expand_count || same_width)) {
|
|
extra_total -= SGN(extra_total)*this->childSpacing();
|
|
}
|
|
}
|
|
|
|
int extra_foreach = extra_total / wantmore_count;
|
|
|
|
for (i=0; i<(int)colstrip.size(); ++i) {
|
|
if (colstrip[i].expand_count == max_expand_count || same_width) {
|
|
ASSERT(wantmore_count > 0);
|
|
|
|
colstrip[i].size += extra_foreach;
|
|
extra_total -= extra_foreach;
|
|
|
|
if (--wantmore_count == 0) {
|
|
colstrip[i].size += extra_total;
|
|
extra_total = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
ASSERT(wantmore_count == 0);
|
|
ASSERT(extra_total == 0);
|
|
}
|
|
}
|
|
|
|
bool Grid::putWidgetInCell(Widget* child, int hspan, int vspan, int align)
|
|
{
|
|
int col, row, colbeg, colend, rowend;
|
|
Cell *cell, *parentcell;
|
|
|
|
for (row=0; row<(int)m_rowstrip.size(); ++row) {
|
|
for (col=0; col<(int)m_colstrip.size(); ++col) {
|
|
cell = m_cells[row][col];
|
|
|
|
if (cell->child == NULL) {
|
|
cell->child = child;
|
|
cell->hspan = hspan;
|
|
cell->vspan = vspan;
|
|
cell->align = align;
|
|
|
|
parentcell = cell;
|
|
colbeg = col;
|
|
colend = MIN(col+hspan, (int)m_colstrip.size());
|
|
rowend = row+vspan;
|
|
|
|
expandRows(row+vspan);
|
|
|
|
for (++col; col<colend; ++col) {
|
|
cell = m_cells[row][col];
|
|
|
|
// If these asserts fails, it's really possible that you
|
|
// specified bad values for hspan or vspan (they are
|
|
// overlapping with other cells).
|
|
ASSERT(cell->parent == NULL);
|
|
ASSERT(cell->child == NULL);
|
|
|
|
cell->parent = parentcell;
|
|
cell->child = child;
|
|
cell->hspan = colend - col;
|
|
cell->vspan = rowend - row;
|
|
}
|
|
|
|
for (++row; row<rowend; ++row) {
|
|
for (col=colbeg; col<colend; ++col) {
|
|
cell = m_cells[row][col];
|
|
|
|
ASSERT(cell->parent == NULL);
|
|
ASSERT(cell->child == NULL);
|
|
|
|
cell->parent = parentcell;
|
|
cell->child = child;
|
|
cell->hspan = colend - col;
|
|
cell->vspan = rowend - row;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Expands the grid's rows to reach the specified quantity of rows in
|
|
// the parameter.
|
|
void Grid::expandRows(int rows)
|
|
{
|
|
if ((int)m_rowstrip.size() < rows) {
|
|
int old_size = (int)m_rowstrip.size();
|
|
|
|
m_cells.resize(rows);
|
|
m_rowstrip.resize(rows);
|
|
|
|
for (int row=old_size; row<rows; ++row) {
|
|
m_cells[row].resize(m_colstrip.size());
|
|
m_rowstrip[row].size = 0;
|
|
m_rowstrip[row].expand_count = 0;
|
|
|
|
for (int col=0; col<(int)m_cells[row].size(); ++col) {
|
|
m_cells[row][col] = new Cell;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Grid::incColSize(int col, int size)
|
|
{
|
|
m_colstrip[col].size += size;
|
|
|
|
for (int row=0; row<(int)m_rowstrip.size(); ) {
|
|
Cell* cell = m_cells[row][col];
|
|
|
|
if (cell->child != NULL) {
|
|
if (cell->parent != NULL)
|
|
cell->parent->w -= size;
|
|
else
|
|
cell->w -= size;
|
|
|
|
row += cell->vspan;
|
|
}
|
|
else
|
|
++row;
|
|
}
|
|
}
|
|
|
|
void Grid::incRowSize(int row, int size)
|
|
{
|
|
m_rowstrip[row].size += size;
|
|
|
|
for (int col=0; col<(int)m_colstrip.size(); ) {
|
|
Cell* cell = m_cells[row][col];
|
|
|
|
if (cell->child != NULL) {
|
|
if (cell->parent != NULL)
|
|
cell->parent->h -= size;
|
|
else
|
|
cell->h -= size;
|
|
|
|
col += cell->hspan;
|
|
}
|
|
else
|
|
++col;
|
|
}
|
|
}
|
|
|
|
} // namespace ui
|