2009-10-09 Chris Toshok <toshok@ximian.com>
[moon.git] / src / grid.cpp
blobdd7b7d2ecafd6d6a6ccf1f98edea82760a3cf418
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * grid.cpp: canvas definitions.
5 * Contact:
6 * Moonlight List (moonlight-list@lists.ximian.com)
8 * Copyright 2008 Novell, Inc. (http://www.novell.com)
10 * See the LICENSE file included with the distribution for details.
14 #include <config.h>
16 #include <math.h>
18 #include "brush.h"
19 #include "rect.h"
20 #include "canvas.h"
21 #include "grid.h"
22 #include "runtime.h"
23 #include "namescope.h"
24 #include "collection.h"
26 Grid::Grid ()
28 SetObjectType (Type::GRID);
29 row_matrix = NULL;
30 col_matrix = NULL;
33 Grid::~Grid ()
35 DestroyMatrices ();
38 double
39 Grid::Clamp (double val, double min, double max)
41 if (val < min)
42 return min;
43 else if (val > max)
44 return max;
45 return val;
48 void
49 Grid::OnPropertyChanged (PropertyChangedEventArgs *args, MoonError *error)
51 if (args->GetProperty ()->GetOwnerType() != Type::GRID) {
52 Panel::OnPropertyChanged (args, error);
53 return;
56 if (args->GetId () == Grid::ShowGridLinesProperty){
57 Invalidate ();
60 InvalidateMeasure ();
62 NotifyListenersOfPropertyChange (args, error);
65 void
66 Grid::OnCollectionChanged (Collection *col, CollectionChangedEventArgs *args)
68 if (col == GetColumnDefinitions () ||
69 col == GetRowDefinitions ()) {
70 //InvalidateMeasure ();
71 } else {
72 Panel::OnCollectionChanged (col, args);
75 InvalidateMeasure ();
78 void
79 Grid::OnCollectionItemChanged (Collection *col, DependencyObject *obj, PropertyChangedEventArgs *args)
81 if (col == GetChildren ()) {
82 if (args->GetId () == Grid::ColumnProperty
83 || args->GetId () == Grid::RowProperty
84 || args->GetId () == Grid::ColumnSpanProperty
85 || args->GetId () == Grid::RowSpanProperty) {
86 InvalidateMeasure ();
87 return;
89 } else if (col == GetColumnDefinitions () || col == GetRowDefinitions ()) {
90 if (args->GetId() != ColumnDefinition::ActualWidthProperty
91 && args->GetId() != RowDefinition::ActualHeightProperty) {
92 InvalidateMeasure ();
94 return;
97 Panel::OnCollectionItemChanged (col, obj, args);
100 Size
101 Grid::MeasureOverride (Size availableSize)
103 Size results = availableSize;
105 ColumnDefinitionCollection *columns = GetColumnDefinitions ();
106 RowDefinitionCollection *rows = GetRowDefinitions ();
107 bool free_col = false;
108 bool free_row = false;
110 int col_count = columns->GetCount ();
111 int row_count = rows->GetCount ();
112 Size total_stars = Size (0,0);
114 if (col_count == 0) {
115 columns = new ColumnDefinitionCollection ();
116 ColumnDefinition *coldef = new ColumnDefinition ();
117 columns->Add (coldef);
118 coldef->unref ();
119 free_col = true;
120 col_count = 1;
123 if (row_count == 0) {
124 rows = new RowDefinitionCollection ();
125 RowDefinition *rowdef = new RowDefinition ();
126 rows->Add (rowdef);
127 rowdef->unref ();
128 free_row = true;
129 row_count = 1;
132 CreateMatrices (row_count, col_count);
134 for (int i = 0; i < row_count; i ++) {
135 RowDefinition *rowdef = rows->GetValueAt (i)->AsRowDefinition ();
136 GridLength* height = rowdef->GetHeight();
138 rowdef->SetActualHeight (INFINITY);
139 row_matrix [i][i] = Segment (0.0, rowdef->GetMinHeight (), rowdef->GetMaxHeight (), height->type);
141 if (height->type == GridUnitTypePixel) {
142 row_matrix [i][i].size = Grid::Clamp (height->val, row_matrix [i][i].min, row_matrix [i][i].max);
143 rowdef->SetActualHeight (row_matrix [i][i].size);
145 if (height->type == GridUnitTypeStar)
146 total_stars.height += height->val;
149 for (int i = 0; i < col_count; i ++) {
150 ColumnDefinition *coldef = columns->GetValueAt (i)->AsColumnDefinition ();
151 GridLength *width = coldef->GetWidth ();
153 coldef->SetActualWidth (INFINITY);
154 col_matrix [i][i] = Segment (0.0, coldef->GetMinWidth (), coldef->GetMaxWidth (), width->type);
156 if (width->type == GridUnitTypePixel) {
157 col_matrix [i][i].size = Grid::Clamp (width->val, col_matrix [i][i].min, col_matrix [i][i].max);
158 coldef->SetActualWidth (col_matrix [i][i].size);
160 if (width->type == GridUnitTypeStar)
161 total_stars.width += width->val;
164 List sizes;
165 GridNode *node;
166 GridNode *separator = new GridNode (NULL, 0, 0, 0);
167 sizes.Append (separator);
169 VisualTreeWalker walker = VisualTreeWalker (this);
170 while (UIElement *child = walker.Step ()) {
171 if (child->GetVisibility () != VisibilityVisible)
172 continue;
174 gint32 col, row;
175 gint32 colspan, rowspan;
177 col = MIN (Grid::GetColumn (child), col_count - 1);
178 row = MIN (Grid::GetRow (child), row_count - 1);
179 colspan = MIN (Grid::GetColumnSpan (child), col_count - col);
180 rowspan = MIN (Grid::GetRowSpan (child), row_count - row);
181 Size pixels = Size ();
182 Size stars = Size ();
183 Size automatic = Size ();
186 Size child_size = Size (0,0);
187 double value = 0.0;
188 for (int r = row; r < row + rowspan; r++) {
189 RowDefinition *rowdef = rows->GetValueAt (r)->AsRowDefinition ();
190 GridLength* height = rowdef->GetHeight();
192 switch (height->type) {
193 case GridUnitTypePixel:
194 value = Grid::Clamp (height->val, rowdef->GetMinHeight (), rowdef->GetMaxHeight ());
195 child_size.height += value;
196 pixels.height += height->val;
197 break;
198 case GridUnitTypeAuto:
199 automatic.height += 1;
200 child_size.height = INFINITY;
201 break;
202 case GridUnitTypeStar:
203 stars.height += height->val;
204 child_size.height += availableSize.height * height->val / total_stars.height;
205 break;
209 for (int c = col; c < col + colspan; c++) {
210 ColumnDefinition *coldef = columns->GetValueAt (c)->AsColumnDefinition ();
211 GridLength* width = coldef->GetWidth();
213 switch (width->type) {
214 case GridUnitTypePixel:
215 value = Grid::Clamp (width->val, coldef->GetMinWidth (), coldef->GetMaxWidth ());
216 child_size.width += value;
217 pixels.width += width->val;
218 break;
219 case GridUnitTypeAuto:
220 automatic.width += 1;
221 child_size.width = INFINITY;
222 break;
223 case GridUnitTypeStar:
224 stars.width += width->val;
225 child_size.width += availableSize.width * width->val / total_stars.width;
226 break;
230 child->Measure (child_size);
231 Size desired = child->GetDesiredSize();
233 // Elements distribute their height based on two rules:
234 // 1) Elements with rowspan/colspan == 1 distribute their height first
235 // 2) Everything else distributes in a LIFO manner.
236 // As such, add all UIElements with rowspan/colspan == 1 after the separator in
237 // the list and everything else before it. Then to process, just keep popping
238 // elements off the end of the list.
239 node = new GridNode (row_matrix, row + rowspan - 1, row, desired.height);
240 sizes.InsertBefore (node, node->row == node->col ? separator->next : separator);
242 node = new GridNode (col_matrix, col + colspan - 1, col, desired.width);
243 sizes.InsertBefore (node, node->row == node->col ? separator->next : separator);
246 sizes.Remove (separator);
248 while (GridNode *node= (GridNode *) sizes.Last ()) {
249 node->matrix [node->row][node->col].size = MAX (node->matrix [node->row][node->col].size, node->size);
250 AllocateGridSegments (row_count, col_count);
251 sizes.Remove (node);
254 Size grid_size;
255 for (int r = 0; r < row_count; r ++)
256 grid_size.height += row_matrix [r][r].size;
258 for (int c = 0; c < col_count; c ++)
259 grid_size.width += col_matrix [c][c].size;
261 grid_size = grid_size.Max (GetWidth (), GetHeight ());
262 results = results.Min (grid_size);
264 if (free_col) {
265 columns->unref ();
268 if (free_row) {
269 rows->unref ();
271 // now choose whichever is smaller, our chosen size or the availableSize.
272 return results;
275 void
276 Grid::AllocateGridSegments (int row_count, int col_count)
278 // First allocate the heights of the RowDefinitions, then allocate
279 // the widths of the ColumnDefinitions.
280 for (int i = 0; i < 2; i ++) {
281 Segment **matrix = i == 0 ? row_matrix : col_matrix;
282 int count = i == 0 ? row_count : col_count;
284 for (int row = count - 1; row >= 0; row--) {
285 for (int col = row; col >= 0; col--) {
286 bool spans_star = false;
287 for (int j = row; j >= col; j --)
288 spans_star |= matrix [j][j].type == GridUnitTypeStar;
290 // This is the amount of pixels which must be available between the grid rows
291 // at index 'col' and 'row'. i.e. if 'row' == 0 and 'col' == 2, there must
292 // be at least 'matrix [row][col].size' pixels of height allocated between
293 // all the rows in the range col -> row.
294 double current = matrix [row][col].size;
296 // Count how many pixels have already been allocated between the grid rows
297 // in the range col -> row. The amount of pixels allocated to each grid row/column
298 // is found on the diagonal of the matrix.
299 double total_allocated = 0;
300 for (int i = row; i >= col; i--)
301 total_allocated += matrix [i][i].size;
303 // If the size requirement has not been met, allocate the additional required
304 // size between 'pixel' rows, then 'star' rows, finally 'auto' rows, until all
305 // height has been assigned.
306 if (total_allocated < current) {
307 double additional = current - total_allocated;
308 // Note that multiple passes may be required at each level depending on whether or not
309 // the MaxHeight/MaxWidth value prevents the row/column from accepting the full contribution
310 if (spans_star) {
311 while (AssignSize (matrix, col, row, &additional, GridUnitTypeStar)) { }
312 } else {
313 while (AssignSize (matrix, col, row, &additional, GridUnitTypePixel)) { }
314 while (AssignSize (matrix, col, row, &additional, GridUnitTypeAuto)) { }
322 bool
323 Grid::AssignSize (Segment **matrix, int start, int end, double *size, GridUnitType type)
325 bool assigned = false;
326 int count = 0;
327 double contribution = *size;
329 for (int i = start; i <= end; i++) {
330 if (matrix [i][i].type == type && matrix [i][i].size < matrix [i][i].max)
331 count++;
334 contribution /= count;
336 for (int i = start; i <= end; i++) {
337 if (!(matrix [i][i].type == type && matrix [i][i].size < matrix [i][i].max))
338 continue;
339 double newsize = contribution + matrix [i][i].size;
340 newsize = MIN (newsize, matrix [i][i].max);
341 assigned |= newsize > matrix [i][i].size;
342 *size -= newsize - matrix [i][i].size;
343 matrix [i][i].size = newsize;
345 return assigned;
348 void
349 Grid::DestroyMatrices ()
351 if (row_matrix != NULL) {
352 for (int i = 0; i < row_matrix_dim; i++)
353 delete [] row_matrix [i];
354 delete [] row_matrix;
355 row_matrix = NULL;
358 if (col_matrix != NULL) {
359 for (int i = 0; i < col_matrix_dim; i++)
360 delete [] col_matrix [i];
361 delete [] col_matrix;
362 col_matrix = NULL;
366 void
367 Grid::CreateMatrices (int row_count, int col_count)
369 DestroyMatrices ();
371 row_matrix_dim = row_count;
372 col_matrix_dim = col_count;
374 row_matrix = new Segment *[row_count];
375 for (int i = 0; i < row_count; i++) {
376 row_matrix [i] = new Segment [row_count];
377 for (int j = 0; j < row_count; j++)
378 row_matrix [i][j] = Segment ();
381 col_matrix = new Segment *[col_count];
382 for (int i = 0; i < col_count; i++) {
383 col_matrix [i] = new Segment [col_count];
384 for (int j = 0; j < col_count; j++)
385 col_matrix [i][j] = Segment ();
389 void
390 Grid::ComputeBounds ()
392 Panel::ComputeBounds ();
394 if (GetShowGridLines ()) {
395 extents = Rect (0,0,GetActualWidth (),GetActualHeight ());
396 bounds = IntersectBoundsWithClipPath (extents, false).Transform (&absolute_xform);
397 bounds_with_children = bounds_with_children.Union (bounds);
400 void
401 Grid::PostRender (cairo_t *cr, Region *region, bool front_to_back)
403 // render our chidren if not in front to back mode
404 if (!front_to_back) {
405 VisualTreeWalker walker = VisualTreeWalker (this, ZForward);
406 while (UIElement *child = walker.Step ())
407 child->DoRender (cr, region);
410 if (GetShowGridLines ()) {
411 double offset = 0;
412 double dash = 4;
413 ColumnDefinitionCollection *cols = GetColumnDefinitions ();
414 RowDefinitionCollection *rows = GetRowDefinitions ();
416 cairo_set_line_width(cr, 1.0);
417 // Initially render a blue color
418 cairo_set_dash (cr, &dash, 1, offset);
419 cairo_set_source_rgb (cr, 0.4, 0.4, 1.0);
421 // Draw gridlines between each pair of columns/rows
422 for (int count = 0; count < 2; count++) {
424 for (int i = 0, offset = 0; i < cols->GetCount () - 1; i++) {
425 ColumnDefinition *def = cols->GetValueAt (i)->AsColumnDefinition ();
426 offset += def->GetActualWidth ();
427 cairo_move_to (cr, offset, 0);
428 cairo_line_to (cr, offset, GetActualHeight ());
431 for (int i = 0, offset = 0; i < rows->GetCount () -1; i++) {
432 RowDefinition *def = rows->GetValueAt (i)->AsRowDefinition ();
433 offset += def->GetActualHeight ();
434 cairo_move_to (cr, 0, offset);
435 cairo_line_to (cr, GetActualWidth (), offset);
438 cairo_stroke (cr);
440 // For the second pass render a yellow color in the gaps between the previous dashes
441 cairo_set_dash (cr, &dash, 1, dash);
442 cairo_set_source_rgb (cr, 1.0, 1.0, 0.3);
446 // Chain up in front_to_back mode since we've alread rendered content
447 UIElement::PostRender (cr, region, true);
450 Size
451 Grid::ArrangeOverride (Size finalSize)
453 ColumnDefinitionCollection *columns = GetColumnDefinitions ();
454 RowDefinitionCollection *rows = GetRowDefinitions ();
455 bool free_col = false;
456 bool free_row = false;
458 int col_count = columns->GetCount ();
459 int row_count = rows->GetCount ();
461 if (col_count == 0) {
462 columns = new ColumnDefinitionCollection ();
463 ColumnDefinition *coldef = new ColumnDefinition ();
464 columns->Add (coldef);
465 coldef->unref ();
466 free_col = true;
467 col_count = 1;
470 if (row_count == 0) {
471 rows = new RowDefinitionCollection ();
472 RowDefinition *rowdef = new RowDefinition ();
473 rows->Add (rowdef);
474 rowdef->unref ();
475 free_row = true;
476 row_count = 1;
479 for (int i = 0; i < row_count; i++)
480 rows->GetValueAt (i)->AsRowDefinition ()->SetActualHeight (row_matrix [i][i].size);
482 for (int i = 0; i < col_count; i++)
483 columns->GetValueAt (i)->AsColumnDefinition ()->SetActualWidth (col_matrix [i][i].size);
485 Size requested = Size ();
486 Size star_size = Size ();
487 double row_stars = 0.0;
488 HorizontalAlignment horiz = !isnan (GetWidth ()) ? HorizontalAlignmentStretch : GetHorizontalAlignment ();
489 VerticalAlignment vert = !isnan (GetHeight ()) ? VerticalAlignmentStretch : GetVerticalAlignment ();
491 for (int i = 0; i < row_count; i ++) {
492 RowDefinition *rowdef = rows->GetValueAt (i)->AsRowDefinition ();
493 GridLength* height = rowdef->GetHeight();
495 switch (height->type) {
496 case GridUnitTypeStar:
497 // Star columns distribute evenly
498 requested.height += rowdef->GetActualHeight ();
499 star_size.height += rowdef->GetActualHeight ();
500 //if (vert == VerticalAlignmentStretch)
501 // rowdef->SetActualHeight (0.0);
503 row_stars += height->val;
504 break;
505 case GridUnitTypePixel:
506 requested.height += rowdef->GetActualHeight ();
507 break;
508 case GridUnitTypeAuto:
509 requested.height += rowdef->GetActualHeight ();
510 break;
514 double col_stars = 0.0;
515 for (int i = 0; i < col_count; i ++) {
516 ColumnDefinition *coldef = columns->GetValueAt (i)->AsColumnDefinition ();
517 GridLength* width = coldef->GetWidth();
519 switch (width->type) {
520 case GridUnitTypeStar:
521 // Star columns distribute evenly
522 requested.width += coldef->GetActualWidth ();
523 star_size.width += coldef->GetActualWidth ();
524 //if (horiz == HorizontalAlignmentStretch)
525 // coldef->SetActualWidth (0.0);
527 col_stars += width->val;
528 break;
529 case GridUnitTypePixel:
530 requested.width += coldef->GetActualWidth ();
531 break;
532 case GridUnitTypeAuto:
533 requested.width += coldef->GetActualWidth ();
534 break;
538 Size remaining = Size (finalSize.width - requested.width, finalSize.height - requested.height);
540 if (horiz != HorizontalAlignmentStretch)
541 remaining.width = MIN (remaining.width, 0);
543 if (vert != VerticalAlignmentStretch)
544 remaining.height = MIN (remaining.height, 0);
546 if (remaining.height != 0) {
547 remaining.height += star_size.height;
549 for (int i = 0; i < row_count; i ++) {
550 RowDefinition *rowdef = rows->GetValueAt (i)->AsRowDefinition ();
551 GridLength* height = rowdef->GetHeight();
553 if (height->type == GridUnitTypeStar)
554 rowdef->SetActualHeight (MAX ((remaining.height * height->val / row_stars), 0));
558 if (remaining.width != 0) {
559 remaining.width += star_size.width;
561 for (int i = 0; i < col_count; i ++) {
562 ColumnDefinition *coldef = columns->GetValueAt (i)->AsColumnDefinition ();
563 GridLength* width = coldef->GetWidth();
565 if (width->type == GridUnitTypeStar)
566 coldef->SetActualWidth (MAX ((remaining.width * width->val / col_stars), 0));
570 bool first = true;
571 Size arranged = finalSize;
573 VisualTreeWalker walker = VisualTreeWalker (this);
574 while (UIElement *child = walker.Step ()) {
575 if (child->GetVisibility () != VisibilityVisible)
576 continue;
578 gint32 col = MIN (Grid::GetColumn (child), col_count - 1);
579 gint32 row = MIN (Grid::GetRow (child), row_count - 1);
580 gint32 colspan = MIN (Grid::GetColumnSpan (child), col_count - col);
581 gint32 rowspan = MIN (Grid::GetRowSpan (child), row_count - row);
583 Rect child_final = Rect (0, 0, 0, 0);
584 Size min_size;
585 Size max_size;
587 if (first) {
588 arranged = Size ();
589 first = false;
592 for (int r = 0; r < row + rowspan; r++) {
593 RowDefinition *rowdef = rows->GetValueAt (r)->AsRowDefinition ();
595 if (r < row) {
596 child_final.y += rowdef->GetActualHeight ();
597 } else {
598 child_final.height += rowdef->GetActualHeight ();
600 min_size.height += rowdef->GetMinHeight ();
601 max_size.height += rowdef->GetMaxHeight ();
605 for (int c = 0; c < col + colspan; c++) {
606 ColumnDefinition *coldef = columns->GetValueAt (c)->AsColumnDefinition ();
608 if (c < col) {
609 child_final.x += coldef->GetActualWidth ();
610 } else {
611 child_final.width += coldef->GetActualWidth ();
613 min_size.width += coldef->GetMinWidth ();
614 max_size.width += coldef->GetMaxWidth ();
618 child->Arrange (child_final);
619 Size child_arranged = child->GetRenderSize ();
621 if (horiz == HorizontalAlignmentStretch)
622 arranged.width = MAX (child_final.x + child_final.width, finalSize.width);
623 else
624 arranged.width = MAX (child_final.x + child_final.width, arranged.width);
626 if (vert == VerticalAlignmentStretch)
627 arranged.height = MAX (child_final.y + child_final.height, finalSize.height);
628 else
629 arranged.height = MAX (child_final.y + child_final.height, arranged.height);
632 if (free_col)
633 columns->unref ();
635 if (free_row)
636 rows->unref ();
638 return arranged;
642 // ColumnDefinitionCollection
645 ColumnDefinitionCollection::ColumnDefinitionCollection ()
647 SetObjectType (Type::COLUMNDEFINITION_COLLECTION);
650 ColumnDefinitionCollection::~ColumnDefinitionCollection ()
654 bool
655 ColumnDefinitionCollection::AddedToCollection (Value *value, MoonError *error)
657 if (Contains (value)) {
658 MoonError::FillIn (error, MoonError::ARGUMENT, "ColumnDefinition is already a member of this collection.");
659 return false;
661 return DependencyObjectCollection::AddedToCollection (value, error);
665 // ColumnDefinition
668 ColumnDefinition::ColumnDefinition ()
670 SetObjectType (Type::COLUMNDEFINITION);
673 ColumnDefinition::~ColumnDefinition ()
678 // RowDefinitionCollection
681 RowDefinitionCollection::RowDefinitionCollection ()
683 SetObjectType (Type::ROWDEFINITION_COLLECTION);
686 RowDefinitionCollection::~RowDefinitionCollection ()
690 bool
691 RowDefinitionCollection::AddedToCollection (Value *value, MoonError *error)
693 if (Contains (value)) {
694 MoonError::FillIn (error, MoonError::ARGUMENT, "RowDefinition is already a member of this collection.");
695 return false;
697 return DependencyObjectCollection::AddedToCollection (value, error);
701 // RowDefinition
704 RowDefinition::RowDefinition ()
706 SetObjectType (Type::ROWDEFINITION);
709 RowDefinition::~RowDefinition ()
713 Segment::Segment ()
715 Init (0.0, 0.0, INFINITY, GridUnitTypePixel);
718 Segment::Segment (double size, double min, double max, GridUnitType type)
720 Init (size, min, max, type);
723 void
724 Segment::Init (double size, double min, double max, GridUnitType type)
726 this->max = max;
727 this->min = min;
728 this->type = type;
730 this->size = Grid::Clamp (size, min, max);