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; 370 int availableWidth = (widget.fixedWidth() ? widget.fixedWidth() : widget.width()) - 2*mMargin; 371 if (availableWidth < 0) availableWidth = 0; 372 373 const Window window = cast(const Window) widget; 374 if (window && window.title.length) 375 height += widget.theme.mWindowHeaderHeight - mMargin/2; 376 377 bool first = true, indent = false; 378 foreach (c; widget.children) { 379 if (!c.visible) 380 continue; 381 import nanogui.label : Label; 382 const Label label = cast(const Label) c; 383 if (!first) 384 height += (label is null) ? mSpacing : mGroupSpacing; 385 first = false; 386 387 bool indentCur = indent && label is null; 388 Vector2i ps = Vector2i(availableWidth - (indentCur ? mGroupIndent : 0), 389 c.preferredSize(ctx).y); 390 Vector2i fs = c.fixedSize(); 391 392 auto targetSize = Vector2i( 393 fs[0] ? fs[0] : ps[0], 394 fs[1] ? fs[1] : ps[1] 395 ); 396 397 c.position = Vector2i(mMargin + (indentCur ? mGroupIndent : 0), height); 398 c.size = targetSize; 399 c.performLayout(ctx); 400 401 height += targetSize.y; 402 403 if (label) 404 indent = label.caption != ""; 405 } 406 } 407 408 protected: 409 /// The margin of this GroupLayout. 410 int mMargin; 411 412 /// The spacing between widgets of this GroupLayout. 413 int mSpacing; 414 415 /// The spacing between groups of this GroupLayout. 416 int mGroupSpacing; 417 418 /// The indent amount of a group under its defining Label of this GroupLayout. 419 int mGroupIndent; 420 } 421 422 /** 423 * Grid layout. 424 * 425 * Widgets are arranged in a grid that has a fixed grid resolution `resolution` 426 * along one of the axes. The layout orientation indicates the fixed dimension; 427 * widgets are also appended on this axis. The spacing between items can be 428 * specified per axis. The horizontal/vertical alignment can be specified per 429 * row and column. 430 */ 431 class GridLayout : Layout 432 { 433 public: 434 /** 435 * Create a 2-column grid layout by default. 436 * 437 * Params: 438 * orientation = The fixed dimension of this GridLayout. 439 * resolution = The number of rows or columns in the grid (depending on the Orientation). 440 * alignment = How widgets should be aligned within each grid cell. 441 * margin = The amount of spacing to add around the border of the grid. 442 * spacing = The amount of spacing between widgets added to the grid. 443 */ 444 this(Orientation orientation = Orientation.Horizontal, int resolution = 2, 445 Alignment alignment = Alignment.Middle, 446 int margin = 0, int spacing = 0) 447 { 448 mOrientation = orientation; 449 mResolution = resolution; 450 mMargin = margin < 0 ? 0 : margin; 451 mSpacing = Vector2i(spacing < 0 ? 0 : spacing); 452 453 mDefaultAlignment[0] = mDefaultAlignment[1] = alignment; 454 } 455 456 /// The Orientation of this GridLayout. 457 final Orientation orientation() const { return mOrientation; } 458 459 /// Sets the Orientation of this GridLayout. 460 final void orientation(Orientation orientation) { 461 mOrientation = orientation; 462 } 463 464 /// The number of rows or columns (depending on the Orientation) of this GridLayout. 465 final int resolution() const { return mResolution; } 466 467 /// Sets the number of rows or columns (depending on the Orientation) of this GridLayout. 468 final void resolution(int resolution) { mResolution = resolution; } 469 470 /// The spacing at the specified axis (row or column number, depending on the Orientation). 471 final int spacing(int axis) const { return mSpacing[axis]; } 472 473 /// Sets the spacing for a specific axis. 474 final void spacing(int axis, int spacing) 475 { 476 if (spacing < 0) 477 return; 478 mSpacing[axis] = spacing; 479 } 480 481 /// Sets the spacing for all axes. 482 final void spacing(int spacing) 483 { 484 if (spacing < 0) 485 return; 486 mSpacing[0] = mSpacing[1] = spacing; 487 } 488 489 /// The margin around this GridLayout. 490 final int margin() const { return mMargin; } 491 492 /// Sets the margin of this GridLayout. 493 final void margin(int margin) 494 { 495 if (margin < 0) 496 return; 497 mMargin = margin; 498 } 499 500 /** 501 * The Alignment of the specified axis (row or column number, depending on 502 * the Orientation) at the specified index of that row or column. 503 */ 504 final Alignment alignment(int axis, int item) const 505 { 506 if (item < cast(int) mAlignment[axis].length) 507 return mAlignment[axis][item]; 508 else 509 return mDefaultAlignment[axis]; 510 } 511 512 /// Sets the Alignment of the columns. 513 final void colAlignment(Alignment value) { mDefaultAlignment[0] = value; } 514 515 /// Sets the Alignment of the rows. 516 final void rowAlignment(Alignment value) { mDefaultAlignment[1] = value; } 517 518 /// Use this to set variable Alignment for columns. 519 final void colAlignment(Array!Alignment value) { mAlignment[0] = value; } 520 521 /// Use this to set variable Alignment for rows. 522 final void rowAlignment(Array!Alignment value) { mAlignment[1] = value; } 523 524 /// Implementation of the layout interface 525 /// See `Layout.preferredSize`. 526 override Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const 527 { 528 /* Compute minimum row / column sizes */ 529 import std.algorithm : sum; 530 Array!(int)[2] grid; 531 computeLayout(ctx, widget, grid, skipped); 532 533 auto size = Vector2i( 534 2*mMargin + sum(grid[0][]) 535 + max(cast(int) grid[0].length - 1, 0) * mSpacing[0], 536 2*mMargin + sum(grid[1][]) 537 + max(cast(int) grid[1].length - 1, 0) * mSpacing[1] 538 ); 539 540 const Window window = cast(const Window) widget; 541 if (window && window.title.length) 542 size[1] += widget.theme.mWindowHeaderHeight - mMargin/2; 543 544 return size; 545 } 546 547 /// See `Layout.performLayout`. 548 override void performLayout(NanoContext ctx, Widget widget) const 549 { 550 Vector2i fs_w = widget.fixedSize; 551 auto containerSize = Vector2i( 552 fs_w[0] ? fs_w[0] : widget.width, 553 fs_w[1] ? fs_w[1] : widget.height 554 ); 555 556 /* Compute minimum row / column sizes */ 557 Array!(int)[2] grid; 558 computeLayout(ctx, widget, grid, null); 559 if (grid[1].length == 0) 560 return; 561 int[2] dim = [ cast(int) grid[0].length, cast(int) grid[1].length ]; 562 563 Vector2i extra; 564 const Window window = cast(const Window) widget; 565 if (window && window.title.length) 566 extra[1] += widget.theme.mWindowHeaderHeight - mMargin / 2; 567 568 /* Strech to size provided by \c widget */ 569 foreach (int i; 0..2) // iterate over axes 570 { 571 // set margin + header if any 572 int gridSize = 2 * mMargin + extra[i]; 573 // add widgets size 574 foreach(s; grid[i]) 575 gridSize += s; 576 // add spacing between widgets 577 gridSize += mSpacing[i] * (grid[i].length - 1); 578 579 if (gridSize < containerSize[i]) { 580 /* Re-distribute remaining space evenly */ 581 int gap = containerSize[i] - gridSize; 582 int g = gap / dim[i]; 583 int rest = gap - g * dim[i]; 584 for (int j = 0; j < dim[i]; ++j) 585 grid[i][j] += g; 586 assert(rest < dim[i]); 587 for (int j = 0; rest > 0 && j < dim[i]; --rest, ++j) 588 grid[i][j] += 1; 589 } 590 } 591 592 int axis1 = cast(int) mOrientation, axis2 = (axis1 + 1) % 2; 593 Vector2i start = Vector2i(mMargin, mMargin) + extra; 594 595 size_t numChildren = widget.children.length; 596 size_t child = 0; 597 598 Vector2i pos = start; 599 for (int i2 = 0; i2 < dim[axis2]; i2++) { 600 pos[axis1] = start[axis1]; 601 for (int i1 = 0; i1 < dim[axis1]; i1++) { 602 Widget w; 603 do { 604 if (child >= numChildren) 605 return; 606 w = widget.children()[child++]; 607 } while (!w.visible()); 608 609 Vector2i ps = w.preferredSize(ctx); 610 Vector2i fs = w.fixedSize(); 611 auto targetSize = Vector2i( 612 fs[0] ? fs[0] : ps[0], 613 fs[1] ? fs[1] : ps[1] 614 ); 615 616 auto itemPos = Vector2i(pos); 617 for (int j = 0; j < 2; j++) { 618 int axis = (axis1 + j) % 2; 619 int item = j == 0 ? i1 : i2; 620 Alignment algn = alignment(axis, item); 621 622 final switch (algn) { 623 case Alignment.Minimum: 624 break; 625 case Alignment.Middle: 626 itemPos[axis] += (grid[axis][item] - targetSize[axis]) / 2; 627 break; 628 case Alignment.Maximum: 629 itemPos[axis] += grid[axis][item] - targetSize[axis]; 630 break; 631 case Alignment.Fill: 632 targetSize[axis] = fs[axis] ? fs[axis] : grid[axis][item]; 633 break; 634 } 635 } 636 w.position(itemPos); 637 w.size(targetSize); 638 w.performLayout(ctx); 639 pos[axis1] += grid[axis1][i1] + mSpacing[axis1]; 640 } 641 pos[axis2] += grid[axis2][i2] + mSpacing[axis2]; 642 } 643 } 644 645 protected: 646 /// Compute the maximum row and column sizes 647 void computeLayout(NanoContext ctx, const Widget widget, 648 ref Array!(int)[2] grid, const Widget skipped) const 649 { 650 int axis1 = cast(int) mOrientation; 651 int axis2 = cast(int) !mOrientation; 652 size_t numChildren = widget.children.length, visibleChildren = 0; 653 foreach (w; widget.children) 654 visibleChildren += w.visible ? 1 : 0; 655 656 Vector2i dim; 657 // count of items in main axis 658 dim[axis1] = mResolution; 659 // count of items in secondary axis 660 dim[axis2] = cast(int) ((visibleChildren + mResolution - 1) / mResolution); 661 662 grid[axis1].clear(); grid[axis1].length = dim[axis1]; grid[axis1][] = 0; 663 grid[axis2].clear(); grid[axis2].length = dim[axis2]; grid[axis2][] = 0; 664 665 size_t child; 666 foreach(int i2; 0..dim[axis2]) 667 { 668 foreach(int i1; 0..dim[axis1]) 669 { 670 import std.typecons : Rebindable; 671 Rebindable!(const Widget) w; 672 do { 673 if (child >= numChildren) 674 return; 675 w = widget.children[child++]; 676 } while (!w.visible || w is skipped); 677 678 Vector2i ps = w.preferredSize(ctx); 679 Vector2i fs = w.fixedSize(); 680 auto targetSize = Vector2i( 681 fs[0] ? fs[0] : ps[0], 682 fs[1] ? fs[1] : ps[1] 683 ); 684 685 grid[axis1][i1] = max(grid[axis1][i1], targetSize[axis1]); 686 grid[axis2][i2] = max(grid[axis2][i2], targetSize[axis2]); 687 } 688 } 689 } 690 691 /// The Orientation defining this GridLayout. 692 Orientation mOrientation; 693 694 /// The default Alignment for this GridLayout. 695 Alignment[2] mDefaultAlignment; 696 697 /// The actual Alignment being used. 698 Array!(Alignment)[2] mAlignment; 699 700 /// The number of rows or columns before starting a new one, depending on the Orientation. 701 int mResolution; 702 703 /// The spacing used for each dimension. 704 Vector2i mSpacing; 705 706 /// The margin around this GridLayout. 707 int mMargin; 708 } 709 710 /** 711 * The is a fancier grid layout with support for items that span multiple rows 712 * or columns, and per-widget alignment flags. Each row and column additionally 713 * stores a stretch factor that controls how additional space is redistributed. 714 * The downside of this flexibility is that a layout anchor data structure must 715 * be provided for each widget. 716 * 717 * An example: 718 * 719 * Label label = new Label(window, "A label"); 720 * // Add a centered label at grid position (1, 5), which spans two horizontal cells 721 * layout.setAnchor(label, AdvancedGridLayout.Anchor(1, 5, 2, 1, Alignment.Middle, Alignment.Middle)); 722 * 723 * The grid is initialized with user-specified column and row size vectors 724 * (which can be expanded later on if desired). If a size value of zero is 725 * specified for a column or row, the size is set to the maximum preferred size 726 * of any widgets contained in the same row or column. Any remaining space is 727 * redistributed according to the row and column stretch factors. 728 * 729 * The high level usage somewhat resembles the classic HIG layout: 730 * 731 * - https://web.archive.org/web/20070813221705/http://www.autel.cz/dmi/tutorial.html 732 * - https://github.com/jaapgeurts/higlayout 733 */ 734 class AdvancedGridLayout : Layout 735 { 736 /** 737 *Helper struct to coordinate anchor points for the layout. 738 */ 739 struct Anchor 740 { 741 ubyte[2] pos; ///< The ``(x, y)`` position. 742 ubyte[2] size; ///< The ``(x, y)`` size. 743 Alignment[2] algn;///< The ``(x, y)`` Alignment. 744 745 /// Creates a ``0`` Anchor. 746 //this() { } 747 748 /// Create an Anchor at position ``(x, y)`` with specified Alignment. 749 this(int x, int y, Alignment horiz = Alignment.Fill, 750 Alignment vert = Alignment.Fill) 751 { 752 pos[0] = cast(ubyte) x; pos[1] = cast(ubyte) y; 753 size[0] = size[1] = 1; 754 algn[0] = horiz; algn[1] = vert; 755 } 756 757 /// Create an Anchor at position ``(x, y)`` of size ``(w, h)`` with specified alignments. 758 this(int x, int y, int w, int h, 759 Alignment horiz = Alignment.Fill, 760 Alignment vert = Alignment.Fill) 761 { 762 pos[0] = cast(ubyte) x; pos[1] = cast(ubyte) y; 763 size[0] = cast(ubyte) w; size[1] = cast(ubyte) h; 764 algn[0] = horiz; algn[1] = vert; 765 } 766 767 /// Allows for printing out Anchor position, size, and alignment. 768 string toString() const 769 { 770 import std.string : format; 771 return format("pos=(%d, %d), size=(%d, %d), align=(%d, %d)", 772 pos[0], pos[1], size[0], size[1], cast(int) algn[0], cast(int) algn[1]); 773 } 774 } 775 776 /// Creates an AdvancedGridLayout with specified columns, rows, and margin. 777 this(int[] cols, int[] rows, int margin = 0) 778 { 779 mCols = Array!int(cols); 780 mRows = Array!int(rows); 781 mMargin = margin; 782 mColStretch.length = mCols.length; mColStretch[] = 0; 783 mRowStretch.length = mRows.length; mRowStretch[] = 0; 784 } 785 786 /// The margin of this AdvancedGridLayout. 787 final int margin() const { return mMargin; } 788 789 /// Sets the margin of this AdvancedGridLayout. 790 final void margin(int margin) { mMargin = margin; } 791 792 /// Return the number of cols 793 final int colCount() const { return cast(int) mCols.length; } 794 795 /// Return the number of rows 796 final int rowCount() const { return cast(int) mRows.length; } 797 798 /// Append a row of the given size (and stretch factor) 799 final void appendRow(int size, float stretch = 0f) { mRows.insertBack(size); mRowStretch.insertBack(stretch); } 800 801 /// Append a column of the given size (and stretch factor) 802 final void appendCol(int size, float stretch = 0f) { mCols.insertBack(size); mColStretch.insertBack(stretch); } 803 804 /// Set the stretch factor of a given row 805 final void setRowStretch(int index, float stretch) { mRowStretch[index] = stretch; } 806 807 /// Set the stretch factor of a given column 808 final void setColStretch(int index, float stretch) { mColStretch[index] = stretch; } 809 810 /// Specify the anchor data structure for a given widget 811 final void setAnchor(const Widget widget, const Anchor anchor) { mAnchor[widget] = anchor; } 812 813 /// Retrieve the anchor data structure for a given widget 814 Anchor anchor(const Widget widget) const 815 { 816 auto it = widget in mAnchor; 817 if (it is null) 818 throw new Exception("Widget was not registered with the grid layout!"); 819 820 return cast(Anchor) *it; 821 } 822 823 /* Implementation of the layout interface */ 824 Vector2i preferredSize(NanoContext ctx, const Widget widget, const Widget skipped = null) const 825 { 826 /* Compute minimum row / column sizes */ 827 Array!int[2] grid; 828 computeLayout(ctx, widget, grid); 829 830 import std.algorithm : sum; 831 832 Vector2i size = Vector2i(sum(grid[0][]), 833 sum(grid[1][])); 834 Vector2i extra = Vector2i(2 * mMargin, 2 * mMargin); 835 auto window = cast(const Window) widget; 836 if (window && window.title.length) 837 extra[1] += widget.theme().mWindowHeaderHeight - mMargin/2; 838 839 return size+extra; 840 } 841 842 void performLayout(NanoContext ctx, Widget widget) const 843 { 844 Array!int[2] grid; 845 computeLayout(ctx, widget, grid); 846 grid[0].insertBefore(grid[0][0..$], mMargin); 847 auto window = cast(const Window) widget; 848 if (window && window.title.length) 849 grid[1].insertBefore(grid[1][0..$], widget.theme.mWindowHeaderHeight + mMargin/2); 850 else 851 grid[1].insertBefore(grid[1][0..$], mMargin); 852 853 for (int axis=0; axis<2; ++axis) { 854 for (size_t i=1; i<grid[axis].length; ++i) 855 grid[axis][i] += grid[axis][i-1]; 856 857 foreach (w; widget.children()) { 858 if (!w.visible()) 859 continue; 860 Anchor anchor = this.anchor(w); 861 862 int itemPos = grid[axis][anchor.pos[axis]]; 863 int cellSize = grid[axis][anchor.pos[axis] + anchor.size[axis]] - itemPos; 864 int ps = w.preferredSize(ctx)[axis], fs = w.fixedSize()[axis]; 865 int targetSize = fs ? fs : ps; 866 867 final switch (anchor.algn[axis]) { 868 case Alignment.Minimum: 869 break; 870 case Alignment.Middle: 871 itemPos += (cellSize - targetSize) / 2; 872 break; 873 case Alignment.Maximum: 874 itemPos += cellSize - targetSize; 875 break; 876 case Alignment.Fill: 877 targetSize = fs ? fs : cellSize; 878 break; 879 } 880 881 Vector2i pos = w.position(), size = w.size(); 882 pos[axis] = itemPos; 883 size[axis] = targetSize; 884 w.position = pos; 885 w.size = size; 886 w.performLayout(ctx); 887 } 888 } 889 } 890 891 protected: 892 /// Computes the layout 893 void computeLayout(NanoContext ctx, const Widget widget, 894 ref Array!(int)[2] _grid) const 895 { 896 Vector2i fs_w = widget.fixedSize(); 897 Vector2i containerSize = Vector2i(fs_w[0] ? fs_w[0] : widget.width(), fs_w[1] ? fs_w[1] : widget.height()); 898 Vector2i extra = Vector2i(2 * mMargin, 2 * mMargin); 899 auto window = cast(const Window) widget; 900 if (window && window.title.length) 901 extra[1] += widget.theme().mWindowHeaderHeight - mMargin/2; 902 903 containerSize -= extra; 904 905 for (int axis=0; axis<2; ++axis) { 906 const sizes = axis == 0 ? mCols : mRows; 907 const stretch = axis == 0 ? mColStretch : mRowStretch; 908 909 _grid[axis].clear; 910 _grid[axis].insertBack(sizes[]); 911 912 auto grid = _grid[axis]; 913 914 for (int phase = 0; phase < 2; ++phase) { 915 foreach (const Widget w, const Anchor anchor; mAnchor) { 916 if (!w.visible()) 917 continue; 918 if ((anchor.size[axis] == 1) != (phase == 0)) 919 continue; 920 int ps = w.preferredSize(ctx)[axis], fs = w.fixedSize()[axis]; 921 int targetSize = fs ? fs : ps; 922 923 if (anchor.pos[axis] + anchor.size[axis] > cast(int) grid.length) 924 throw new Exception("Advanced grid layout: widget is out of bounds: " ~ anchor.toString); 925 926 int currentSize = 0; 927 float totalStretch = 0; 928 929 import std.algorithm : max; 930 931 for (int i = anchor.pos[axis]; 932 i < anchor.pos[axis] + anchor.size[axis]; ++i) { 933 if (sizes[i] == 0 && anchor.size[axis] == 1) 934 grid[i] = max(grid[i], targetSize); 935 currentSize += grid[i]; 936 totalStretch += stretch[i]; 937 } 938 if (targetSize <= currentSize) 939 continue; 940 if (totalStretch == 0) 941 throw new Exception("Advanced grid layout: no space to place widget: ", anchor.toString); 942 import std.math : round; 943 944 float amt = (targetSize - currentSize) / totalStretch; 945 for (int i = anchor.pos[axis]; 946 i < anchor.pos[axis] + anchor.size[axis]; ++i) { 947 grid[i] += cast(int) round(amt * stretch[i]); 948 } 949 } 950 } 951 952 import std.algorithm : sum; 953 int currentSize = sum(grid[]); 954 float totalStretch; 955 foreach(e; stretch[]) 956 totalStretch += e; 957 if (currentSize >= containerSize[axis] || totalStretch == 0) 958 continue; 959 float amt = (containerSize[axis] - currentSize) / totalStretch; 960 import std.math : round; 961 for (auto i = 0; i<grid.length; ++i) 962 grid[i] += cast(int) round(amt * stretch[i]); 963 } 964 } 965 966 protected: 967 /// The columns of this AdvancedGridLayout. 968 Array!int mCols; 969 970 /// The rows of this AdvancedGridLayout. 971 Array!int mRows; 972 973 /// The stretch for each column of this AdvancedGridLayout. 974 Array!float mColStretch; 975 976 /// The stretch for each row of this AdvancedGridLayout. 977 Array!float mRowStretch; 978 979 /// The mapping of widgets to their specified anchor points. 980 Anchor[const Widget] mAnchor; 981 982 /// The margin around this AdvancedGridLayout. 983 int mMargin; 984 }