1 /* 2 * Copyright 2007, Ingo Weinhold <bonefish@cs.tu-berlin.de>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 #include "GroupView.h" 7 8 #include <stdio.h> 9 10 #include <LayoutUtils.h> 11 12 13 // #pragma mark - GroupView 14 15 16 struct GroupView::LayoutInfo { 17 int32 min; 18 int32 max; 19 int32 preferred; 20 int32 size; 21 22 LayoutInfo() 23 : min(0), 24 max(B_SIZE_UNLIMITED), 25 preferred(0) 26 { 27 } 28 29 void AddConstraints(float addMin, float addMax, float addPreferred) 30 { 31 if (addMin >= min) 32 min = (int32)addMin + 1; 33 if (addMax <= max) 34 max = (int32)addMax + 1; 35 if (addPreferred >= preferred) 36 preferred = (int32)addPreferred + 1; 37 } 38 39 void Normalize() 40 { 41 if (max < min) 42 max = min; 43 if (preferred < min) 44 preferred = min; 45 if (preferred > max) 46 preferred = max; 47 } 48 }; 49 50 51 GroupView::GroupView(enum orientation orientation, int32 lineCount) 52 : View(BRect(0, 0, 0, 0)), 53 fOrientation(orientation), 54 fLineCount(lineCount), 55 fColumnSpacing(0), 56 fRowSpacing(0), 57 fInsets(0, 0, 0, 0), 58 fMinMaxValid(false), 59 fColumnInfos(NULL), 60 fRowInfos(NULL) 61 { 62 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 63 64 if (fLineCount < 1) 65 fLineCount = 1; 66 } 67 68 69 GroupView::~GroupView() 70 { 71 delete fColumnInfos; 72 delete fRowInfos; 73 } 74 75 76 void 77 GroupView::SetSpacing(float horizontal, float vertical) 78 { 79 if (horizontal != fColumnSpacing || vertical != fRowSpacing) { 80 fColumnSpacing = horizontal; 81 fRowSpacing = vertical; 82 83 InvalidateLayout(); 84 } 85 } 86 87 88 void 89 GroupView::SetInsets(float left, float top, float right, float bottom) 90 { 91 BRect newInsets(left, top, right, bottom); 92 if (newInsets != fInsets) { 93 fInsets = newInsets; 94 InvalidateLayout(); 95 } 96 } 97 98 99 BSize 100 GroupView::MinSize() 101 { 102 _ValidateMinMax(); 103 return _AddInsetsAndSpacing(BSize(fMinWidth - 1, fMinHeight - 1)); 104 } 105 106 107 BSize 108 GroupView::MaxSize() 109 { 110 _ValidateMinMax(); 111 return _AddInsetsAndSpacing(BSize(fMaxWidth - 1, fMaxHeight - 1)); 112 } 113 114 115 BSize 116 GroupView::PreferredSize() 117 { 118 _ValidateMinMax(); 119 return _AddInsetsAndSpacing(BSize(fPreferredWidth - 1, 120 fPreferredHeight - 1)); 121 } 122 123 124 BAlignment 125 GroupView::Alignment() 126 { 127 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 128 } 129 130 131 void 132 GroupView::InvalidateLayout() 133 { 134 fMinMaxValid = false; 135 View::InvalidateLayout(); 136 } 137 138 139 void 140 GroupView::Layout() 141 { 142 //printf("%p->GroupView::Layout()\n", this); 143 _ValidateMinMax(); 144 // actually a little late already 145 146 BSize size = _SubtractInsetsAndSpacing(Size()); 147 _LayoutLine(size.IntegerWidth() + 1, fColumnInfos, fColumnCount); 148 _LayoutLine(size.IntegerHeight() + 1, fRowInfos, fRowCount); 149 150 // layout children 151 BPoint location = fInsets.LeftTop(); 152 for (int32 column = 0; column < fColumnCount; column++) { 153 LayoutInfo& columnInfo = fColumnInfos[column]; 154 location.y = fInsets.top; 155 for (int32 row = 0; row < fRowCount; row++) { 156 View* child = _ChildAt(column, row); 157 if (!child) 158 continue; 159 160 // get the grid cell frame 161 BRect cellFrame(location, 162 BSize(columnInfo.size - 1, fRowInfos[row].size - 1)); 163 164 // align the child frame in the grid cell 165 BRect childFrame = BLayoutUtils::AlignInFrame(cellFrame, 166 child->MaxSize(), child->Alignment()); 167 168 // layout child 169 child->SetFrame(childFrame); 170 171 location.y += fRowInfos[row].size + fRowSpacing; 172 } 173 174 location.x += columnInfo.size + fColumnSpacing; 175 } 176 //printf("%p->GroupView::Layout() done\n", this); 177 } 178 179 180 void 181 GroupView::_ValidateMinMax() 182 { 183 if (fMinMaxValid) 184 return; 185 186 //printf("%p->GroupView::_ValidateMinMax()\n", this); 187 delete fColumnInfos; 188 delete fRowInfos; 189 190 fColumnCount = _ColumnCount(); 191 fRowCount = _RowCount(); 192 193 fColumnInfos = new LayoutInfo[fColumnCount]; 194 fRowInfos = new LayoutInfo[fRowCount]; 195 196 // collect the children's min/max constraints 197 for (int32 column = 0; column < fColumnCount; column++) { 198 for (int32 row = 0; row < fRowCount; row++) { 199 View* child = _ChildAt(column, row); 200 if (!child) 201 continue; 202 203 BSize min = child->MinSize(); 204 BSize max = child->MaxSize(); 205 BSize preferred = child->PreferredSize(); 206 207 // apply constraints to column/row info 208 fColumnInfos[column].AddConstraints(min.width, max.width, 209 preferred.width); 210 fRowInfos[row].AddConstraints(min.height, max.height, 211 preferred.height); 212 } 213 } 214 215 // normalize the column/row constraints and compute sum min/max 216 fMinWidth = 0; 217 fMinHeight = 0; 218 fMaxWidth = 0; 219 fMaxHeight = 0; 220 fPreferredWidth = 0; 221 fPreferredHeight = 0; 222 223 for (int32 column = 0; column < fColumnCount; column++) { 224 fColumnInfos[column].Normalize(); 225 fMinWidth = BLayoutUtils::AddSizesInt32(fMinWidth, 226 fColumnInfos[column].min); 227 fMaxWidth = BLayoutUtils::AddSizesInt32(fMaxWidth, 228 fColumnInfos[column].max); 229 fPreferredWidth = BLayoutUtils::AddSizesInt32(fPreferredWidth, 230 fColumnInfos[column].preferred); 231 //printf(" column %ld: min: %ld, max: %ld, preferred: %ld\n", column, fColumnInfos[column].min, fColumnInfos[column].max, fColumnInfos[column].preferred); 232 } 233 234 for (int32 row = 0; row < fRowCount; row++) { 235 fRowInfos[row].Normalize(); 236 fMinHeight = BLayoutUtils::AddSizesInt32(fMinHeight, 237 fRowInfos[row].min); 238 fMaxHeight = BLayoutUtils::AddSizesInt32(fMaxHeight, 239 fRowInfos[row].max); 240 fPreferredHeight = BLayoutUtils::AddSizesInt32(fPreferredHeight, 241 fRowInfos[row].preferred); 242 //printf(" row %ld: min: %ld, max: %ld, preferred: %ld\n", row, fRowInfos[row].min, fRowInfos[row].max, fRowInfos[row].preferred); 243 } 244 245 fMinMaxValid = true; 246 //printf("%p->GroupView::_ValidateMinMax() done\n", this); 247 } 248 249 250 void 251 GroupView::_LayoutLine(int32 size, LayoutInfo* infos, int32 infoCount) 252 { 253 BList infosToLayout; 254 for (int32 i = 0; i < infoCount; i++) { 255 infos[i].size = 0; 256 infosToLayout.AddItem(infos + i); 257 } 258 259 // Distribute the available space over the infos. Each iteration we 260 // try to distribute the remaining space evenly. We respect min and 261 // max constraints, though, add up the space we failed to assign 262 // due to the constraints, and use the sum as remaining space for the 263 // next iteration (can be negative). Then we ignore infos that can't 264 // shrink or grow anymore (depending on what would be needed). 265 while (!infosToLayout.IsEmpty()) { 266 BList canShrinkInfos; 267 BList canGrowInfos; 268 269 int32 sizeDiff = 0; 270 int32 infosToLayoutCount = infosToLayout.CountItems(); 271 for (int32 i = 0; i < infosToLayoutCount; i++) { 272 LayoutInfo* info = (LayoutInfo*)infosToLayout.ItemAt(i); 273 info->size += (i + 1) * size / infosToLayoutCount 274 - i * size / infosToLayoutCount; 275 if (info->size < info->min) { 276 sizeDiff -= info->min - info->size; 277 info->size = info->min; 278 } else if (info->size > info->max) { 279 sizeDiff += info->size - info->max; 280 info->size = info->max; 281 } 282 283 if (info->size > info->min) 284 canShrinkInfos.AddItem(info); 285 if (info->size < info->max) 286 canGrowInfos.AddItem(info); 287 } 288 289 size = sizeDiff; 290 if (size == 0) 291 break; 292 293 if (size > 0) 294 infosToLayout = canGrowInfos; 295 else 296 infosToLayout = canShrinkInfos; 297 } 298 299 // If unassigned space is remaining, that means, that the group has 300 // been resized beyond its max size. We distribute the excess space 301 // evenly. 302 if (size > 0) { 303 for (int32 i = 0; i < infoCount; i++) { 304 infos[i].size += (i + 1) * size / infoCount 305 - i * size / infoCount; 306 } 307 } 308 } 309 310 311 BSize 312 GroupView::_AddInsetsAndSpacing(BSize size) 313 { 314 size.width = BLayoutUtils::AddDistances(size.width, 315 fInsets.left + fInsets.right - 1 316 + (fColumnCount - 1) * fColumnSpacing); 317 size.height = BLayoutUtils::AddDistances(size.height, 318 fInsets.top + fInsets.bottom - 1 319 + (fRowCount - 1) * fRowSpacing); 320 return size; 321 } 322 323 324 BSize 325 GroupView::_SubtractInsetsAndSpacing(BSize size) 326 { 327 size.width = BLayoutUtils::SubtractDistances(size.width, 328 fInsets.left + fInsets.right - 1 329 + (fColumnCount - 1) * fColumnSpacing); 330 size.height = BLayoutUtils::SubtractDistances(size.height, 331 fInsets.top + fInsets.bottom - 1 332 + (fRowCount - 1) * fRowSpacing); 333 return size; 334 } 335 336 int32 337 GroupView::_RowCount() const 338 { 339 int32 childCount = CountChildren(); 340 int32 count; 341 if (fOrientation == B_HORIZONTAL) 342 count = min_c(fLineCount, childCount); 343 else 344 count = (childCount + fLineCount - 1) / fLineCount; 345 346 return max_c(count, 1); 347 } 348 349 350 int32 351 GroupView::_ColumnCount() const 352 { 353 int32 childCount = CountChildren(); 354 int32 count; 355 if (fOrientation == B_HORIZONTAL) 356 count = (childCount + fLineCount - 1) / fLineCount; 357 else 358 count = min_c(fLineCount, childCount); 359 360 return max_c(count, 1); 361 } 362 363 364 View* 365 GroupView::_ChildAt(int32 column, int32 row) const 366 { 367 if (fOrientation == B_HORIZONTAL) 368 return ChildAt(column * fLineCount + row); 369 else 370 return ChildAt(row * fLineCount + column); 371 } 372 373 374 // #pragma mark - Glue 375 376 377 Glue::Glue() 378 : View() 379 { 380 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 381 } 382 383 384 // #pragma mark - Strut 385 386 387 Strut::Strut(float pixelWidth, float pixelHeight) 388 : View(), 389 fSize(pixelWidth >= 0 ? pixelWidth - 1 : B_SIZE_UNSET, 390 pixelHeight >= 0 ? pixelHeight - 1 : B_SIZE_UNSET) 391 { 392 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 393 } 394 395 396 BSize 397 Strut::MinSize() 398 { 399 return BLayoutUtils::ComposeSize(fSize, BSize(-1, -1)); 400 } 401 402 403 BSize 404 Strut::MaxSize() 405 { 406 return BLayoutUtils::ComposeSize(fSize, 407 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 408 } 409 410 411 // #pragma mark - HStrut 412 413 414 HStrut::HStrut(float width) 415 : Strut(width, -1) 416 { 417 } 418 419 420 // #pragma mark - VStrut 421 422 423 VStrut::VStrut(float height) 424 : Strut(-1, height) 425 { 426 } 427