1 /// 2 module nanogui.layout; 3 4 import std.container.array : Array; 5 import std.algorithm : max; 6 7 import nanogui.window : Window; 8 import nanogui.common; 9 import nanogui.widget; 10 11 /* 12 NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>. 13 The widget drawing code is based on the NanoVG demo application 14 by Mikko Mononen. 15 16 All rights reserved. Use of this source code is governed by a 17 BSD-style license that can be found in the LICENSE.txt file. 18 */ 19 /** 20 * A collection of useful layout managers. The \ref nanogui.GridLayout 21 * was contributed by Christian Schueller. 22 */ 23 24 /// The different kinds of alignments a layout can perform. 25 enum Alignment : ubyte 26 { 27 Minimum, /// Take only as much space as is required. 28 Middle, /// Center align. 29 Maximum, /// Take as much space as is allowed. 30 Fill /// Fill according to preferred sizes. 31 } 32 33 /// The direction of data flow for a layout. 34 /// 35 /// Important: the source is heavily based on assumption that 36 /// only two orientations are possible. It's true in case of 2D 37 /// layout. 38 enum Orientation 39 { 40 Horizontal, /// Layout expands on horizontal axis. 41 Vertical, /// Layout expands on vertical axis. 42 } 43 44 auto axisIndex(Orientation o) 45 { 46 int axis_index = o; 47 return axis_index; 48 } 49 50 auto nextAxisIndex(Orientation o) 51 { 52 import std.traits : EnumMembers; 53 int idx = (o + 1) % EnumMembers!Orientation.length; 54 return idx; 55 } 56 57 /** 58 * interface Layout 59 * 60 * Basic interface of a layout engine. 61 */ 62 interface Layout 63 { 64 public: 65 /** 66 * Performs any and all resizing applicable. 67 * 68 * Params: 69 * ctx = The `NanoVG` context being used for drawing. 70 * widget = The Widget this layout is controlling sizing for. 71 */ 72 void performLayout(NanoContext ctx, Widget widget) const; 73 74 /** 75 * The preferred size for this layout. 76 * 77 * Params: 78 * ctx = The `NanoVG` context being used for drawing. 79 * widget = The Widget this layout's preferred size is considering. 80 * 81 * Returns: 82 * The preferred size, accounting for things such as spacing, padding 83 * for icons, etc. 84 */ 85 Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const; 86 87 /// The margin of this Layout. 88 int margin() const; 89 90 /// Sets the margin of this Layout. 91 void margin(int); 92 } 93 94 /** 95 * Simple horizontal/vertical box layout 96 * 97 * This widget stacks up a bunch of widgets horizontally or vertically. It adds 98 * margins around the entire container and a custom spacing between adjacent 99 * widgets. 100 */ 101 class BoxLayout : Layout 102 { 103 public: 104 /** 105 * Construct a box layout which packs widgets in the given `Orientation` 106 * 107 * Params: 108 * orientation = The Orientation this BoxLayout expands along 109 * alignment = Widget alignment perpendicular to the chosen orientation 110 * margin = Margin around the layout container 111 * spacing = Extra spacing placed between widgets 112 */ 113 this(Orientation orientation, Alignment alignment = Alignment.Middle, 114 int margin = 0, int spacing = 0) 115 { 116 mOrientation = orientation; 117 mAlignment = alignment; 118 mMargin = margin; 119 mSpacing = spacing; 120 } 121 122 /// The Orientation this BoxLayout is using. 123 final Orientation orientation() const { return mOrientation; } 124 125 /// Sets the Orientation of this BoxLayout. 126 final void setOrientation(Orientation orientation) { mOrientation = orientation; } 127 128 /// The Alignment of this BoxLayout. 129 final Alignment alignment() const { return mAlignment; } 130 131 /// Sets the Alignment of this BoxLayout. 132 final void setAlignment(Alignment alignment) { mAlignment = alignment; } 133 134 /// The margin of this BoxLayout. 135 final int margin() const { return mMargin; } 136 137 /// Sets the margin of this BoxLayout. 138 final void margin(int margin) { mMargin = margin; } 139 140 /// The spacing this BoxLayout is using to pad in between widgets. 141 final int spacing() const { return mSpacing; } 142 143 /// Sets the spacing of this BoxLayout. 144 final void setSpacing(int spacing) { mSpacing = spacing; } 145 146 /// Implementation of the layout interface 147 /// See `Layout.preferredSize`. 148 override Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const 149 { 150 Vector2i size = Vector2i(2*mMargin, 2*mMargin); 151 152 int yOffset = 0; 153 auto window = cast(Window) widget; 154 if (window && window.title().length) { 155 if (mOrientation == Orientation.Vertical) 156 size[1] += widget.theme.mWindowHeaderHeight - mMargin/2; 157 else 158 yOffset = widget.theme.mWindowHeaderHeight; 159 } 160 161 bool first = true; 162 int axis1 = cast(int) mOrientation; 163 int axis2 = (cast(int) mOrientation + 1)%2; 164 foreach (w; widget.children) 165 { 166 if (!w.visible || w is skipped) 167 continue; 168 if (first) 169 first = false; 170 else 171 size[axis1] += mSpacing; 172 173 Vector2i ps = w.preferredSize(ctx); 174 Vector2i fs = w.fixedSize(); 175 auto targetSize = Vector2i( 176 fs[0] ? fs[0] : ps[0], 177 fs[1] ? fs[1] : ps[1] 178 ); 179 180 size[axis1] += targetSize[axis1]; 181 size[axis2] = max(size[axis2], targetSize[axis2] + 2*mMargin); 182 first = false; 183 } 184 return size + Vector2i(0, yOffset); 185 } 186 187 /// See `Layout.performLayout`. 188 override void performLayout(NanoContext ctx, Widget widget) const 189 { 190 Vector2i fs_w = widget.fixedSize(); 191 auto containerSize = Vector2i( 192 fs_w[0] ? fs_w[0] : widget.width, 193 fs_w[1] ? fs_w[1] : widget.height 194 ); 195 196 int axis1 = cast(int) mOrientation; 197 int axis2 = (cast(int) mOrientation + 1)%2; 198 int position = mMargin; 199 int yOffset = 0; 200 201 import nanogui.window : Window; 202 auto window = cast(const Window)(widget); 203 if (window && window.title.length) 204 { 205 if (mOrientation == Orientation.Vertical) 206 { 207 position += widget.theme.mWindowHeaderHeight - mMargin/2; 208 } 209 else 210 { 211 yOffset = widget.theme.mWindowHeaderHeight; 212 containerSize[1] -= yOffset; 213 } 214 } 215 216 bool first = true; 217 foreach(w; widget.children) { 218 if (!w.visible) 219 continue; 220 if (first) 221 first = false; 222 else 223 position += mSpacing; 224 225 Vector2i ps = w.preferredSize(ctx), fs = w.fixedSize(); 226 auto targetSize = Vector2i( 227 fs[0] ? fs[0] : ps[0], 228 fs[1] ? fs[1] : ps[1] 229 ); 230 auto pos = Vector2i(0, yOffset); 231 232 pos[axis1] = position; 233 234 final switch (mAlignment) 235 { 236 case Alignment.Minimum: 237 pos[axis2] += mMargin; 238 break; 239 case Alignment.Middle: 240 pos[axis2] += (containerSize[axis2] - targetSize[axis2]) / 2; 241 break; 242 case Alignment.Maximum: 243 pos[axis2] += containerSize[axis2] - targetSize[axis2] - mMargin * 2; 244 break; 245 case Alignment.Fill: 246 pos[axis2] += mMargin; 247 targetSize[axis2] = fs[axis2] ? fs[axis2] : (containerSize[axis2] - mMargin * 2); 248 break; 249 } 250 251 w.position(pos); 252 w.size(targetSize); 253 w.performLayout(ctx); 254 position += targetSize[axis1]; 255 } 256 } 257 258 protected: 259 /// The Orientation of this BoxLayout. 260 Orientation mOrientation; 261 262 /// The Alignment of this BoxLayout. 263 Alignment mAlignment; 264 265 /// The margin of this BoxLayout. 266 int mMargin; 267 268 /// The spacing between widgets of this BoxLayout. 269 int mSpacing; 270 } 271 272 /** 273 * Special layout for widgets grouped by labels. 274 * 275 * This widget resembles a box layout in that it arranges a set of widgets 276 * vertically. All widgets are indented on the horizontal axis except for 277 * `Label` widgets, which are not indented. 278 * 279 * This creates a pleasing layout where a number of widgets are grouped 280 * under some high-level heading. 281 */ 282 class GroupLayout : Layout 283 { 284 public: 285 /** 286 * Creates a GroupLayout. 287 * 288 * Params: 289 * margin = The margin around the widgets added. 290 * spacing = The spacing between widgets added. 291 * groupSpacing = The spacing between groups (groups are defined by each Label added). 292 * groupIndent = The amount to indent widgets in a group (underneath a Label). 293 */ 294 this(int margin = 15, int spacing = 6, int groupSpacing = 14, 295 int groupIndent = 20) 296 { 297 mMargin = margin; 298 mSpacing = spacing; 299 mGroupSpacing = groupSpacing; 300 mGroupIndent = groupIndent; 301 } 302 303 /// The margin of this GroupLayout. 304 final int margin() const { return mMargin; } 305 306 /// Sets the margin of this GroupLayout. 307 final void margin(int margin) { mMargin = margin; } 308 309 /// The spacing between widgets of this GroupLayout. 310 final int spacing() const { return mSpacing; } 311 312 /// Sets the spacing between widgets of this GroupLayout. 313 final void spacing(int spacing) { mSpacing = spacing; } 314 315 /// The indent of widgets in a group (underneath a Label) of this GroupLayout. 316 final int groupIndent() const { return mGroupIndent; } 317 318 /// Sets the indent of widgets in a group (underneath a Label) of this GroupLayout. 319 final void groupIndent(int groupIndent) { mGroupIndent = groupIndent; } 320 321 /// The spacing between groups of this GroupLayout. 322 final int groupSpacing() const { return mGroupSpacing; } 323 324 /// Sets the spacing between groups of this GroupLayout. 325 final void groupSpacing(int groupSpacing) { mGroupSpacing = groupSpacing; } 326 327 /// Implementation of the layout interface 328 /// See `Layout.preferredSize`. 329 override Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const 330 { 331 int height = mMargin, width = 2*mMargin; 332 333 import nanogui.window : Window; 334 auto window = cast(const Window) widget; 335 if (window && window.title.length) 336 height += widget.theme.mWindowHeaderHeight - mMargin/2; 337 338 bool first = true, indent = false; 339 foreach (c; widget.children) { 340 if (!c.visible || c is skipped) 341 continue; 342 import nanogui.label : Label; 343 auto label = cast(const Label) c; 344 if (!first) 345 height += (label is null) ? mSpacing : mGroupSpacing; 346 first = false; 347 348 Vector2i ps = c.preferredSize(ctx), fs = c.fixedSize(); 349 auto targetSize = Vector2i( 350 fs[0] ? fs[0] : ps[0], 351 fs[1] ? fs[1] : ps[1] 352 ); 353 354 bool indentCur = indent && label is null; 355 height += targetSize.y; 356 357 width = max(width, targetSize.x + 2*mMargin + (indentCur ? mGroupIndent : 0)); 358 359 if (label) 360 indent = label.caption().length != 0; 361 } 362 height += mMargin; 363 return Vector2i(width, height); 364 } 365 366 /// See `Layout.performLayout`. 367 override void performLayout(NanoContext ctx, Widget widget) const 368 { 369 int height = mMargin, availableWidth = 370 (widget.fixedWidth() ? widget.fixedWidth() : widget.width()) - 2*mMargin; 371 372 const Window window = cast(const Window) widget; 373 if (window && window.title.length) 374 height += widget.theme.mWindowHeaderHeight - mMargin/2; 375 376 bool first = true, indent = false; 377 foreach (c; widget.children) { 378 if (!c.visible) 379 continue; 380 import nanogui.label : Label; 381 const Label label = cast(const Label) c; 382 if (!first) 383 height += (label is null) ? mSpacing : mGroupSpacing; 384 first = false; 385 386 bool indentCur = indent && label is null; 387 Vector2i ps = Vector2i(availableWidth - (indentCur ? mGroupIndent : 0), 388 c.preferredSize(ctx).y); 389 Vector2i fs = c.fixedSize(); 390 391 auto targetSize = Vector2i( 392 fs[0] ? fs[0] : ps[0], 393 fs[1] ? fs[1] : ps[1] 394 ); 395 396 c.position = Vector2i(mMargin + (indentCur ? mGroupIndent : 0), height); 397 c.size = targetSize; 398 c.performLayout(ctx); 399 400 height += targetSize.y; 401 402 if (label) 403 indent = label.caption != ""; 404 } 405 } 406 407 protected: 408 /// The margin of this GroupLayout. 409 int mMargin; 410 411 /// The spacing between widgets of this GroupLayout. 412 int mSpacing; 413 414 /// The spacing between groups of this GroupLayout. 415 int mGroupSpacing; 416 417 /// The indent amount of a group under its defining Label of this GroupLayout. 418 int mGroupIndent; 419 } 420 421 /** 422 * Grid layout. 423 * 424 * Widgets are arranged in a grid that has a fixed grid resolution `resolution` 425 * along one of the axes. The layout orientation indicates the fixed dimension; 426 * widgets are also appended on this axis. The spacing between items can be 427 * specified per axis. The horizontal/vertical alignment can be specified per 428 * row and column. 429 */ 430 class GridLayout : Layout 431 { 432 public: 433 /** 434 * Create a 2-column grid layout by default. 435 * 436 * Params: 437 * orientation = The fixed dimension of this GridLayout. 438 * resolution = The number of rows or columns in the grid (depending on the Orientation). 439 * alignment = How widgets should be aligned within each grid cell. 440 * margin = The amount of spacing to add around the border of the grid. 441 * spacing = The amount of spacing between widgets added to the grid. 442 */ 443 this(Orientation orientation = Orientation.Horizontal, int resolution = 2, 444 Alignment alignment = Alignment.Middle, 445 int margin = 0, int spacing = 0) 446 { 447 mOrientation = orientation; 448 mResolution = resolution; 449 mMargin = margin < 0 ? 0 : margin; 450 mSpacing = Vector2i(spacing < 0 ? 0 : spacing); 451 452 mDefaultAlignment[0] = mDefaultAlignment[1] = alignment; 453 } 454 455 /// The Orientation of this GridLayout. 456 final Orientation orientation() const { return mOrientation; } 457 458 /// Sets the Orientation of this GridLayout. 459 final void orientation(Orientation orientation) { 460 mOrientation = orientation; 461 } 462 463 /// The number of rows or columns (depending on the Orientation) of this GridLayout. 464 final int resolution() const { return mResolution; } 465 466 /// Sets the number of rows or columns (depending on the Orientation) of this GridLayout. 467 final void resolution(int resolution) { mResolution = resolution; } 468 469 /// The spacing at the specified axis (row or column number, depending on the Orientation). 470 final int spacing(int axis) const { return mSpacing[axis]; } 471 472 /// Sets the spacing for a specific axis. 473 final void spacing(int axis, int spacing) 474 { 475 if (spacing < 0) 476 return; 477 mSpacing[axis] = spacing; 478 } 479 480 /// Sets the spacing for all axes. 481 final void spacing(int spacing) 482 { 483 if (spacing < 0) 484 return; 485 mSpacing[0] = mSpacing[1] = spacing; 486 } 487 488 /// The margin around this GridLayout. 489 final int margin() const { return mMargin; } 490 491 /// Sets the margin of this GridLayout. 492 final void margin(int margin) 493 { 494 if (margin < 0) 495 return; 496 mMargin = margin; 497 } 498 499 /** 500 * The Alignment of the specified axis (row or column number, depending on 501 * the Orientation) at the specified index of that row or column. 502 */ 503 final Alignment alignment(int axis, int item) const 504 { 505 if (item < cast(int) mAlignment[axis].length) 506 return mAlignment[axis][item]; 507 else 508 return mDefaultAlignment[axis]; 509 } 510 511 /// Sets the Alignment of the columns. 512 final void colAlignment(Alignment value) { mDefaultAlignment[0] = value; } 513 514 /// Sets the Alignment of the rows. 515 final void rowAlignment(Alignment value) { mDefaultAlignment[1] = value; } 516 517 /// Use this to set variable Alignment for columns. 518 final void colAlignment(Array!Alignment value) { mAlignment[0] = value; } 519 520 /// Use this to set variable Alignment for rows. 521 final void rowAlignment(Array!Alignment value) { mAlignment[1] = value; } 522 523 /// Implementation of the layout interface 524 /// See `Layout.preferredSize`. 525 override Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const 526 { 527 /* Compute minimum row / column sizes */ 528 import std.algorithm : sum; 529 Array!(int)[2] grid; 530 computeLayout(ctx, widget, grid, skipped); 531 532 auto size = Vector2i( 533 2*mMargin + sum(grid[0][]) 534 + max(cast(int) grid[0].length - 1, 0) * mSpacing[0], 535 2*mMargin + sum(grid[1][]) 536 + max(cast(int) grid[1].length - 1, 0) * mSpacing[1] 537 ); 538 539 const Window window = cast(const Window) widget; 540 if (window && window.title.length) 541 size[1] += widget.theme.mWindowHeaderHeight - mMargin/2; 542 543 return size; 544 } 545 546 /// See `Layout.performLayout`. 547 override void performLayout(NanoContext ctx, Widget widget) const 548 { 549 Vector2i fs_w = widget.fixedSize; 550 auto containerSize = Vector2i( 551 fs_w[0] ? fs_w[0] : widget.width, 552 fs_w[1] ? fs_w[1] : widget.height 553 ); 554 555 /* Compute minimum row / column sizes */ 556 Array!(int)[2] grid; 557 computeLayout(ctx, widget, grid, null); 558 if (grid[1].length == 0) 559 return; 560 int[2] dim = [ cast(int) grid[0].length, cast(int) grid[1].length ]; 561 562 Vector2i extra; 563 const Window window = cast(const Window) widget; 564 if (window && window.title.length) 565 extra[1] += widget.theme.mWindowHeaderHeight - mMargin / 2; 566 567 /* Strech to size provided by \c widget */ 568 foreach (int i; 0..2) // iterate over axes 569 { 570 // set margin + header if any 571 int gridSize = 2 * mMargin + extra[i]; 572 // add widgets size 573 foreach(s; grid[i]) 574 gridSize += s; 575 // add spacing between widgets 576 gridSize += mSpacing[i] * (grid[i].length - 1); 577 578 if (gridSize < containerSize[i]) { 579 /* Re-distribute remaining space evenly */ 580 int gap = containerSize[i] - gridSize; 581 int g = gap / dim[i]; 582 int rest = gap - g * dim[i]; 583 for (int j = 0; j < dim[i]; ++j) 584 grid[i][j] += g; 585 assert(rest < dim[i]); 586 for (int j = 0; rest > 0 && j < dim[i]; --rest, ++j) 587 grid[i][j] += 1; 588 } 589 } 590 591 int axis1 = cast(int) mOrientation, axis2 = (axis1 + 1) % 2; 592 Vector2i start = Vector2i(mMargin, mMargin) + extra; 593 594 size_t numChildren = widget.children.length; 595 size_t child = 0; 596 597 Vector2i pos = start; 598 for (int i2 = 0; i2 < dim[axis2]; i2++) { 599 pos[axis1] = start[axis1]; 600 for (int i1 = 0; i1 < dim[axis1]; i1++) { 601 Widget w; 602 do { 603 if (child >= numChildren) 604 return; 605 w = widget.children()[child++]; 606 } while (!w.visible()); 607 608 Vector2i ps = w.preferredSize(ctx); 609 Vector2i fs = w.fixedSize(); 610 auto targetSize = Vector2i( 611 fs[0] ? fs[0] : ps[0], 612 fs[1] ? fs[1] : ps[1] 613 ); 614 615 auto itemPos = Vector2i(pos); 616 for (int j = 0; j < 2; j++) { 617 int axis = (axis1 + j) % 2; 618 int item = j == 0 ? i1 : i2; 619 Alignment algn = alignment(axis, item); 620 621 final switch (algn) { 622 case Alignment.Minimum: 623 break; 624 case Alignment.Middle: 625 itemPos[axis] += (grid[axis][item] - targetSize[axis]) / 2; 626 break; 627 case Alignment.Maximum: 628 itemPos[axis] += grid[axis][item] - targetSize[axis]; 629 break; 630 case Alignment.Fill: 631 targetSize[axis] = fs[axis] ? fs[axis] : grid[axis][item]; 632 break; 633 } 634 } 635 w.position(itemPos); 636 w.size(targetSize); 637 w.performLayout(ctx); 638 pos[axis1] += grid[axis1][i1] + mSpacing[axis1]; 639 } 640 pos[axis2] += grid[axis2][i2] + mSpacing[axis2]; 641 } 642 } 643 644 protected: 645 /// Compute the maximum row and column sizes 646 void computeLayout(NanoContext ctx, const Widget widget, 647 ref Array!(int)[2] grid, const Widget skipped) const 648 { 649 int axis1 = cast(int) mOrientation; 650 int axis2 = cast(int) !mOrientation; 651 size_t numChildren = widget.children.length, visibleChildren = 0; 652 foreach (w; widget.children) 653 visibleChildren += w.visible ? 1 : 0; 654 655 Vector2i dim; 656 // count of items in main axis 657 dim[axis1] = mResolution; 658 // count of items in secondary axis 659 dim[axis2] = cast(int) ((visibleChildren + mResolution - 1) / mResolution); 660 661 grid[axis1].clear(); grid[axis1].length = dim[axis1]; grid[axis1][] = 0; 662 grid[axis2].clear(); grid[axis2].length = dim[axis2]; grid[axis2][] = 0; 663 664 size_t child; 665 foreach(int i2; 0..dim[axis2]) 666 { 667 foreach(int i1; 0..dim[axis1]) 668 { 669 import std.typecons : Rebindable; 670 Rebindable!(const Widget) w; 671 do { 672 if (child >= numChildren) 673 return; 674 w = widget.children[child++]; 675 } while (!w.visible || w is skipped); 676 677 Vector2i ps = w.preferredSize(ctx); 678 Vector2i fs = w.fixedSize(); 679 auto targetSize = Vector2i( 680 fs[0] ? fs[0] : ps[0], 681 fs[1] ? fs[1] : ps[1] 682 ); 683 684 grid[axis1][i1] = max(grid[axis1][i1], targetSize[axis1]); 685 grid[axis2][i2] = max(grid[axis2][i2], targetSize[axis2]); 686 } 687 } 688 } 689 690 /// The Orientation defining this GridLayout. 691 Orientation mOrientation; 692 693 /// The default Alignment for this GridLayout. 694 Alignment[2] mDefaultAlignment; 695 696 /// The actual Alignment being used. 697 Array!(Alignment)[2] mAlignment; 698 699 /// The number of rows or columns before starting a new one, depending on the Orientation. 700 int mResolution; 701 702 /// The spacing used for each dimension. 703 Vector2i mSpacing; 704 705 /// The margin around this GridLayout. 706 int mMargin; 707 } 708 709 /** 710 * The is a fancier grid layout with support for items that span multiple rows 711 * or columns, and per-widget alignment flags. Each row and column additionally 712 * stores a stretch factor that controls how additional space is redistributed. 713 * The downside of this flexibility is that a layout anchor data structure must 714 * be provided for each widget. 715 * 716 * An example: 717 * 718 * Label label = new Label(window, "A label"); 719 * // Add a centered label at grid position (1, 5), which spans two horizontal cells 720 * layout.setAnchor(label, AdvancedGridLayout.Anchor(1, 5, 2, 1, Alignment.Middle, Alignment.Middle)); 721 * 722 * The grid is initialized with user-specified column and row size vectors 723 * (which can be expanded later on if desired). If a size value of zero is 724 * specified for a column or row, the size is set to the maximum preferred size 725 * of any widgets contained in the same row or column. Any remaining space is 726 * redistributed according to the row and column stretch factors. 727 * 728 * The high level usage somewhat resembles the classic HIG layout: 729 * 730 * - https://web.archive.org/web/20070813221705/http://www.autel.cz/dmi/tutorial.html 731 * - https://github.com/jaapgeurts/higlayout 732 */ 733 class AdvancedGridLayout : Layout 734 { 735 /** 736 *Helper struct to coordinate anchor points for the layout. 737 */ 738 struct Anchor 739 { 740 ubyte[2] pos; ///< The ``(x, y)`` position. 741 ubyte[2] size; ///< The ``(x, y)`` size. 742 Alignment[2] algn;///< The ``(x, y)`` Alignment. 743 744 /// Creates a ``0`` Anchor. 745 //this() { } 746 747 /// Create an Anchor at position ``(x, y)`` with specified Alignment. 748 this(int x, int y, Alignment horiz = Alignment.Fill, 749 Alignment vert = Alignment.Fill) 750 { 751 pos[0] = cast(ubyte) x; pos[1] = cast(ubyte) y; 752 size[0] = size[1] = 1; 753 algn[0] = horiz; algn[1] = vert; 754 } 755 756 /// Create an Anchor at position ``(x, y)`` of size ``(w, h)`` with specified alignments. 757 this(int x, int y, int w, int h, 758 Alignment horiz = Alignment.Fill, 759 Alignment vert = Alignment.Fill) 760 { 761 pos[0] = cast(ubyte) x; pos[1] = cast(ubyte) y; 762 size[0] = cast(ubyte) w; size[1] = cast(ubyte) h; 763 algn[0] = horiz; algn[1] = vert; 764 } 765 766 /// Allows for printing out Anchor position, size, and alignment. 767 string toString() const 768 { 769 import std.string : format; 770 return format("pos=(%d, %d), size=(%d, %d), align=(%d, %d)", 771 pos[0], pos[1], size[0], size[1], cast(int) algn[0], cast(int) algn[1]); 772 } 773 } 774 775 /// Creates an AdvancedGridLayout with specified columns, rows, and margin. 776 this(int[] cols, int[] rows, int margin = 0) 777 { 778 mCols = Array!int(cols); 779 mRows = Array!int(rows); 780 mMargin = margin; 781 mColStretch.length = mCols.length; mColStretch[] = 0; 782 mRowStretch.length = mRows.length; mRowStretch[] = 0; 783 } 784 785 /// The margin of this AdvancedGridLayout. 786 final int margin() const { return mMargin; } 787 788 /// Sets the margin of this AdvancedGridLayout. 789 final void margin(int margin) { mMargin = margin; } 790 791 /// Return the number of cols 792 final int colCount() const { return cast(int) mCols.length; } 793 794 /// Return the number of rows 795 final int rowCount() const { return cast(int) mRows.length; } 796 797 /// Append a row of the given size (and stretch factor) 798 final void appendRow(int size, float stretch = 0f) { mRows.insertBack(size); mRowStretch.insertBack(stretch); } 799 800 /// Append a column of the given size (and stretch factor) 801 final void appendCol(int size, float stretch = 0f) { mCols.insertBack(size); mColStretch.insertBack(stretch); } 802 803 /// Set the stretch factor of a given row 804 final void setRowStretch(int index, float stretch) { mRowStretch[index] = stretch; } 805 806 /// Set the stretch factor of a given column 807 final void setColStretch(int index, float stretch) { mColStretch[index] = stretch; } 808 809 /// Specify the anchor data structure for a given widget 810 final void setAnchor(const Widget widget, const Anchor anchor) { mAnchor[widget] = anchor; } 811 812 /// Retrieve the anchor data structure for a given widget 813 Anchor anchor(const Widget widget) const 814 { 815 auto it = widget in mAnchor; 816 if (it is null) 817 throw new Exception("Widget was not registered with the grid layout!"); 818 819 return cast(Anchor) *it; 820 } 821 822 /* Implementation of the layout interface */ 823 Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const 824 { 825 /* Compute minimum row / column sizes */ 826 Array!int[2] grid; 827 computeLayout(ctx, widget, grid); 828 829 import std.algorithm : sum; 830 831 Vector2i size = Vector2i(sum(grid[0][]), 832 sum(grid[1][])); 833 Vector2i extra = Vector2i(2 * mMargin, 2 * mMargin); 834 auto window = cast(const Window) widget; 835 if (window && window.title.length) 836 extra[1] += widget.theme().mWindowHeaderHeight - mMargin/2; 837 838 return size+extra; 839 } 840 841 void performLayout(NanoContext ctx, Widget widget) const 842 { 843 Array!int[2] grid; 844 computeLayout(ctx, widget, grid); 845 grid[0].insertBefore(grid[0][0..$], mMargin); 846 auto window = cast(const Window) widget; 847 if (window && window.title.length) 848 grid[1].insertBefore(grid[1][0..$], widget.theme.mWindowHeaderHeight + mMargin/2); 849 else 850 grid[1].insertBefore(grid[1][0..$], mMargin); 851 852 for (int axis=0; axis<2; ++axis) { 853 for (size_t i=1; i<grid[axis].length; ++i) 854 grid[axis][i] += grid[axis][i-1]; 855 856 foreach (w; widget.children()) { 857 if (!w.visible()) 858 continue; 859 Anchor anchor = this.anchor(w); 860 861 int itemPos = grid[axis][anchor.pos[axis]]; 862 int cellSize = grid[axis][anchor.pos[axis] + anchor.size[axis]] - itemPos; 863 int ps = w.preferredSize(ctx)[axis], fs = w.fixedSize()[axis]; 864 int targetSize = fs ? fs : ps; 865 866 final switch (anchor.algn[axis]) { 867 case Alignment.Minimum: 868 break; 869 case Alignment.Middle: 870 itemPos += (cellSize - targetSize) / 2; 871 break; 872 case Alignment.Maximum: 873 itemPos += cellSize - targetSize; 874 break; 875 case Alignment.Fill: 876 targetSize = fs ? fs : cellSize; 877 break; 878 } 879 880 Vector2i pos = w.position(), size = w.size(); 881 pos[axis] = itemPos; 882 size[axis] = targetSize; 883 w.position = pos; 884 w.size = size; 885 w.performLayout(ctx); 886 } 887 } 888 } 889 890 protected: 891 /// Computes the layout 892 void computeLayout(NanoContext ctx, const Widget widget, 893 ref Array!(int)[2] _grid) const 894 { 895 Vector2i fs_w = widget.fixedSize(); 896 Vector2i containerSize = Vector2i(fs_w[0] ? fs_w[0] : widget.width(), fs_w[1] ? fs_w[1] : widget.height()); 897 Vector2i extra = Vector2i(2 * mMargin, 2 * mMargin); 898 auto window = cast(const Window) widget; 899 if (window && window.title.length) 900 extra[1] += widget.theme().mWindowHeaderHeight - mMargin/2; 901 902 containerSize -= extra; 903 904 for (int axis=0; axis<2; ++axis) { 905 const sizes = axis == 0 ? mCols : mRows; 906 const stretch = axis == 0 ? mColStretch : mRowStretch; 907 908 _grid[axis].clear; 909 _grid[axis].insertBack(sizes[]); 910 911 auto grid = _grid[axis]; 912 913 for (int phase = 0; phase < 2; ++phase) { 914 foreach (const Widget w, const Anchor anchor; mAnchor) { 915 if (!w.visible()) 916 continue; 917 if ((anchor.size[axis] == 1) != (phase == 0)) 918 continue; 919 int ps = w.preferredSize(ctx)[axis], fs = w.fixedSize()[axis]; 920 int targetSize = fs ? fs : ps; 921 922 if (anchor.pos[axis] + anchor.size[axis] > cast(int) grid.length) 923 throw new Exception("Advanced grid layout: widget is out of bounds: " ~ anchor.toString); 924 925 int currentSize = 0; 926 float totalStretch = 0; 927 928 import std.algorithm : max; 929 930 for (int i = anchor.pos[axis]; 931 i < anchor.pos[axis] + anchor.size[axis]; ++i) { 932 if (sizes[i] == 0 && anchor.size[axis] == 1) 933 grid[i] = max(grid[i], targetSize); 934 currentSize += grid[i]; 935 totalStretch += stretch[i]; 936 } 937 if (targetSize <= currentSize) 938 continue; 939 if (totalStretch == 0) 940 throw new Exception("Advanced grid layout: no space to place widget: ", anchor.toString); 941 import std.math : round; 942 943 float amt = (targetSize - currentSize) / totalStretch; 944 for (int i = anchor.pos[axis]; 945 i < anchor.pos[axis] + anchor.size[axis]; ++i) { 946 grid[i] += cast(int) round(amt * stretch[i]); 947 } 948 } 949 } 950 951 import std.algorithm : sum; 952 int currentSize = sum(grid[]); 953 float totalStretch; 954 foreach(e; stretch[]) 955 totalStretch += e; 956 if (currentSize >= containerSize[axis] || totalStretch == 0) 957 continue; 958 float amt = (containerSize[axis] - currentSize) / totalStretch; 959 import std.math : round; 960 for (auto i = 0; i<grid.length; ++i) 961 grid[i] += cast(int) round(amt * stretch[i]); 962 } 963 } 964 965 protected: 966 /// The columns of this AdvancedGridLayout. 967 Array!int mCols; 968 969 /// The rows of this AdvancedGridLayout. 970 Array!int mRows; 971 972 /// The stretch for each column of this AdvancedGridLayout. 973 Array!float mColStretch; 974 975 /// The stretch for each row of this AdvancedGridLayout. 976 Array!float mRowStretch; 977 978 /// The mapping of widgets to their specified anchor points. 979 Anchor[const Widget] mAnchor; 980 981 /// The margin around this AdvancedGridLayout. 982 int mMargin; 983 }