* Copyright 2007, Ingo Weinhold <bonefish@cs.tu-berlin.de>.
* All rights reserved. Distributed under the terms of the MIT License.
*/
#include "GroupView.h"
#include <stdio.h>
#include <LayoutUtils.h>
struct GroupView::LayoutInfo {
int32 min;
int32 max;
int32 preferred;
int32 size;
LayoutInfo()
: min(0),
max(B_SIZE_UNLIMITED),
preferred(0)
{
}
void AddConstraints(float addMin, float addMax, float addPreferred)
{
if (addMin >= min)
min = (int32)addMin + 1;
if (addMax <= max)
max = (int32)addMax + 1;
if (addPreferred >= preferred)
preferred = (int32)addPreferred + 1;
}
void Normalize()
{
if (max < min)
max = min;
if (preferred < min)
preferred = min;
if (preferred > max)
preferred = max;
}
};
GroupView::GroupView(enum orientation orientation, int32 lineCount)
: View(BRect(0, 0, 0, 0)),
fOrientation(orientation),
fLineCount(lineCount),
fColumnSpacing(0),
fRowSpacing(0),
fInsets(0, 0, 0, 0),
fMinMaxValid(false),
fColumnInfos(NULL),
fRowInfos(NULL)
{
SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
if (fLineCount < 1)
fLineCount = 1;
}
GroupView::~GroupView()
{
delete fColumnInfos;
delete fRowInfos;
}
void
GroupView::SetSpacing(float horizontal, float vertical)
{
if (horizontal != fColumnSpacing || vertical != fRowSpacing) {
fColumnSpacing = horizontal;
fRowSpacing = vertical;
InvalidateLayout();
}
}
void
GroupView::SetInsets(float left, float top, float right, float bottom)
{
BRect newInsets(left, top, right, bottom);
if (newInsets != fInsets) {
fInsets = newInsets;
InvalidateLayout();
}
}
BSize
GroupView::MinSize()
{
_ValidateMinMax();
return _AddInsetsAndSpacing(BSize(fMinWidth - 1, fMinHeight - 1));
}
BSize
GroupView::MaxSize()
{
_ValidateMinMax();
return _AddInsetsAndSpacing(BSize(fMaxWidth - 1, fMaxHeight - 1));
}
BSize
GroupView::PreferredSize()
{
_ValidateMinMax();
return _AddInsetsAndSpacing(BSize(fPreferredWidth - 1,
fPreferredHeight - 1));
}
BAlignment
GroupView::Alignment()
{
return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT);
}
void
GroupView::InvalidateLayout()
{
fMinMaxValid = false;
View::InvalidateLayout();
}
void
GroupView::Layout()
{
_ValidateMinMax();
BSize size = _SubtractInsetsAndSpacing(Size());
_LayoutLine(size.IntegerWidth() + 1, fColumnInfos, fColumnCount);
_LayoutLine(size.IntegerHeight() + 1, fRowInfos, fRowCount);
BPoint location = fInsets.LeftTop();
for (int32 column = 0; column < fColumnCount; column++) {
LayoutInfo& columnInfo = fColumnInfos[column];
location.y = fInsets.top;
for (int32 row = 0; row < fRowCount; row++) {
View* child = _ChildAt(column, row);
if (!child)
continue;
BRect cellFrame(location,
BSize(columnInfo.size - 1, fRowInfos[row].size - 1));
BRect childFrame = BLayoutUtils::AlignInFrame(cellFrame,
child->MaxSize(), child->Alignment());
child->SetFrame(childFrame);
location.y += fRowInfos[row].size + fRowSpacing;
}
location.x += columnInfo.size + fColumnSpacing;
}
}
void
GroupView::_ValidateMinMax()
{
if (fMinMaxValid)
return;
delete fColumnInfos;
delete fRowInfos;
fColumnCount = _ColumnCount();
fRowCount = _RowCount();
fColumnInfos = new LayoutInfo[fColumnCount];
fRowInfos = new LayoutInfo[fRowCount];
for (int32 column = 0; column < fColumnCount; column++) {
for (int32 row = 0; row < fRowCount; row++) {
View* child = _ChildAt(column, row);
if (!child)
continue;
BSize min = child->MinSize();
BSize max = child->MaxSize();
BSize preferred = child->PreferredSize();
fColumnInfos[column].AddConstraints(min.width, max.width,
preferred.width);
fRowInfos[row].AddConstraints(min.height, max.height,
preferred.height);
}
}
fMinWidth = 0;
fMinHeight = 0;
fMaxWidth = 0;
fMaxHeight = 0;
fPreferredWidth = 0;
fPreferredHeight = 0;
for (int32 column = 0; column < fColumnCount; column++) {
fColumnInfos[column].Normalize();
fMinWidth = BLayoutUtils::AddSizesInt32(fMinWidth,
fColumnInfos[column].min);
fMaxWidth = BLayoutUtils::AddSizesInt32(fMaxWidth,
fColumnInfos[column].max);
fPreferredWidth = BLayoutUtils::AddSizesInt32(fPreferredWidth,
fColumnInfos[column].preferred);
}
for (int32 row = 0; row < fRowCount; row++) {
fRowInfos[row].Normalize();
fMinHeight = BLayoutUtils::AddSizesInt32(fMinHeight,
fRowInfos[row].min);
fMaxHeight = BLayoutUtils::AddSizesInt32(fMaxHeight,
fRowInfos[row].max);
fPreferredHeight = BLayoutUtils::AddSizesInt32(fPreferredHeight,
fRowInfos[row].preferred);
}
fMinMaxValid = true;
}
void
GroupView::_LayoutLine(int32 size, LayoutInfo* infos, int32 infoCount)
{
BList infosToLayout;
for (int32 i = 0; i < infoCount; i++) {
infos[i].size = 0;
infosToLayout.AddItem(infos + i);
}
while (!infosToLayout.IsEmpty()) {
BList canShrinkInfos;
BList canGrowInfos;
int32 sizeDiff = 0;
int32 infosToLayoutCount = infosToLayout.CountItems();
for (int32 i = 0; i < infosToLayoutCount; i++) {
LayoutInfo* info = (LayoutInfo*)infosToLayout.ItemAt(i);
info->size += (i + 1) * size / infosToLayoutCount
- i * size / infosToLayoutCount;
if (info->size < info->min) {
sizeDiff -= info->min - info->size;
info->size = info->min;
} else if (info->size > info->max) {
sizeDiff += info->size - info->max;
info->size = info->max;
}
if (info->size > info->min)
canShrinkInfos.AddItem(info);
if (info->size < info->max)
canGrowInfos.AddItem(info);
}
size = sizeDiff;
if (size == 0)
break;
if (size > 0)
infosToLayout = canGrowInfos;
else
infosToLayout = canShrinkInfos;
}
if (size > 0) {
for (int32 i = 0; i < infoCount; i++) {
infos[i].size += (i + 1) * size / infoCount
- i * size / infoCount;
}
}
}
BSize
GroupView::_AddInsetsAndSpacing(BSize size)
{
size.width = BLayoutUtils::AddDistances(size.width,
fInsets.left + fInsets.right - 1
+ (fColumnCount - 1) * fColumnSpacing);
size.height = BLayoutUtils::AddDistances(size.height,
fInsets.top + fInsets.bottom - 1
+ (fRowCount - 1) * fRowSpacing);
return size;
}
BSize
GroupView::_SubtractInsetsAndSpacing(BSize size)
{
size.width = BLayoutUtils::SubtractDistances(size.width,
fInsets.left + fInsets.right - 1
+ (fColumnCount - 1) * fColumnSpacing);
size.height = BLayoutUtils::SubtractDistances(size.height,
fInsets.top + fInsets.bottom - 1
+ (fRowCount - 1) * fRowSpacing);
return size;
}
int32
GroupView::_RowCount() const
{
int32 childCount = CountChildren();
int32 count;
if (fOrientation == B_HORIZONTAL)
count = min_c(fLineCount, childCount);
else
count = (childCount + fLineCount - 1) / fLineCount;
return max_c(count, 1);
}
int32
GroupView::_ColumnCount() const
{
int32 childCount = CountChildren();
int32 count;
if (fOrientation == B_HORIZONTAL)
count = (childCount + fLineCount - 1) / fLineCount;
else
count = min_c(fLineCount, childCount);
return max_c(count, 1);
}
View*
GroupView::_ChildAt(int32 column, int32 row) const
{
if (fOrientation == B_HORIZONTAL)
return ChildAt(column * fLineCount + row);
else
return ChildAt(row * fLineCount + column);
}
Glue::Glue()
: View()
{
SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
}
Strut::Strut(float pixelWidth, float pixelHeight)
: View(),
fSize(pixelWidth >= 0 ? pixelWidth - 1 : B_SIZE_UNSET,
pixelHeight >= 0 ? pixelHeight - 1 : B_SIZE_UNSET)
{
SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
}
BSize
Strut::MinSize()
{
return BLayoutUtils::ComposeSize(fSize, BSize(-1, -1));
}
BSize
Strut::MaxSize()
{
return BLayoutUtils::ComposeSize(fSize,
BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
}
HStrut::HStrut(float width)
: Strut(width, -1)
{
}
VStrut::VStrut(float height)
: Strut(-1, height)
{
}