1 /* 2 * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.layout; 27 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 import javafx.beans.property.BooleanProperty; 32 import javafx.beans.property.DoubleProperty; 33 import javafx.beans.property.ObjectProperty; 34 import javafx.collections.ListChangeListener.Change; 35 import javafx.collections.ObservableList; 36 import javafx.geometry.HPos; 37 import javafx.geometry.Insets; 38 import javafx.geometry.Orientation; 39 import javafx.geometry.Pos; 40 import javafx.geometry.VPos; 41 import javafx.scene.Group; 42 import javafx.scene.Node; 43 import javafx.scene.paint.Color; 44 import javafx.scene.shape.Line; 45 import com.sun.javafx.collections.TrackableObservableList; 46 import javafx.css.StyleableBooleanProperty; 47 import javafx.css.StyleableDoubleProperty; 48 import javafx.css.StyleableObjectProperty; 49 import javafx.css.CssMetaData; 50 import javafx.css.converter.BooleanConverter; 51 import javafx.css.converter.EnumConverter; 52 import javafx.css.converter.SizeConverter; 53 import java.util.Arrays; 54 import java.util.BitSet; 55 import java.util.Iterator; 56 import java.util.Map.Entry; 57 import java.util.Set; 58 import java.util.SortedMap; 59 import java.util.TreeMap; 60 import java.util.TreeSet; 61 62 import javafx.beans.Observable; 63 import javafx.css.Styleable; 64 import javafx.css.StyleableProperty; 65 import javafx.geometry.BoundingBox; 66 import javafx.geometry.Bounds; 67 import javafx.util.Callback; 68 69 70 71 /** 72 * GridPane lays out its children within a flexible grid of rows and columns. 73 * If a border and/or padding is set, then its content will be layed out within 74 * those insets. 75 * <p> 76 * A child may be placed anywhere within the grid and may span multiple 77 * rows/columns. Children may freely overlap within rows/columns and their 78 * stacking order will be defined by the order of the gridpane's children list 79 * (0th node in back, last node in front). 80 * <p> 81 * GridPane may be styled with backgrounds and borders using CSS. See 82 * {@link javafx.scene.layout.Region Region} superclass for details.</p> 83 * 84 * <h4>Grid Constraints</h4> 85 * <p> 86 * A child's placement within the grid is defined by it's layout constraints: 87 * <p> 88 * <table border="1"> 89 * <tr><th>Constraint</th><th>Type</th><th>Description</th></tr> 90 * <tr><td>columnIndex</td><td>integer</td><td>column where child's layout area starts.</td></tr> 91 * <tr><td>rowIndex</td><td>integer</td><td>row where child's layout area starts.</td></tr> 92 * <tr><td>columnSpan</td><td>integer</td><td>the number of columns the child's layout area spans horizontally.</td></tr> 93 * <tr><td>rowSpan</td><td>integer</td><td>the number of rows the child's layout area spans vertically.</td></tr> 94 * </table> 95 * <p> 96 * If the row/column indices are not explicitly set, then the child will be placed 97 * in the first row/column. If row/column spans are not set, they will default to 1. 98 * A child's placement constraints can be changed dynamically and the gridpane 99 * will update accordingly. 100 * <p> 101 * The total number of rows/columns does not need to be specified up front as the 102 * gridpane will automatically expand/contract the grid to accommodate the content. 103 * <p> 104 * To use the GridPane, an application needs to set the layout constraints on 105 * the children and add those children to the gridpane instance. 106 * Constraints are set on the children using static setter methods on the GridPane 107 * class: 108 * <pre><code> GridPane gridpane = new GridPane(); 109 * 110 * // Set one constraint at a time... 111 * // Places the button at the first row and second column 112 * Button button = new Button(); 113 * <b>GridPane.setRowIndex(button, 0); 114 * GridPane.setColumnIndex(button, 1);</b> 115 * 116 * // or convenience methods set more than one constraint at once... 117 * Label label = new Label(); 118 * <b>GridPane.setConstraints(label, 2, 0);</b> // column=2 row=0 119 * 120 * // don't forget to add children to gridpane 121 * <b>gridpane.getChildren().addAll(button, label);</b> 122 * </code></pre> 123 * 124 * Applications may also use convenience methods which combine the steps of 125 * setting the constraints and adding the children: 126 * <pre><code> 127 * GridPane gridpane = new GridPane(); 128 * <b>gridpane.add(new Button(), 1, 0);</b> // column=1 row=0 129 * <b>gridpane.add(new Label(), 2, 0);</b> // column=2 row=0 130 * </code></pre> 131 * 132 * 133 * <h4>Row/Column Sizing</h4> 134 * 135 * By default, rows and columns will be sized to fit their content; 136 * a column will be wide enough to accommodate the widest child, a 137 * row tall enough to fit the tallest child.However, if an application needs 138 * to explicitly control the size of rows or columns, it may do so by adding 139 * RowConstraints and ColumnConstraints objects to specify those metrics. 140 * For example, to create a grid with two fixed-width columns: 141 * <pre><code> 142 * GridPane gridpane = new GridPane(); 143 * <b>gridpane.getColumnConstraints().add(new ColumnConstraints(100));</b> // column 0 is 100 wide 144 * <b>gridpane.getColumnConstraints().add(new ColumnConstraints(200));</b> // column 1 is 200 wide 145 * </code></pre> 146 * By default the gridpane will resize rows/columns to their preferred sizes (either 147 * computed from content or fixed), even if the gridpane is resized larger than 148 * its preferred size. If an application needs a particular row or column to 149 * grow if there is extra space, it may set its grow priority on the RowConstraints 150 * or ColumnConstraints object. For example: 151 * <pre><code> 152 * GridPane gridpane = new GridPane(); 153 * ColumnConstraints column1 = new ColumnConstraints(100,100,Double.MAX_VALUE); 154 * <b>column1.setHgrow(Priority.ALWAYS);</b> 155 * ColumnConstraints column2 = new ColumnConstraints(100); 156 * gridpane.getColumnConstraints().addAll(column1, column2); // first column gets any extra width 157 * </code></pre> 158 * <p> 159 * Note: Nodes spanning multiple rows/columns will be also size to the preferred sizes. 160 * The affected rows/columns are resized by the following priority: grow priorities, last row. 161 * This is with respect to row/column constraints. 162 * 163 * <h4>Percentage Sizing</h4> 164 * 165 * Alternatively, RowConstraints and ColumnConstraints allow the size to be specified 166 * as a percentage of gridpane's available space: 167 * <pre><code> 168 * GridPane gridpane = new GridPane(); 169 * ColumnConstraints column1 = new ColumnConstraints(); 170 * <b>column1.setPercentWidth(50);</b> 171 * ColumnConstraints column2 = new ColumnConstraints(); 172 * <b>column2.setPercentWidth(50);</b> 173 * gridpane.getColumnConstraints().addAll(column1, column2); // each get 50% of width 174 * </code></pre> 175 * If a percentage value is set on a row/column, then that value takes precedent and the 176 * row/column's min, pref, max, and grow constraints will be ignored. 177 * <p> 178 * Note that if the sum of the widthPercent (or heightPercent) values total greater than 100, the values will 179 * be treated as weights. e.g. if 3 columns are each given a widthPercent of 50, 180 * then each will be allocated 1/3 of the gridpane's available width (50/(50+50+50)). 181 * 182 * <h4>Mixing Size Types</h4> 183 * 184 * An application may freely mix the size-types of rows/columns (computed from content, fixed, 185 * or percentage). The percentage rows/columns will always be allocated space first 186 * based on their percentage of the gridpane's available space (size minus insets and gaps). 187 * The remaining space will be allocated to rows/columns given their minimum, preferred, 188 * and maximum sizes and grow priorities. 189 * 190 * <h4>Resizable Range</h4> 191 * A gridpane's parent will resize the gridpane within the gridpane's resizable range 192 * during layout. By default the gridpane computes this range based on its content 193 * and row/column constraints as outlined in the table below. 194 * <p> 195 * <table border="1"> 196 * <tr><td></td><th>width</th><th>height</th></tr> 197 * <tr><th>minimum</th> 198 * <td>left/right insets plus the sum of each column's min width.</td> 199 * <td>top/bottom insets plus the sum of each row's min height.</td></tr> 200 * <tr><th>preferred</th> 201 * <td>left/right insets plus the sum of each column's pref width.</td> 202 * <td>top/bottom insets plus the sum of each row's pref height.</td></tr> 203 * <tr><th>maximum</th> 204 * <td>Double.MAX_VALUE</td><td>Double.MAX_VALUE</td></tr> 205 * </table> 206 * <p> 207 * A gridpane's unbounded maximum width and height are an indication to the parent that 208 * it may be resized beyond its preferred size to fill whatever space is assigned 209 * to it. 210 * <p> 211 * GridPane provides properties for setting the size range directly. These 212 * properties default to the sentinel value USE_COMPUTED_SIZE, however the 213 * application may set them to other values as needed: 214 * <pre><code> <b>gridpane.setPrefSize(300, 300);</b> 215 * // never size the gridpane larger than its preferred size: 216 * <b>gridpane.setMaxSize(Region.USE_COMPUTED_SIZE, Region.USE_COMPUTED_SIZE);</b> 217 * </code></pre> 218 * Applications may restore the computed values by setting these properties back 219 * to USE_COMPUTED_SIZE. 220 * <p> 221 * GridPane does not clip its content by default, so it is possible that childrens' 222 * bounds may extend outside its own bounds if a child's min size prevents it from 223 * being fit within it space.</p> 224 * 225 * <h4>Optional Layout Constraints</h4> 226 * 227 * An application may set additional constraints on children to customize how the 228 * child is sized and positioned within the layout area established by it's row/column 229 * indices/spans: 230 * <p> 231 * <table border="1"> 232 * <tr><th>Constraint</th><th>Type</th><th>Description</th></tr> 233 * <tr><td>halignment</td><td>javafx.geometry.HPos</td><td>The horizontal alignment of the child within its layout area.</td></tr> 234 * <tr><td>valignment</td><td>javafx.geometry.VPos</td><td>The vertical alignment of the child within its layout area.</td></tr> 235 * <tr><td>hgrow</td><td>javafx.scene.layout.Priority</td><td>The horizontal grow priority of the child.</td></tr> 236 * <tr><td>vgrow</td><td>javafx.scene.layout.Priority</td><td>The vertical grow priority of the child.</td></tr> 237 * <tr><td>margin</td><td>javafx.geometry.Insets</td><td>Margin space around the outside of the child.</td></tr> 238 * </table> 239 * <p> 240 * By default the alignment of a child within its layout area is defined by the 241 * alignment set for the row and column. If an individual alignment constraint is 242 * set on a child, that alignment will override the row/column alignment only 243 * for that child. Alignment of other children in the same row or column will 244 * not be affected. 245 * <p> 246 * Grow priorities, on the other hand, can only be applied to entire rows or columns. 247 * Therefore, if a grow priority constraint is set on a single child, it will be 248 * used to compute the default grow priority of the encompassing row/column. If 249 * a grow priority is set directly on a RowConstraint or ColumnConstraint object, 250 * it will override the value computed from content. 251 * 252 * 253 * @since JavaFX 2.0 254 */ 255 public class GridPane extends Pane { 256 257 /** 258 * Sentinel value which may be set on a child's row/column span constraint to 259 * indicate that it should span the remaining rows/columns. 260 */ 261 public static final int REMAINING = Integer.MAX_VALUE; 262 263 /******************************************************************** 264 * BEGIN static methods 265 ********************************************************************/ 266 private static final String MARGIN_CONSTRAINT = "gridpane-margin"; 267 private static final String HALIGNMENT_CONSTRAINT = "gridpane-halignment"; 268 private static final String VALIGNMENT_CONSTRAINT = "gridpane-valignment"; 269 private static final String HGROW_CONSTRAINT = "gridpane-hgrow"; 270 private static final String VGROW_CONSTRAINT = "gridpane-vgrow"; 271 private static final String ROW_INDEX_CONSTRAINT = "gridpane-row"; 272 private static final String COLUMN_INDEX_CONSTRAINT = "gridpane-column"; 273 private static final String ROW_SPAN_CONSTRAINT = "gridpane-row-span"; 274 private static final String COLUMN_SPAN_CONSTRAINT = "gridpane-column-span"; 275 private static final String FILL_WIDTH_CONSTRAINT = "gridpane-fill-width"; 276 private static final String FILL_HEIGHT_CONSTRAINT = "gridpane-fill-height"; 277 278 /** 279 * Sets the row index for the child when contained by a gridpane 280 * so that it will be positioned starting in that row of the gridpane. 281 * If a gridpane child has no row index set, it will be positioned in the 282 * first row. 283 * Setting the value to null will remove the constraint. 284 * @param child the child node of a gridpane 285 * @param value the row index of the child 286 */ 287 public static void setRowIndex(Node child, Integer value) { 288 if (value != null && value < 0) { 289 throw new IllegalArgumentException("rowIndex must be greater or equal to 0, but was "+value); 290 } 291 setConstraint(child, ROW_INDEX_CONSTRAINT, value); 292 } 293 294 /** 295 * Returns the child's row index constraint if set. 296 * @param child the child node of a gridpane 297 * @return the row index for the child or null if no row index was set 298 */ 299 public static Integer getRowIndex(Node child) { 300 return (Integer)getConstraint(child, ROW_INDEX_CONSTRAINT); 301 } 302 303 /** 304 * Sets the column index for the child when contained by a gridpane 305 * so that it will be positioned starting in that column of the gridpane. 306 * If a gridpane child has no column index set, it will be positioned in 307 * the first column. 308 * Setting the value to null will remove the constraint. 309 * @param child the child node of a gridpane 310 * @param value the column index of the child 311 */ 312 public static void setColumnIndex(Node child, Integer value) { 313 if (value != null && value < 0) { 314 throw new IllegalArgumentException("columnIndex must be greater or equal to 0, but was "+value); 315 } 316 setConstraint(child, COLUMN_INDEX_CONSTRAINT, value); 317 } 318 319 /** 320 * Returns the child's column index constraint if set. 321 * @param child the child node of a gridpane 322 * @return the column index for the child or null if no column index was set 323 */ 324 public static Integer getColumnIndex(Node child) { 325 return (Integer)getConstraint(child, COLUMN_INDEX_CONSTRAINT); 326 } 327 328 /** 329 * Sets the row span for the child when contained by a gridpane 330 * so that it will span that number of rows vertically. This may be 331 * set to REMAINING, which will cause the span to extend across all the remaining 332 * rows. 333 * <p> 334 * If a gridpane child has no row span set, it will default to spanning one row. 335 * Setting the value to null will remove the constraint. 336 * @param child the child node of a gridpane 337 * @param value the row span of the child 338 */ 339 public static void setRowSpan(Node child, Integer value) { 340 if (value != null && value < 1) { 341 throw new IllegalArgumentException("rowSpan must be greater or equal to 1, but was "+value); 342 } 343 setConstraint(child, ROW_SPAN_CONSTRAINT, value); 344 } 345 346 /** 347 * Returns the child's row-span constraint if set. 348 * @param child the child node of a gridpane 349 * @return the row span for the child or null if no row span was set 350 */ 351 public static Integer getRowSpan(Node child) { 352 return (Integer)getConstraint(child, ROW_SPAN_CONSTRAINT); 353 } 354 355 /** 356 * Sets the column span for the child when contained by a gridpane 357 * so that it will span that number of columns horizontally. This may be 358 * set to REMAINING, which will cause the span to extend across all the remaining 359 * columns. 360 * <p> 361 * If a gridpane child has no column span set, it will default to spanning one column. 362 * Setting the value to null will remove the constraint. 363 * @param child the child node of a gridpane 364 * @param value the column span of the child 365 */ 366 public static void setColumnSpan(Node child, Integer value) { 367 if (value != null && value < 1) { 368 throw new IllegalArgumentException("columnSpan must be greater or equal to 1, but was "+value); 369 } 370 setConstraint(child, COLUMN_SPAN_CONSTRAINT, value); 371 } 372 373 /** 374 * Returns the child's column-span constraint if set. 375 * @param child the child node of a gridpane 376 * @return the column span for the child or null if no column span was set 377 */ 378 public static Integer getColumnSpan(Node child) { 379 return (Integer)getConstraint(child, COLUMN_SPAN_CONSTRAINT); 380 } 381 382 /** 383 * Sets the margin for the child when contained by a gridpane. 384 * If set, the gridpane will lay it out with the margin space around it. 385 * Setting the value to null will remove the constraint. 386 * @param child the child node of a gridpane 387 * @param value the margin of space around the child 388 */ 389 public static void setMargin(Node child, Insets value) { 390 setConstraint(child, MARGIN_CONSTRAINT, value); 391 } 392 393 /** 394 * Returns the child's margin constraint if set. 395 * @param child the child node of a gridpane 396 * @return the margin for the child or null if no margin was set 397 */ 398 public static Insets getMargin(Node child) { 399 return (Insets)getConstraint(child, MARGIN_CONSTRAINT); 400 } 401 402 private double getBaselineComplementForChild(Node child) { 403 if (isNodePositionedByBaseline(child)) { 404 return rowMinBaselineComplement[getNodeRowIndex(child)]; 405 } 406 return -1; 407 } 408 409 private static final Callback<Node, Insets> marginAccessor = n -> getMargin(n); 410 411 /** 412 * Sets the horizontal alignment for the child when contained by a gridpane. 413 * If set, will override the gridpane's default horizontal alignment. 414 * Setting the value to null will remove the constraint. 415 * @param child the child node of a gridpane 416 * @param value the hozizontal alignment for the child 417 */ 418 public static void setHalignment(Node child, HPos value) { 419 setConstraint(child, HALIGNMENT_CONSTRAINT, value); 420 } 421 422 /** 423 * Returns the child's halignment constraint if set. 424 * @param child the child node of a gridpane 425 * @return the horizontal alignment for the child or null if no alignment was set 426 */ 427 public static HPos getHalignment(Node child) { 428 return (HPos)getConstraint(child, HALIGNMENT_CONSTRAINT); 429 } 430 431 /** 432 * Sets the vertical alignment for the child when contained by a gridpane. 433 * If set, will override the gridpane's default vertical alignment. 434 * Setting the value to null will remove the constraint. 435 * @param child the child node of a gridpane 436 * @param value the vertical alignment for the child 437 */ 438 public static void setValignment(Node child, VPos value) { 439 setConstraint(child, VALIGNMENT_CONSTRAINT, value); 440 } 441 442 /** 443 * Returns the child's valignment constraint if set. 444 * @param child the child node of a gridpane 445 * @return the vertical alignment for the child or null if no alignment was set 446 */ 447 public static VPos getValignment(Node child) { 448 return (VPos)getConstraint(child, VALIGNMENT_CONSTRAINT); 449 } 450 451 /** 452 * Sets the horizontal grow priority for the child when contained by a gridpane. 453 * If set, the gridpane will use the priority to allocate the child additional 454 * horizontal space if the gridpane is resized larger than it's preferred width. 455 * Setting the value to null will remove the constraint. 456 * @param child the child of a gridpane 457 * @param value the horizontal grow priority for the child 458 */ 459 public static void setHgrow(Node child, Priority value) { 460 setConstraint(child, HGROW_CONSTRAINT, value); 461 } 462 463 /** 464 * Returns the child's hgrow constraint if set. 465 * @param child the child node of a gridpane 466 * @return the horizontal grow priority for the child or null if no priority was set 467 */ 468 public static Priority getHgrow(Node child) { 469 return (Priority)getConstraint(child, HGROW_CONSTRAINT); 470 } 471 472 /** 473 * Sets the vertical grow priority for the child when contained by a gridpane. 474 * If set, the gridpane will use the priority to allocate the child additional 475 * vertical space if the gridpane is resized larger than it's preferred height. 476 * Setting the value to null will remove the constraint. 477 * @param child the child of a gridpane 478 * @param value the vertical grow priority for the child 479 */ 480 public static void setVgrow(Node child, Priority value) { 481 setConstraint(child, VGROW_CONSTRAINT, value); 482 } 483 484 /** 485 * Returns the child's vgrow constraint if set. 486 * @param child the child node of a gridpane 487 * @return the vertical grow priority for the child or null if no priority was set 488 */ 489 public static Priority getVgrow(Node child) { 490 return (Priority)getConstraint(child, VGROW_CONSTRAINT); 491 } 492 493 /** 494 * Sets the horizontal fill policy for the child when contained by a gridpane. 495 * If set, the gridpane will use the policy to determine whether node 496 * should be expanded to fill the column or resized to its preferred width. 497 * Setting the value to null will remove the constraint. 498 * If not value is specified for the node nor for the column, the default value is true. 499 * @param child the child node of a gridpane 500 * @param value the horizontal fill policy or null for unset 501 * @since JavaFX 8.0 502 */ 503 public static void setFillWidth(Node child, Boolean value) { 504 setConstraint(child, FILL_WIDTH_CONSTRAINT, value); 505 } 506 507 /** 508 * Returns the child's horizontal fill policy if set 509 * @param child the child node of a gridpane 510 * @return the horizontal fill policy for the child or null if no policy was set 511 * @since JavaFX 8.0 512 */ 513 public static Boolean isFillWidth(Node child) { 514 return (Boolean) getConstraint(child, FILL_WIDTH_CONSTRAINT); 515 } 516 517 /** 518 * Sets the vertical fill policy for the child when contained by a gridpane. 519 * If set, the gridpane will use the policy to determine whether node 520 * should be expanded to fill the row or resized to its preferred height. 521 * Setting the value to null will remove the constraint. 522 * If not value is specified for the node nor for the row, the default value is true. 523 * @param child the child node of a gridpane 524 * @param value the vertical fill policy or null for unset 525 * @since JavaFX 8.0 526 */ 527 public static void setFillHeight(Node child, Boolean value) { 528 setConstraint(child, FILL_HEIGHT_CONSTRAINT, value); 529 } 530 531 /** 532 * Returns the child's vertical fill policy if set 533 * @param child the child node of a gridpane 534 * @return the vertical fill policy for the child or null if no policy was set 535 * @since JavaFX 8.0 536 */ 537 public static Boolean isFillHeight(Node child) { 538 return (Boolean) getConstraint(child, FILL_HEIGHT_CONSTRAINT); 539 } 540 541 /** 542 * Sets the column,row indeces for the child when contained in a gridpane. 543 * @param child the child node of a gridpane 544 * @param columnIndex the column index position for the child 545 * @param rowIndex the row index position for the child 546 */ 547 public static void setConstraints(Node child, int columnIndex, int rowIndex) { 548 setRowIndex(child, rowIndex); 549 setColumnIndex(child, columnIndex); 550 } 551 552 /** 553 * Sets the column, row, column-span, and row-span value for the child when 554 * contained in a gridpane. 555 * @param child the child node of a gridpane 556 * @param columnIndex the column index position for the child 557 * @param rowIndex the row index position for the child 558 * @param columnspan the number of columns the child should span 559 * @param rowspan the number of rows the child should span 560 */ 561 public static void setConstraints(Node child, int columnIndex, int rowIndex, int columnspan, int rowspan) { 562 setRowIndex(child, rowIndex); 563 setColumnIndex(child, columnIndex); 564 setRowSpan(child, rowspan); 565 setColumnSpan(child, columnspan); 566 } 567 568 /** 569 * Sets the grid position, spans, and alignment for the child when contained in a gridpane. 570 * @param child the child node of a gridpane 571 * @param columnIndex the column index position for the child 572 * @param rowIndex the row index position for the child 573 * @param columnspan the number of columns the child should span 574 * @param rowspan the number of rows the child should span 575 * @param halignment the horizontal alignment of the child 576 * @param valignment the vertical alignment of the child 577 */ 578 public static void setConstraints(Node child, int columnIndex, int rowIndex, int columnspan, int rowspan, 579 HPos halignment, VPos valignment) { 580 setRowIndex(child, rowIndex); 581 setColumnIndex(child, columnIndex); 582 setRowSpan(child, rowspan); 583 setColumnSpan(child, columnspan); 584 setHalignment(child, halignment); 585 setValignment(child, valignment); 586 } 587 588 /** 589 * Sets the grid position, spans, and alignment for the child when contained in a gridpane. 590 * @param child the child node of a gridpane 591 * @param columnIndex the column index position for the child 592 * @param rowIndex the row index position for the child 593 * @param columnspan the number of columns the child should span 594 * @param rowspan the number of rows the child should span 595 * @param halignment the horizontal alignment of the child 596 * @param valignment the vertical alignment of the child 597 * @param hgrow the horizontal grow priority of the child 598 * @param vgrow the vertical grow priority of the child 599 */ 600 public static void setConstraints(Node child, int columnIndex, int rowIndex, int columnspan, int rowspan, 601 HPos halignment, VPos valignment, Priority hgrow, Priority vgrow) { 602 setRowIndex(child, rowIndex); 603 setColumnIndex(child, columnIndex); 604 setRowSpan(child, rowspan); 605 setColumnSpan(child, columnspan); 606 setHalignment(child, halignment); 607 setValignment(child, valignment); 608 setHgrow(child, hgrow); 609 setVgrow(child, vgrow); 610 } 611 612 /** 613 * Sets the grid position, spans, alignment, grow priorities, and margin for 614 * the child when contained in a gridpane. 615 * @param child the child node of a gridpane 616 * @param columnIndex the column index position for the child 617 * @param rowIndex the row index position for the child 618 * @param columnspan the number of columns the child should span 619 * @param rowspan the number of rows the child should span 620 * @param halignment the horizontal alignment of the child 621 * @param valignment the vertical alignment of the child 622 * @param hgrow the horizontal grow priority of the child 623 * @param vgrow the vertical grow priority of the child 624 * @param margin the margin of space around the child 625 */ 626 public static void setConstraints(Node child, int columnIndex, int rowIndex, int columnspan, int rowspan, 627 HPos halignment, VPos valignment, Priority hgrow, Priority vgrow, Insets margin) { 628 setRowIndex(child, rowIndex); 629 setColumnIndex(child, columnIndex); 630 setRowSpan(child, rowspan); 631 setColumnSpan(child, columnspan); 632 setHalignment(child, halignment); 633 setValignment(child, valignment); 634 setHgrow(child, hgrow); 635 setVgrow(child, vgrow); 636 setMargin(child, margin); 637 } 638 639 /** 640 * Removes all gridpane constraints from the child node. 641 * @param child the child node 642 */ 643 public static void clearConstraints(Node child) { 644 setRowIndex(child, null); 645 setColumnIndex(child, null); 646 setRowSpan(child, null); 647 setColumnSpan(child, null); 648 setHalignment(child, null); 649 setValignment(child, null); 650 setHgrow(child, null); 651 setVgrow(child, null); 652 setMargin(child, null); 653 } 654 655 656 private static final Color GRID_LINE_COLOR = Color.rgb(30, 30, 30); 657 private static final double GRID_LINE_DASH = 3; 658 659 static void createRow(int rowIndex, int columnIndex, Node... nodes) { 660 for (int i = 0; i < nodes.length; i++) { 661 setConstraints(nodes[i], columnIndex + i, rowIndex); 662 } 663 } 664 665 static void createColumn(int columnIndex, int rowIndex, Node... nodes) { 666 for (int i = 0; i < nodes.length; i++) { 667 setConstraints(nodes[i], columnIndex, rowIndex + i); 668 } 669 } 670 671 static int getNodeRowIndex(Node node) { 672 Integer rowIndex = getRowIndex(node); 673 return rowIndex != null? rowIndex : 0; 674 } 675 676 private static int getNodeRowSpan(Node node) { 677 Integer rowspan = getRowSpan(node); 678 return rowspan != null? rowspan : 1; 679 } 680 681 static int getNodeRowEnd(Node node) { 682 int rowSpan = getNodeRowSpan(node); 683 return rowSpan != REMAINING? getNodeRowIndex(node) + rowSpan - 1 : REMAINING; 684 } 685 686 static int getNodeColumnIndex(Node node) { 687 Integer columnIndex = getColumnIndex(node); 688 return columnIndex != null? columnIndex : 0; 689 } 690 691 private static int getNodeColumnSpan(Node node) { 692 Integer colspan = getColumnSpan(node); 693 return colspan != null? colspan : 1; 694 } 695 696 static int getNodeColumnEnd(Node node) { 697 int columnSpan = getNodeColumnSpan(node); 698 return columnSpan != REMAINING? getNodeColumnIndex(node) + columnSpan - 1 : REMAINING; 699 } 700 701 private static Priority getNodeHgrow(Node node) { 702 Priority hgrow = getHgrow(node); 703 return hgrow != null? hgrow : Priority.NEVER; 704 } 705 706 private static Priority getNodeVgrow(Node node) { 707 Priority vgrow = getVgrow(node); 708 return vgrow != null? vgrow : Priority.NEVER; 709 } 710 711 private static Priority[] createPriorityArray(int length, Priority value) { 712 Priority[] array = new Priority[length]; 713 Arrays.fill(array, value); 714 return array; 715 } 716 717 /******************************************************************** 718 * END static methods 719 ********************************************************************/ 720 721 /** 722 * Creates a GridPane layout with hgap/vgap = 0 and TOP_LEFT alignment. 723 */ 724 public GridPane() { 725 super(); 726 getChildren().addListener((Observable o) -> requestLayout()); 727 } 728 729 /** 730 * The width of the horizontal gaps between columns. 731 */ 732 public final DoubleProperty hgapProperty() { 733 if (hgap == null) { 734 hgap = new StyleableDoubleProperty(0) { 735 @Override 736 public void invalidated() { 737 requestLayout(); 738 } 739 740 @Override 741 public CssMetaData<GridPane, Number> getCssMetaData() { 742 return StyleableProperties.HGAP; 743 } 744 745 @Override 746 public Object getBean() { 747 return GridPane.this; 748 } 749 750 @Override 751 public String getName() { 752 return "hgap"; 753 } 754 }; 755 } 756 return hgap; 757 } 758 759 private DoubleProperty hgap; 760 public final void setHgap(double value) { hgapProperty().set(value); } 761 public final double getHgap() { return hgap == null ? 0 : hgap.get(); } 762 763 /** 764 * The height of the vertical gaps between rows. 765 */ 766 public final DoubleProperty vgapProperty() { 767 if (vgap == null) { 768 vgap = new StyleableDoubleProperty(0) { 769 @Override 770 public void invalidated() { 771 requestLayout(); 772 } 773 774 @Override 775 public CssMetaData<GridPane, Number> getCssMetaData() { 776 return StyleableProperties.VGAP; 777 } 778 779 @Override 780 public Object getBean() { 781 return GridPane.this; 782 } 783 784 @Override 785 public String getName() { 786 return "vgap"; 787 } 788 }; 789 } 790 return vgap; 791 } 792 793 private DoubleProperty vgap; 794 public final void setVgap(double value) { vgapProperty().set(value); } 795 public final double getVgap() { return vgap == null ? 0 : vgap.get(); } 796 797 /** 798 * The alignment of of the grid within the gridpane's width and height. 799 */ 800 public final ObjectProperty<Pos> alignmentProperty() { 801 if (alignment == null) { 802 alignment = new StyleableObjectProperty<Pos>(Pos.TOP_LEFT) { 803 @Override 804 public void invalidated() { 805 requestLayout(); 806 } 807 808 @Override 809 public CssMetaData<GridPane, Pos> getCssMetaData() { 810 return StyleableProperties.ALIGNMENT; 811 } 812 813 @Override 814 public Object getBean() { 815 return GridPane.this; 816 } 817 818 @Override 819 public String getName() { 820 return "alignment"; 821 } 822 }; 823 } 824 return alignment; 825 } 826 827 private ObjectProperty<Pos> alignment; 828 public final void setAlignment(Pos value) { 829 alignmentProperty().set(value); 830 } 831 public final Pos getAlignment() { 832 return alignment == null ? Pos.TOP_LEFT : alignment.get(); 833 } 834 private Pos getAlignmentInternal() { 835 Pos localPos = getAlignment(); 836 return localPos == null ? Pos.TOP_LEFT : localPos; 837 } 838 839 /** 840 * For debug purposes only: controls whether lines are displayed to show the gridpane's rows and columns. 841 * Default is <code>false</code>. 842 */ 843 public final BooleanProperty gridLinesVisibleProperty() { 844 if (gridLinesVisible == null) { 845 gridLinesVisible = new StyleableBooleanProperty() { 846 @Override 847 protected void invalidated() { 848 if (get()) { 849 gridLines = new Group(); 850 gridLines.setManaged(false); 851 getChildren().add(gridLines); 852 } else { 853 getChildren().remove(gridLines); 854 gridLines = null; 855 } 856 requestLayout(); 857 } 858 859 @Override 860 public CssMetaData<GridPane, Boolean> getCssMetaData() { 861 return StyleableProperties.GRID_LINES_VISIBLE; 862 } 863 864 @Override 865 public Object getBean() { 866 return GridPane.this; 867 } 868 869 @Override 870 public String getName() { 871 return "gridLinesVisible"; 872 } 873 }; 874 } 875 return gridLinesVisible; 876 } 877 878 private BooleanProperty gridLinesVisible; 879 public final void setGridLinesVisible(boolean value) { gridLinesVisibleProperty().set(value); } 880 public final boolean isGridLinesVisible() { return gridLinesVisible == null ? false : gridLinesVisible.get(); } 881 882 /** 883 * RowConstraints instances can be added to explicitly control individual row 884 * sizing and layout behavior. 885 * If not set, row sizing and layout behavior will be computed based on content. 886 * 887 */ 888 private final ObservableList<RowConstraints> rowConstraints = new TrackableObservableList<RowConstraints>() { 889 @Override 890 protected void onChanged(Change<RowConstraints> c) { 891 while (c.next()) { 892 for (RowConstraints constraints : c.getRemoved()) { 893 if (constraints != null && !rowConstraints.contains(constraints)) { 894 constraints.remove(GridPane.this); 895 } 896 } 897 for (RowConstraints constraints : c.getAddedSubList()) { 898 if (constraints != null) { 899 constraints.add(GridPane.this); 900 } 901 } 902 } 903 requestLayout(); 904 } 905 }; 906 907 /** 908 * Returns list of row constraints. Row constraints can be added to 909 * explicitly control individual row sizing and layout behavior. 910 * If not set, row sizing and layout behavior is computed based on content. 911 * 912 * Index in the ObservableList denotes the row number, so the row constraint for the first row 913 * is at the position of 0. 914 */ 915 public final ObservableList<RowConstraints> getRowConstraints() { return rowConstraints; } 916 /** 917 * ColumnConstraints instances can be added to explicitly control individual column 918 * sizing and layout behavior. 919 * If not set, column sizing and layout behavior will be computed based on content. 920 */ 921 private final ObservableList<ColumnConstraints> columnConstraints = new TrackableObservableList<ColumnConstraints>() { 922 @Override 923 protected void onChanged(Change<ColumnConstraints> c) { 924 while(c.next()) { 925 for (ColumnConstraints constraints : c.getRemoved()) { 926 if (constraints != null && !columnConstraints.contains(constraints)) { 927 constraints.remove(GridPane.this); 928 } 929 } 930 for (ColumnConstraints constraints : c.getAddedSubList()) { 931 if (constraints != null) { 932 constraints.add(GridPane.this); 933 } 934 } 935 } 936 requestLayout(); 937 } 938 }; 939 940 /** 941 * Returns list of column constraints. Column constraints can be added to 942 * explicitly control individual column sizing and layout behavior. 943 * If not set, column sizing and layout behavior is computed based on content. 944 * 945 * Index in the ObservableList denotes the column number, so the column constraint for the first column 946 * is at the position of 0. 947 */ 948 public final ObservableList<ColumnConstraints> getColumnConstraints() { return columnConstraints; } 949 950 /** 951 * Adds a child to the gridpane at the specified column,row position. 952 * This convenience method will set the gridpane column and row constraints 953 * on the child. 954 * @param child the node being added to the gridpane 955 * @param columnIndex the column index position for the child within the gridpane, counting from 0 956 * @param rowIndex the row index position for the child within the gridpane, counting from 0 957 */ 958 public void add(Node child, int columnIndex, int rowIndex) { 959 setConstraints(child, columnIndex, rowIndex); 960 getChildren().add(child); 961 } 962 963 /** 964 * Adds a child to the gridpane at the specified column,row position and spans. 965 * This convenience method will set the gridpane column, row, and span constraints 966 * on the child. 967 * @param child the node being added to the gridpane 968 * @param columnIndex the column index position for the child within the gridpane, counting from 0 969 * @param rowIndex the row index position for the child within the gridpane, counting from 0 970 * @param colspan the number of columns the child's layout area should span 971 * @param rowspan the number of rows the child's layout area should span 972 */ 973 public void add(Node child, int columnIndex, int rowIndex, int colspan, int rowspan) { 974 setConstraints(child, columnIndex, rowIndex, colspan, rowspan); 975 getChildren().add(child); 976 } 977 978 /** 979 * Convenience method for placing the specified nodes sequentially in a given 980 * row of the gridpane. If the row already contains nodes the specified nodes 981 * will be appended to the row. For example, the first node will be positioned at [column,row], 982 * the second at [column+1,row], etc. This method will set the appropriate gridpane 983 * row/column constraints on the nodes as well as add the nodes to the gridpane's 984 * children sequence. 985 * 986 * @param rowIndex the row index position for the children within the gridpane 987 * @param children the nodes to be added as a row in the gridpane 988 */ 989 public void addRow(int rowIndex, Node... children) { 990 int columnIndex = 0; 991 final List<Node> managed = getManagedChildren(); 992 for (int i = 0, size = managed.size(); i < size; i++) { 993 Node child = managed.get(i); 994 final int nodeRowIndex = getNodeRowIndex(child); 995 final int nodeRowEnd = getNodeRowEnd(child); 996 if (rowIndex >= nodeRowIndex && 997 (rowIndex <= nodeRowEnd || nodeRowEnd == REMAINING)) { 998 int index = getNodeColumnIndex(child); 999 int end = getNodeColumnEnd(child); 1000 columnIndex = Math.max(columnIndex, (end != REMAINING? end : index) + 1); 1001 } 1002 } 1003 createRow(rowIndex, columnIndex, children); 1004 getChildren().addAll(children); 1005 } 1006 1007 /** 1008 * Convenience method for placing the specified nodes sequentially in a given 1009 * column of the gridpane. If the column already contains nodes the specified nodes 1010 * will be appended to the column. For example, the first node will be positioned at [column, row], 1011 * the second at [column, row+1], etc. This method will set the appropriate gridpane 1012 * row/column constraints on the nodes as well as add the nodes to the gridpane's 1013 * children sequence. 1014 * 1015 * @param columnIndex the column index position for the children within the gridpane 1016 * @param children the nodes to be added as a column in the gridpane 1017 */ 1018 public void addColumn(int columnIndex, Node... children) { 1019 int rowIndex = 0; 1020 final List<Node> managed = getManagedChildren(); 1021 for (int i = 0, size = managed.size(); i < size; i++) { 1022 Node child = managed.get(i); 1023 final int nodeColumnIndex = getNodeColumnIndex(child); 1024 final int nodeColumnEnd = getNodeColumnEnd(child); 1025 if (columnIndex >= nodeColumnIndex 1026 && (columnIndex <= nodeColumnEnd || nodeColumnEnd == REMAINING)) { 1027 int index = getNodeRowIndex(child); 1028 int end = getNodeRowEnd(child); 1029 rowIndex = Math.max(rowIndex, (end != REMAINING? end : index) + 1); 1030 } 1031 } 1032 createColumn(columnIndex, rowIndex, children); 1033 getChildren().addAll(children); 1034 } 1035 1036 private Group gridLines; 1037 private Orientation bias; 1038 1039 private double[] rowPercentHeight; 1040 private double rowPercentTotal = 0; 1041 1042 private CompositeSize rowMinHeight; 1043 private CompositeSize rowPrefHeight; 1044 private CompositeSize rowMaxHeight; 1045 private List<Node>[] rowBaseline; 1046 private double[] rowMinBaselineComplement; 1047 private double[] rowPrefBaselineComplement; 1048 private double[] rowMaxBaselineComplement; 1049 private Priority[] rowGrow; 1050 1051 private double[] columnPercentWidth; 1052 private double columnPercentTotal = 0; 1053 1054 private CompositeSize columnMinWidth; 1055 private CompositeSize columnPrefWidth; 1056 private CompositeSize columnMaxWidth; 1057 private Priority[] columnGrow; 1058 1059 private boolean metricsDirty = true; 1060 1061 // This is set to true while in layoutChildren and set false on the conclusion. 1062 // It is used to decide whether to update metricsDirty in requestLayout(). 1063 private boolean performingLayout = false; 1064 1065 private int numRows; 1066 private int numColumns; 1067 1068 private int getNumberOfRows() { 1069 computeGridMetrics(); 1070 return numRows; 1071 } 1072 1073 private int getNumberOfColumns() { 1074 computeGridMetrics(); 1075 return numColumns; 1076 } 1077 1078 private boolean isNodePositionedByBaseline(Node n){ 1079 return (getRowValignment(getNodeRowIndex(n)) == VPos.BASELINE && getValignment(n) == null) 1080 || getValignment(n) == VPos.BASELINE; 1081 } 1082 1083 private void computeGridMetrics() { 1084 if (metricsDirty) { 1085 numRows = rowConstraints.size(); 1086 numColumns = columnConstraints.size(); 1087 final List<Node> managed = getManagedChildren(); 1088 for (int i = 0, size = managed.size(); i < size; i++) { 1089 Node child = managed.get(i); 1090 int rowIndex = getNodeRowIndex(child); 1091 int columnIndex = getNodeColumnIndex(child); 1092 int rowEnd = getNodeRowEnd(child); 1093 int columnEnd = getNodeColumnEnd(child); 1094 numRows = Math.max(numRows, (rowEnd != REMAINING ? rowEnd : rowIndex) + 1); 1095 numColumns = Math.max(numColumns, (columnEnd != REMAINING ? columnEnd : columnIndex) + 1); 1096 } 1097 rowPercentHeight = createDoubleArray(numRows, -1); 1098 rowPercentTotal = 0; 1099 columnPercentWidth = createDoubleArray(numColumns, -1); 1100 columnPercentTotal = 0; 1101 columnGrow = createPriorityArray(numColumns, Priority.NEVER); 1102 rowGrow = createPriorityArray(numRows, Priority.NEVER); 1103 rowMinBaselineComplement = createDoubleArray(numRows, -1); 1104 rowPrefBaselineComplement = createDoubleArray(numRows, -1); 1105 rowMaxBaselineComplement = createDoubleArray(numRows, -1); 1106 rowBaseline = new List[numRows]; 1107 for (int i = 0, sz = numRows; i < sz; ++i) { 1108 if (i < rowConstraints.size()) { 1109 final RowConstraints rc = rowConstraints.get(i); 1110 double percentHeight = rc.getPercentHeight(); 1111 Priority vGrow = rc.getVgrow(); 1112 if (percentHeight >= 0) { 1113 rowPercentHeight[i] = percentHeight; 1114 } 1115 if (vGrow != null) { 1116 rowGrow[i] = vGrow; 1117 } 1118 } 1119 1120 List<Node> baselineNodes = new ArrayList<>(numColumns); 1121 for (int j = 0, size = managed.size(); j < size; j++) { 1122 Node n = managed.get(j); 1123 if (getNodeRowIndex(n) == i && isNodePositionedByBaseline(n)) { 1124 baselineNodes.add(n); 1125 } 1126 } 1127 rowMinBaselineComplement[i] = getMinBaselineComplement(baselineNodes); 1128 rowPrefBaselineComplement[i] = getPrefBaselineComplement(baselineNodes); 1129 rowMaxBaselineComplement[i] = getMaxBaselineComplement(baselineNodes); 1130 rowBaseline[i] = baselineNodes; 1131 1132 } 1133 for (int i = 0, sz = Math.min(numColumns, columnConstraints.size()); i < sz; ++i) { 1134 final ColumnConstraints cc = columnConstraints.get(i); 1135 double percentWidth = cc.getPercentWidth(); 1136 Priority hGrow = cc.getHgrow(); 1137 if (percentWidth >= 0) 1138 columnPercentWidth[i] = percentWidth; 1139 if (hGrow != null) 1140 columnGrow[i] = hGrow; 1141 } 1142 1143 for (int i = 0, size = managed.size(); i < size; i++) { 1144 Node child = managed.get(i); 1145 if (getNodeColumnSpan(child) == 1) { 1146 Priority hg = getNodeHgrow(child); 1147 int idx = getNodeColumnIndex(child); 1148 columnGrow[idx] = Priority.max(columnGrow[idx], hg); 1149 } 1150 if (getNodeRowSpan(child) == 1) { 1151 Priority vg = getNodeVgrow(child); 1152 int idx = getNodeRowIndex(child); 1153 rowGrow[idx] = Priority.max(rowGrow[idx], vg); 1154 } 1155 } 1156 1157 for (int i = 0; i < rowPercentHeight.length; i++) { 1158 if (rowPercentHeight[i] > 0) { 1159 rowPercentTotal += rowPercentHeight[i]; 1160 } 1161 } 1162 if (rowPercentTotal > 100) { 1163 double weight = 100 / rowPercentTotal; 1164 for (int i = 0; i < rowPercentHeight.length; i++) { 1165 if (rowPercentHeight[i] > 0) { 1166 rowPercentHeight[i] *= weight; 1167 } 1168 } 1169 rowPercentTotal = 100; 1170 } 1171 for (int i = 0; i < columnPercentWidth.length; i++) { 1172 if (columnPercentWidth[i] > 0) { 1173 columnPercentTotal += columnPercentWidth[i]; 1174 } 1175 } 1176 if (columnPercentTotal > 100) { 1177 double weight = 100 / columnPercentTotal; 1178 for (int i = 0; i < columnPercentWidth.length; i++) { 1179 if (columnPercentWidth[i] > 0) { 1180 columnPercentWidth[i] *= weight; 1181 } 1182 } 1183 columnPercentTotal = 100; 1184 } 1185 1186 bias = null; 1187 for (int i = 0; i < managed.size(); ++i) { 1188 final Orientation b = managed.get(i).getContentBias(); 1189 if (b != null) { 1190 bias = b; 1191 if (b == Orientation.HORIZONTAL) { 1192 break; 1193 } 1194 } 1195 } 1196 1197 metricsDirty = false; 1198 } 1199 } 1200 1201 @Override protected double computeMinWidth(double height) { 1202 computeGridMetrics(); 1203 performingLayout = true; 1204 try { 1205 final double[] heights = height == -1 ? null : computeHeightsToFit(height).asArray(); 1206 1207 return snapSpace(getInsets().getLeft()) + 1208 computeMinWidths(heights).computeTotalWithMultiSize() + 1209 snapSpace(getInsets().getRight()); 1210 } finally { 1211 performingLayout = false; 1212 } 1213 1214 } 1215 1216 @Override protected double computeMinHeight(double width) { 1217 computeGridMetrics(); 1218 performingLayout = true; 1219 try { 1220 final double[] widths = width == -1 ? null : computeWidthsToFit(width).asArray(); 1221 1222 return snapSpace(getInsets().getTop()) + 1223 computeMinHeights(widths).computeTotalWithMultiSize() + 1224 snapSpace(getInsets().getBottom()); 1225 } finally { 1226 performingLayout = false; 1227 } 1228 } 1229 1230 @Override protected double computePrefWidth(double height) { 1231 computeGridMetrics(); 1232 performingLayout = true; 1233 try { 1234 final double[] heights = height == -1 ? null : computeHeightsToFit(height).asArray(); 1235 1236 return snapSpace(getInsets().getLeft()) + 1237 computePrefWidths(heights).computeTotalWithMultiSize() + 1238 snapSpace(getInsets().getRight()); 1239 } finally { 1240 performingLayout = false; 1241 } 1242 } 1243 1244 @Override protected double computePrefHeight(double width) { 1245 computeGridMetrics(); 1246 performingLayout = true; 1247 try { 1248 final double[] widths = width == -1 ? null : computeWidthsToFit(width).asArray(); 1249 1250 return snapSpace(getInsets().getTop()) + 1251 computePrefHeights(widths).computeTotalWithMultiSize() + 1252 snapSpace(getInsets().getBottom()); 1253 } finally { 1254 performingLayout = false; 1255 } 1256 } 1257 1258 private VPos getRowValignment(int rowIndex) { 1259 if (rowIndex < getRowConstraints().size()) { 1260 RowConstraints constraints = getRowConstraints().get(rowIndex); 1261 if (constraints.getValignment() != null) { 1262 return constraints.getValignment(); 1263 } 1264 } 1265 return VPos.CENTER; 1266 } 1267 1268 private HPos getColumnHalignment(int columnIndex) { 1269 if (columnIndex < getColumnConstraints().size()) { 1270 ColumnConstraints constraints = getColumnConstraints().get(columnIndex); 1271 if (constraints.getHalignment() != null) { 1272 return constraints.getHalignment(); 1273 } 1274 } 1275 return HPos.LEFT; 1276 } 1277 1278 private double getColumnMinWidth(int columnIndex) { 1279 if (columnIndex < getColumnConstraints().size()) { 1280 ColumnConstraints constraints = getColumnConstraints().get(columnIndex); 1281 return constraints.getMinWidth(); 1282 1283 } 1284 return USE_COMPUTED_SIZE; 1285 } 1286 1287 private double getRowMinHeight(int rowIndex) { 1288 if (rowIndex < getRowConstraints().size()) { 1289 RowConstraints constraints = getRowConstraints().get(rowIndex); 1290 return constraints.getMinHeight(); 1291 } 1292 return USE_COMPUTED_SIZE; 1293 } 1294 1295 private double getColumnMaxWidth(int columnIndex) { 1296 if (columnIndex < getColumnConstraints().size()) { 1297 ColumnConstraints constraints = getColumnConstraints().get(columnIndex); 1298 return constraints.getMaxWidth(); 1299 1300 } 1301 return USE_COMPUTED_SIZE; 1302 } 1303 1304 private double getColumnPrefWidth(int columnIndex) { 1305 if (columnIndex < getColumnConstraints().size()) { 1306 ColumnConstraints constraints = getColumnConstraints().get(columnIndex); 1307 return constraints.getPrefWidth(); 1308 1309 } 1310 return USE_COMPUTED_SIZE; 1311 } 1312 1313 private double getRowPrefHeight(int rowIndex) { 1314 if (rowIndex < getRowConstraints().size()) { 1315 RowConstraints constraints = getRowConstraints().get(rowIndex); 1316 return constraints.getPrefHeight(); 1317 1318 } 1319 return USE_COMPUTED_SIZE; 1320 } 1321 1322 private double getRowMaxHeight(int rowIndex) { 1323 if (rowIndex < getRowConstraints().size()) { 1324 RowConstraints constraints = getRowConstraints().get(rowIndex); 1325 return constraints.getMaxHeight(); 1326 } 1327 return USE_COMPUTED_SIZE; 1328 } 1329 1330 private boolean shouldRowFillHeight(int rowIndex) { 1331 if (rowIndex < getRowConstraints().size()) { 1332 return getRowConstraints().get(rowIndex).isFillHeight(); 1333 } 1334 return true; 1335 } 1336 1337 private boolean shouldColumnFillWidth(int columnIndex) { 1338 if (columnIndex < getColumnConstraints().size()) { 1339 return getColumnConstraints().get(columnIndex).isFillWidth(); 1340 } 1341 return true; 1342 } 1343 1344 private double getTotalWidthOfNodeColumns(Node child, double[] widths) { 1345 if (getNodeColumnSpan(child) == 1) { 1346 return widths[getNodeColumnIndex(child)]; 1347 } else { 1348 double total = 0; 1349 for (int i = getNodeColumnIndex(child), last = getNodeColumnEndConvertRemaining(child); i <= last; ++i) { 1350 total += widths[i]; 1351 } 1352 return total; 1353 } 1354 } 1355 1356 private CompositeSize computeMaxHeights() { 1357 if (rowMaxHeight == null) { 1358 rowMaxHeight = createCompositeRows(Double.MAX_VALUE); // Do not restrict the row (to allow grow). The 1359 // Nodes will be restricted to their computed size 1360 // in Region.layoutInArea call 1361 final ObservableList<RowConstraints> rowConstr = getRowConstraints(); 1362 CompositeSize prefHeights = null; 1363 for (int i = 0; i < rowConstr.size(); ++i) { 1364 final RowConstraints curConstraint = rowConstr.get(i); 1365 double maxRowHeight = snapSize(curConstraint.getMaxHeight()); 1366 if (maxRowHeight == USE_PREF_SIZE) { 1367 if (prefHeights == null) { 1368 prefHeights = computePrefHeights(null); 1369 } 1370 rowMaxHeight.setPresetSize(i, prefHeights.getSize(i)); 1371 } else if (maxRowHeight != USE_COMPUTED_SIZE) { 1372 final double min = snapSize(curConstraint.getMinHeight()); 1373 if (min >= 0 ) { 1374 rowMaxHeight.setPresetSize(i, boundedSize(min, maxRowHeight, maxRowHeight)); 1375 } else { 1376 rowMaxHeight.setPresetSize(i, maxRowHeight); 1377 } 1378 } 1379 } 1380 } 1381 return rowMaxHeight; 1382 } 1383 1384 private CompositeSize computePrefHeights(double[] widths) { 1385 CompositeSize result; 1386 if (widths == null) { 1387 if (rowPrefHeight != null) { 1388 return rowPrefHeight; 1389 } 1390 rowPrefHeight = createCompositeRows(0); 1391 result = rowPrefHeight; 1392 } else { 1393 result = createCompositeRows(0); 1394 } 1395 1396 final ObservableList<RowConstraints> rowConstr = getRowConstraints(); 1397 for (int i = 0; i < rowConstr.size(); ++i) { 1398 final RowConstraints curConstraint = rowConstr.get(i); 1399 double prefRowHeight = snapSize(curConstraint.getPrefHeight()); 1400 final double min = snapSize(curConstraint.getMinHeight()); 1401 if (prefRowHeight != USE_COMPUTED_SIZE) { 1402 final double max = snapSize(curConstraint.getMaxHeight()); 1403 if (min >= 0 || max >= 0) { 1404 result.setPresetSize(i, boundedSize(min < 0 ? 0 : min, 1405 prefRowHeight, 1406 max < 0 ? Double.POSITIVE_INFINITY : max)); 1407 } else { 1408 result.setPresetSize(i, prefRowHeight); 1409 } 1410 } else if (min > 0){ 1411 result.setSize(i, min); 1412 } 1413 } 1414 List<Node> managed = getManagedChildren(); 1415 for (int i = 0, size = managed.size(); i < size; i++) { 1416 Node child = managed.get(i); 1417 int start = getNodeRowIndex(child); 1418 int end = getNodeRowEndConvertRemaining(child); 1419 double childPrefAreaHeight = computeChildPrefAreaHeight(child, isNodePositionedByBaseline(child) ? rowPrefBaselineComplement[start] : -1, getMargin(child), 1420 widths == null ? -1 : getTotalWidthOfNodeColumns(child, widths)); 1421 if (start == end && !result.isPreset(start)) { 1422 double min = getRowMinHeight(start); 1423 double max = getRowMaxHeight(start); 1424 result.setMaxSize(start, boundedSize(min < 0 ? 0 : min, childPrefAreaHeight, max < 0 ? Double.MAX_VALUE : max)); 1425 } else if (start != end){ 1426 result.setMaxMultiSize(start, end + 1, childPrefAreaHeight); 1427 } 1428 } 1429 return result; 1430 } 1431 1432 private CompositeSize computeMinHeights(double[] widths) { 1433 CompositeSize result; 1434 if (widths == null) { 1435 if (rowMinHeight != null) { 1436 return rowMinHeight; 1437 } 1438 rowMinHeight = createCompositeRows(0); 1439 result = rowMinHeight; 1440 } else { 1441 result = createCompositeRows(0); 1442 } 1443 1444 final ObservableList<RowConstraints> rowConstr = getRowConstraints(); 1445 CompositeSize prefHeights = null; 1446 for (int i = 0; i < rowConstr.size(); ++i) { 1447 double minRowHeight = snapSize(rowConstr.get(i).getMinHeight()); 1448 if (minRowHeight == USE_PREF_SIZE) { 1449 if (prefHeights == null) { 1450 prefHeights = computePrefHeights(widths); 1451 } 1452 result.setPresetSize(i, prefHeights.getSize(i)); 1453 } else if (minRowHeight != USE_COMPUTED_SIZE) { 1454 result.setPresetSize(i, minRowHeight); 1455 } 1456 } 1457 List<Node> managed = getManagedChildren(); 1458 for (int i = 0, size = managed.size(); i < size; i++) { 1459 Node child = managed.get(i); 1460 int start = getNodeRowIndex(child); 1461 int end = getNodeRowEndConvertRemaining(child); 1462 double childMinAreaHeight = computeChildMinAreaHeight(child, isNodePositionedByBaseline(child) ? rowMinBaselineComplement[start] : -1, getMargin(child), 1463 widths == null ? -1 : getTotalWidthOfNodeColumns(child, widths)); 1464 if (start == end && !result.isPreset(start)) { 1465 result.setMaxSize(start, childMinAreaHeight); 1466 } else if (start != end){ 1467 result.setMaxMultiSize(start, end + 1, childMinAreaHeight); 1468 } 1469 } 1470 return result; 1471 } 1472 1473 private double getTotalHeightOfNodeRows(Node child, double[] heights) { 1474 if (getNodeRowSpan(child) == 1) { 1475 return heights[getNodeRowIndex(child)]; 1476 } else { 1477 double total = 0; 1478 for (int i = getNodeRowIndex(child), last = getNodeRowEndConvertRemaining(child); i <= last; ++i) { 1479 total += heights[i]; 1480 } 1481 return total; 1482 } 1483 } 1484 1485 private CompositeSize computeMaxWidths() { 1486 if (columnMaxWidth == null) { 1487 columnMaxWidth = createCompositeColumns(Double.MAX_VALUE);// Do not restrict the column (to allow grow). The 1488 // Nodes will be restricted to their computed size 1489 // in Region.layoutInArea call 1490 final ObservableList<ColumnConstraints> columnConstr = getColumnConstraints(); 1491 CompositeSize prefWidths = null; 1492 for (int i = 0; i < columnConstr.size(); ++i) { 1493 final ColumnConstraints curConstraint = columnConstr.get(i); 1494 double maxColumnWidth = snapSize(curConstraint.getMaxWidth()); 1495 if (maxColumnWidth == USE_PREF_SIZE) { 1496 if (prefWidths == null) { 1497 prefWidths = computePrefWidths(null); 1498 } 1499 columnMaxWidth.setPresetSize(i, prefWidths.getSize(i)); 1500 } else if (maxColumnWidth != USE_COMPUTED_SIZE) { 1501 final double min = snapSize(curConstraint.getMinWidth()); 1502 if (min >= 0) { 1503 columnMaxWidth.setPresetSize(i, boundedSize(min, maxColumnWidth, maxColumnWidth)); 1504 } else { 1505 columnMaxWidth.setPresetSize(i, maxColumnWidth); 1506 } 1507 } 1508 } 1509 } 1510 return columnMaxWidth; 1511 } 1512 1513 private CompositeSize computePrefWidths(double[] heights) { 1514 CompositeSize result; 1515 if (heights == null) { 1516 if (columnPrefWidth != null) { 1517 return columnPrefWidth; 1518 } 1519 columnPrefWidth = createCompositeColumns(0); 1520 result = columnPrefWidth; 1521 } else { 1522 result = createCompositeColumns(0); 1523 } 1524 1525 final ObservableList<ColumnConstraints> columnConstr = getColumnConstraints(); 1526 for (int i = 0; i < columnConstr.size(); ++i) { 1527 final ColumnConstraints curConstraint = columnConstr.get(i); 1528 double prefColumnWidth = snapSize(curConstraint.getPrefWidth()); 1529 final double min = snapSize(curConstraint.getMinWidth()); 1530 if (prefColumnWidth != USE_COMPUTED_SIZE) { 1531 final double max = snapSize(curConstraint.getMaxWidth()); 1532 if (min >= 0 || max >= 0) { 1533 result.setPresetSize(i, boundedSize(min < 0 ? 0 : min, 1534 prefColumnWidth, 1535 max < 0 ? Double.POSITIVE_INFINITY : max)); 1536 } else { 1537 result.setPresetSize(i, prefColumnWidth); 1538 } 1539 } else if (min > 0){ 1540 result.setSize(i, min); 1541 } 1542 } 1543 List<Node> managed = getManagedChildren(); 1544 for (int i = 0, size = managed.size(); i < size; i++) { 1545 Node child = managed.get(i); 1546 int start = getNodeColumnIndex(child); 1547 int end = getNodeColumnEndConvertRemaining(child); 1548 if (start == end && !result.isPreset(start)) { 1549 double min = getColumnMinWidth(start); 1550 double max = getColumnMaxWidth(start); 1551 result.setMaxSize(start, boundedSize(min < 0 ? 0 : min, computeChildPrefAreaWidth(child, 1552 getBaselineComplementForChild(child), getMargin(child), 1553 heights == null ? -1 : getTotalHeightOfNodeRows(child, heights), false), 1554 max < 0 ? Double.MAX_VALUE : max)); 1555 } else if (start != end) { 1556 result.setMaxMultiSize(start, end + 1, computeChildPrefAreaWidth(child, getBaselineComplementForChild(child), 1557 getMargin(child), 1558 heights == null ? -1 : getTotalHeightOfNodeRows(child, heights), false)); 1559 } 1560 } 1561 return result; 1562 } 1563 1564 private CompositeSize computeMinWidths(double[] heights) { 1565 CompositeSize result; 1566 if (heights == null) { 1567 if (columnMinWidth != null) { 1568 return columnMinWidth; 1569 } 1570 columnMinWidth = createCompositeColumns(0); 1571 result = columnMinWidth; 1572 } else { 1573 result = createCompositeColumns(0); 1574 } 1575 1576 final ObservableList<ColumnConstraints> columnConstr = getColumnConstraints(); 1577 CompositeSize prefWidths = null; 1578 for (int i = 0; i < columnConstr.size(); ++i) { 1579 double minColumnWidth = snapSize(columnConstr.get(i).getMinWidth()); 1580 if (minColumnWidth == USE_PREF_SIZE) { 1581 if (prefWidths == null) { 1582 prefWidths = computePrefWidths(heights); 1583 } 1584 result.setPresetSize(i, prefWidths.getSize(i)); 1585 } else if (minColumnWidth != USE_COMPUTED_SIZE) { 1586 result.setPresetSize(i, minColumnWidth); 1587 } 1588 } 1589 List<Node> managed = getManagedChildren(); 1590 for (int i = 0, size = managed.size(); i < size; i++) { 1591 Node child = managed.get(i); 1592 int start = getNodeColumnIndex(child); 1593 int end = getNodeColumnEndConvertRemaining(child); 1594 if (start == end && !result.isPreset(start)) { 1595 result.setMaxSize(start, computeChildMinAreaWidth(child, getBaselineComplementForChild(child), 1596 getMargin(child), 1597 heights == null ? -1 : getTotalHeightOfNodeRows(child, heights),false)); 1598 } else if (start != end){ 1599 result.setMaxMultiSize(start, end + 1, computeChildMinAreaWidth(child, getBaselineComplementForChild(child), 1600 getMargin(child), 1601 heights == null ? -1 : getTotalHeightOfNodeRows(child, heights), false)); 1602 } 1603 } 1604 return result; 1605 } 1606 1607 private CompositeSize computeHeightsToFit(double height) { 1608 assert(height != -1); 1609 final CompositeSize heights; 1610 if (rowPercentTotal == 100) { 1611 // all rows defined by percentage, no need to compute pref heights 1612 heights = createCompositeRows(0); 1613 } else { 1614 heights = (CompositeSize) computePrefHeights(null).clone(); 1615 } 1616 adjustRowHeights(heights, height); 1617 return heights; 1618 } 1619 1620 private CompositeSize computeWidthsToFit(double width) { 1621 assert(width != -1); 1622 final CompositeSize widths; 1623 if (columnPercentTotal == 100) { 1624 // all columns defined by percentage, no need to compute pref widths 1625 widths = createCompositeColumns(0); 1626 } else { 1627 widths = (CompositeSize) computePrefWidths(null).clone(); 1628 } 1629 adjustColumnWidths(widths, width); 1630 return widths; 1631 } 1632 1633 /** 1634 * 1635 * @return null unless one of its children has a content bias. 1636 */ 1637 @Override public Orientation getContentBias() { 1638 computeGridMetrics(); 1639 return bias; 1640 } 1641 1642 @Override public void requestLayout() { 1643 // RT-18878: Do not update metrics dirty if we are performing layout. 1644 // If metricsDirty is set true during a layout pass the next call to computeGridMetrics() 1645 // will clear all the cell bounds resulting in out of date info until the 1646 // next layout pass. 1647 if (performingLayout) { 1648 return; 1649 } else if (metricsDirty) { 1650 super.requestLayout(); 1651 return; 1652 } 1653 metricsDirty = true; 1654 bias = null; 1655 rowGrow = null; 1656 rowMinHeight = rowPrefHeight = rowMaxHeight = null; 1657 columnGrow = null; 1658 columnMinWidth = columnPrefWidth = columnMaxWidth = null; 1659 rowMinBaselineComplement = rowPrefBaselineComplement = rowMaxBaselineComplement = null; 1660 super.requestLayout(); 1661 } 1662 1663 @Override protected void layoutChildren() { 1664 performingLayout = true; 1665 try { 1666 final double snaphgap = snapSpace(getHgap()); 1667 final double snapvgap = snapSpace(getVgap()); 1668 final double top = snapSpace(getInsets().getTop()); 1669 final double bottom = snapSpace(getInsets().getBottom()); 1670 final double left = snapSpace(getInsets().getLeft()); 1671 final double right = snapSpace(getInsets().getRight()); 1672 1673 final double width = getWidth(); 1674 final double height = getHeight(); 1675 final double contentHeight = height - top - bottom; 1676 final double contentWidth = width - left - right; 1677 double columnTotal; 1678 double rowTotal; 1679 computeGridMetrics(); 1680 1681 Orientation contentBias = getContentBias(); 1682 CompositeSize heights; 1683 final CompositeSize widths; 1684 if (contentBias == null) { 1685 heights = (CompositeSize) computePrefHeights(null).clone(); 1686 widths = (CompositeSize) computePrefWidths(null).clone(); 1687 rowTotal = adjustRowHeights(heights, height); 1688 columnTotal = adjustColumnWidths(widths, width); 1689 } else if (contentBias == Orientation.HORIZONTAL) { 1690 widths = (CompositeSize) computePrefWidths(null).clone(); 1691 columnTotal = adjustColumnWidths(widths, width); 1692 heights = computePrefHeights(widths.asArray()); 1693 rowTotal = adjustRowHeights(heights, height); 1694 } else { 1695 heights = (CompositeSize) computePrefHeights(null).clone(); 1696 rowTotal = adjustRowHeights(heights, height); 1697 widths = computePrefWidths(heights.asArray()); 1698 columnTotal = adjustColumnWidths(widths, width); 1699 } 1700 1701 final double x = left + computeXOffset(contentWidth, columnTotal, getAlignmentInternal().getHpos()); 1702 final double y = top + computeYOffset(contentHeight, rowTotal, getAlignmentInternal().getVpos()); 1703 final List<Node> managed = getManagedChildren(); 1704 1705 double[] baselineOffsets = createDoubleArray(numRows, -1); 1706 1707 for (int i = 0, size = managed.size(); i < size; i++) { 1708 final Node child = managed.get(i); 1709 final int rowIndex = getNodeRowIndex(child); 1710 int columnIndex = getNodeColumnIndex(child); 1711 int colspan = getNodeColumnSpan(child); 1712 if (colspan == REMAINING) { 1713 colspan = widths.getLength() - columnIndex; 1714 } 1715 int rowspan = getNodeRowSpan(child); 1716 if (rowspan == REMAINING) { 1717 rowspan = heights.getLength() - rowIndex; 1718 } 1719 double areaX = x; 1720 for (int j = 0; j < columnIndex; j++) { 1721 areaX += widths.getSize(j) + snaphgap; 1722 } 1723 double areaY = y; 1724 for (int j = 0; j < rowIndex; j++) { 1725 areaY += heights.getSize(j) + snapvgap; 1726 } 1727 double areaW = widths.getSize(columnIndex); 1728 for (int j = 2; j <= colspan; j++) { 1729 areaW += widths.getSize(columnIndex + j - 1) + snaphgap; 1730 } 1731 double areaH = heights.getSize(rowIndex); 1732 for (int j = 2; j <= rowspan; j++) { 1733 areaH += heights.getSize(rowIndex + j - 1) + snapvgap; 1734 } 1735 1736 HPos halign = getHalignment(child); 1737 VPos valign = getValignment(child); 1738 Boolean fillWidth = isFillWidth(child); 1739 Boolean fillHeight = isFillHeight(child); 1740 1741 if (halign == null) { 1742 halign = getColumnHalignment(columnIndex); 1743 } 1744 if (valign == null) { 1745 valign = getRowValignment(rowIndex); 1746 } 1747 if (fillWidth == null) { 1748 fillWidth = shouldColumnFillWidth(columnIndex); 1749 } 1750 if (fillHeight == null) { 1751 fillHeight = shouldRowFillHeight(rowIndex); 1752 } 1753 1754 double baselineOffset = 0; 1755 if (valign == VPos.BASELINE) { 1756 if (baselineOffsets[rowIndex] == -1) { 1757 baselineOffsets[rowIndex] = getAreaBaselineOffset(rowBaseline[rowIndex], 1758 marginAccessor, 1759 t -> { 1760 Node n = rowBaseline[rowIndex].get(t); 1761 int c = getNodeColumnIndex(n); 1762 int cs = getNodeColumnSpan(n); 1763 if (cs == REMAINING) { 1764 cs = widths.getLength() - c; 1765 } 1766 double w = widths.getSize(c); 1767 for (int j = 2; j <= cs; j++) { 1768 w += widths.getSize(c + j - 1) + snaphgap; 1769 } 1770 return w; 1771 }, 1772 areaH, 1773 t -> { 1774 Boolean b = isFillHeight(child); 1775 if (b != null) { 1776 return b; 1777 } 1778 return shouldRowFillHeight(getNodeRowIndex(child)); 1779 }, rowMinBaselineComplement[rowIndex] 1780 ); 1781 } 1782 baselineOffset = baselineOffsets[rowIndex]; 1783 } 1784 1785 Insets margin = getMargin(child); 1786 layoutInArea(child, areaX, areaY, areaW, areaH, 1787 baselineOffset, 1788 margin, 1789 fillWidth, fillHeight, 1790 halign, valign); 1791 } 1792 layoutGridLines(widths, heights, x, y, rowTotal, columnTotal); 1793 currentHeights = heights; 1794 currentWidths = widths; 1795 } finally { 1796 performingLayout = false; 1797 } 1798 } 1799 1800 private double adjustRowHeights(final CompositeSize heights, double height) { 1801 assert(height != -1); 1802 final double snapvgap = snapSpace(getVgap()); 1803 final double top = snapSpace(getInsets().getTop()); 1804 final double bottom = snapSpace(getInsets().getBottom()); 1805 final double vgaps = snapvgap * (getNumberOfRows() - 1); 1806 final double contentHeight = height - top - bottom; 1807 1808 // if there are percentage rows, give them their percentages first 1809 if (rowPercentTotal > 0) { 1810 double remainder = 0; 1811 for (int i = 0; i < rowPercentHeight.length; i++) { 1812 if (rowPercentHeight[i] >= 0) { 1813 double size = (contentHeight - vgaps) * (rowPercentHeight[i]/100); 1814 double floor = Math.floor(size); 1815 remainder += size - floor; 1816 1817 // snap size to integer boundary based on the computed remainder as we loop through the rows. 1818 size = floor; 1819 if (remainder >= 0.5) { 1820 size++; 1821 remainder = (-1.0) + remainder; 1822 } 1823 heights.setSize(i, size); 1824 } 1825 } 1826 } 1827 double rowTotal = heights.computeTotal(); 1828 if (rowPercentTotal < 100) { 1829 double heightAvailable = height - top - bottom - rowTotal; 1830 // now that both fixed and percentage rows have been computed, divy up any surplus or deficit 1831 if (heightAvailable != 0) { 1832 // maybe grow or shrink row heights 1833 double remaining = growToMultiSpanPreferredHeights(heights, heightAvailable); 1834 remaining = growOrShrinkRowHeights(heights, Priority.ALWAYS, remaining); 1835 remaining = growOrShrinkRowHeights(heights, Priority.SOMETIMES, remaining); 1836 rowTotal += (heightAvailable - remaining); 1837 } 1838 } 1839 1840 return rowTotal; 1841 } 1842 1843 private double growToMultiSpanPreferredHeights(CompositeSize heights, double extraHeight) { 1844 if (extraHeight <= 0) { 1845 return extraHeight; 1846 } 1847 1848 Set<Integer> rowsAlways = new TreeSet<>(); 1849 Set<Integer> rowsSometimes = new TreeSet<>(); 1850 Set<Integer> lastRows = new TreeSet<>(); 1851 for (Entry<Interval, Double> ms : heights.multiSizes()) { 1852 final Interval interval = ms.getKey(); 1853 for (int i = interval.begin; i < interval.end; ++i) { 1854 if (rowPercentHeight[i] < 0) { 1855 switch (rowGrow[i]) { 1856 case ALWAYS: 1857 rowsAlways.add(i); 1858 break; 1859 case SOMETIMES: 1860 rowsSometimes.add(i); 1861 break; 1862 } 1863 } 1864 } 1865 if (rowPercentHeight[interval.end - 1] < 0) { 1866 lastRows.add(interval.end - 1); 1867 } 1868 } 1869 1870 double remaining = extraHeight; 1871 1872 while (rowsAlways.size() > 0 && remaining > rowsAlways.size()) { 1873 double rowPortion = Math.floor(remaining / rowsAlways.size()); 1874 for (Iterator<Integer> it = rowsAlways.iterator(); it.hasNext();) { 1875 int i = it.next(); 1876 double maxOfRow = getRowMaxHeight(i); 1877 double prefOfRow = getRowPrefHeight(i); 1878 double actualPortion = rowPortion; 1879 1880 for (Entry<Interval, Double> ms : heights.multiSizes()) { 1881 final Interval interval = ms.getKey(); 1882 if (interval.contains(i)) { 1883 int intervalRows = 0; 1884 for (int j = interval.begin; j < interval.end; ++j) { 1885 if (rowsAlways.contains(j)) { 1886 intervalRows++; 1887 } 1888 } 1889 double curLength = heights.computeTotal(interval.begin, interval.end); 1890 actualPortion = Math.min(Math.floor(Math.max(0, (ms.getValue() - curLength) / intervalRows)), 1891 actualPortion); 1892 } 1893 } 1894 1895 final double current = heights.getSize(i); 1896 double bounded = maxOfRow >= 0 ? boundedSize(0, current + actualPortion, maxOfRow) : 1897 maxOfRow == USE_PREF_SIZE && prefOfRow > 0 ? boundedSize(0, current + actualPortion, prefOfRow) : 1898 current + actualPortion; 1899 final double portionUsed = bounded - current; 1900 remaining -= portionUsed; 1901 if (portionUsed != actualPortion || portionUsed == 0) { 1902 it.remove(); 1903 } 1904 heights.setSize(i, bounded); 1905 } 1906 } 1907 1908 while (rowsSometimes.size() > 0 && remaining > rowsSometimes.size()) { 1909 double colPortion = Math.floor(remaining / rowsSometimes.size()); 1910 for (Iterator<Integer> it = rowsSometimes.iterator(); it.hasNext();) { 1911 int i = it.next(); 1912 double maxOfRow = getRowMaxHeight(i); 1913 double prefOfRow = getRowPrefHeight(i); 1914 double actualPortion = colPortion; 1915 1916 for (Entry<Interval, Double> ms : heights.multiSizes()) { 1917 final Interval interval = ms.getKey(); 1918 if (interval.contains(i)) { 1919 int intervalRows = 0; 1920 for (int j = interval.begin; j < interval.end; ++j) { 1921 if (rowsSometimes.contains(j)) { 1922 intervalRows++; 1923 } 1924 } 1925 double curLength = heights.computeTotal(interval.begin, interval.end); 1926 actualPortion = Math.min(Math.floor(Math.max(0, (ms.getValue() - curLength) / intervalRows)), 1927 actualPortion); 1928 } 1929 } 1930 1931 final double current = heights.getSize(i); 1932 double bounded = maxOfRow >= 0 ? boundedSize(0, current + actualPortion, maxOfRow) : 1933 maxOfRow == USE_PREF_SIZE && prefOfRow > 0 ? boundedSize(0, current + actualPortion, prefOfRow) : 1934 current + actualPortion; 1935 final double portionUsed = bounded - current; 1936 remaining -= portionUsed; 1937 if (portionUsed != actualPortion || portionUsed == 0) { 1938 it.remove(); 1939 } 1940 heights.setSize(i, bounded); 1941 } 1942 } 1943 1944 1945 while (lastRows.size() > 0 && remaining > lastRows.size()) { 1946 double colPortion = Math.floor(remaining / lastRows.size()); 1947 for (Iterator<Integer> it = lastRows.iterator(); it.hasNext();) { 1948 int i = it.next(); 1949 double maxOfRow = getRowMaxHeight(i); 1950 double prefOfRow = getRowPrefHeight(i); 1951 double actualPortion = colPortion; 1952 1953 for (Entry<Interval, Double> ms : heights.multiSizes()) { 1954 final Interval interval = ms.getKey(); 1955 if (interval.end - 1 == i) { 1956 double curLength = heights.computeTotal(interval.begin, interval.end); 1957 actualPortion = Math.min(Math.max(0, ms.getValue() - curLength), 1958 actualPortion); 1959 } 1960 } 1961 1962 final double current = heights.getSize(i); 1963 double bounded = maxOfRow >= 0 ? boundedSize(0, current + actualPortion, maxOfRow) : 1964 maxOfRow == USE_PREF_SIZE && prefOfRow > 0 ? boundedSize(0, current + actualPortion, prefOfRow) : 1965 current + actualPortion; 1966 final double portionUsed = bounded - current; 1967 remaining -= portionUsed; 1968 if (portionUsed != actualPortion || portionUsed == 0) { 1969 it.remove(); 1970 } 1971 heights.setSize(i, bounded); 1972 } 1973 } 1974 return remaining; 1975 } 1976 1977 private double growOrShrinkRowHeights(CompositeSize heights, Priority priority, double extraHeight) { 1978 final boolean shrinking = extraHeight < 0; 1979 List<Integer> adjusting = new ArrayList<>(); 1980 1981 for (int i = 0; i < rowGrow.length; i++) { 1982 if (rowPercentHeight[i] < 0 && (shrinking || rowGrow[i] == priority)) { 1983 adjusting.add(i); 1984 } 1985 } 1986 1987 double available = extraHeight; // will be negative in shrinking case 1988 boolean handleRemainder = false; 1989 double portion = 0; 1990 1991 // RT-25684: We have to be careful that when subtracting change 1992 // that we don't jump right past 0 - this leads to an infinite 1993 // loop 1994 final boolean wasPositive = available >= 0.0; 1995 boolean isPositive = wasPositive; 1996 1997 CompositeSize limitSize = shrinking? computeMinHeights(null) : 1998 computeMaxHeights(); 1999 while (available != 0 && wasPositive == isPositive && adjusting.size() > 0) { 2000 if (!handleRemainder) { 2001 portion = available > 0 ? Math.floor(available / adjusting.size()) : 2002 Math.ceil(available / adjusting.size()); // negative in shrinking case 2003 } 2004 if (portion != 0) { 2005 for (Iterator<Integer> i = adjusting.iterator(); i.hasNext();) { 2006 final int index = i.next(); 2007 double limit = snapSpace(limitSize.getProportionalMinOrMaxSize(index, shrinking)) 2008 - heights.getSize(index); // negative in shrinking case 2009 if (shrinking && limit > 0 2010 || !shrinking && limit < 0) { // Limit completely if current size 2011 // (originally based on preferred) already passed the computed limit 2012 limit = 0; 2013 } 2014 final double change = Math.abs(limit) <= Math.abs(portion)? limit : portion; 2015 heights.addSize(index, change); 2016 available -= change; 2017 isPositive = available >= 0.0; 2018 if (Math.abs(change) < Math.abs(portion)) { 2019 i.remove(); 2020 } 2021 if (available == 0) { 2022 break; 2023 } 2024 } 2025 } else { 2026 // Handle the remainder 2027 portion = (int)(available) % adjusting.size(); 2028 if (portion == 0) { 2029 break; 2030 } else { 2031 // We have a remainder evenly distribute it. 2032 portion = shrinking ? -1 : 1; 2033 handleRemainder = true; 2034 } 2035 } 2036 } 2037 2038 return available; // might be negative in shrinking case 2039 } 2040 2041 private double adjustColumnWidths(final CompositeSize widths, double width) { 2042 assert(width != -1); 2043 final double snaphgap = snapSpace(getHgap()); 2044 final double left = snapSpace(getInsets().getLeft()); 2045 final double right = snapSpace(getInsets().getRight()); 2046 final double hgaps = snaphgap * (getNumberOfColumns() - 1); 2047 final double contentWidth = width - left - right; 2048 2049 // if there are percentage rows, give them their percentages first 2050 if (columnPercentTotal > 0) { 2051 double remainder = 0; 2052 for (int i = 0; i < columnPercentWidth.length; i++) { 2053 if (columnPercentWidth[i] >= 0) { 2054 double size = (contentWidth - hgaps) * (columnPercentWidth[i]/100); 2055 double floor = Math.floor(size); 2056 remainder += size - floor; 2057 2058 // snap size to integer boundary based on the computed remainder as we loop through the columns. 2059 size = floor; 2060 if (remainder >= 0.5) { 2061 size++; 2062 remainder = (-1.0) + remainder; 2063 } 2064 widths.setSize(i, size); 2065 } 2066 } 2067 } 2068 2069 double columnTotal = widths.computeTotal(); 2070 if (columnPercentTotal < 100) { 2071 double widthAvailable = width - left - right - columnTotal; 2072 // now that both fixed and percentage rows have been computed, divy up any surplus or deficit 2073 if (widthAvailable != 0) { 2074 // maybe grow or shrink row heights 2075 double remaining = growToMultiSpanPreferredWidths(widths, widthAvailable); 2076 remaining = growOrShrinkColumnWidths(widths, Priority.ALWAYS, remaining); 2077 remaining = growOrShrinkColumnWidths(widths, Priority.SOMETIMES, remaining); 2078 columnTotal += (widthAvailable - remaining); 2079 } 2080 } 2081 return columnTotal; 2082 } 2083 2084 private double growToMultiSpanPreferredWidths(CompositeSize widths, double extraWidth) { 2085 if (extraWidth <= 0) { 2086 return extraWidth; 2087 } 2088 2089 Set<Integer> columnsAlways = new TreeSet<>(); 2090 Set<Integer> columnsSometimes = new TreeSet<>(); 2091 Set<Integer> lastColumns = new TreeSet<>(); 2092 for (Entry<Interval, Double> ms : widths.multiSizes()) { 2093 final Interval interval = ms.getKey(); 2094 for (int i = interval.begin; i < interval.end; ++i) { 2095 if (columnPercentWidth[i] < 0) { 2096 switch (columnGrow[i]) { 2097 case ALWAYS: 2098 columnsAlways.add(i); 2099 break; 2100 case SOMETIMES: 2101 columnsSometimes.add(i); 2102 break; 2103 } 2104 } 2105 } 2106 if (columnPercentWidth[interval.end - 1] < 0) { 2107 lastColumns.add(interval.end - 1); 2108 } 2109 } 2110 2111 double remaining = extraWidth; 2112 2113 while (columnsAlways.size() > 0 && remaining > columnsAlways.size()) { 2114 double colPortion = Math.floor(remaining / columnsAlways.size()); 2115 for (Iterator<Integer> it = columnsAlways.iterator(); it.hasNext();) { 2116 int i = it.next(); 2117 double maxOfColumn = getColumnMaxWidth(i); 2118 double prefOfColumn = getColumnPrefWidth(i); 2119 double actualPortion = colPortion; 2120 2121 for (Entry<Interval, Double> ms : widths.multiSizes()) { 2122 final Interval interval = ms.getKey(); 2123 if (interval.contains(i)) { 2124 int intervalColumns = 0; 2125 for (int j = interval.begin; j < interval.end; ++j) { 2126 if (columnsAlways.contains(j)) { 2127 intervalColumns++; 2128 } 2129 } 2130 double curLength = widths.computeTotal(interval.begin, interval.end); 2131 actualPortion = Math.min(Math.floor(Math.max(0, (ms.getValue() - curLength) / intervalColumns)), 2132 actualPortion); 2133 } 2134 } 2135 2136 final double current = widths.getSize(i); 2137 double bounded = maxOfColumn >= 0 ? boundedSize(0, current + actualPortion, maxOfColumn) : 2138 maxOfColumn == USE_PREF_SIZE && prefOfColumn > 0 ? boundedSize(0, current + actualPortion, prefOfColumn) : 2139 current + actualPortion; 2140 final double portionUsed = bounded - current; 2141 remaining -= portionUsed; 2142 if (portionUsed != actualPortion || portionUsed == 0) { 2143 it.remove(); 2144 } 2145 widths.setSize(i, bounded); 2146 } 2147 } 2148 2149 while (columnsSometimes.size() > 0 && remaining > columnsSometimes.size()) { 2150 double colPortion = Math.floor(remaining / columnsSometimes.size()); 2151 for (Iterator<Integer> it = columnsSometimes.iterator(); it.hasNext();) { 2152 int i = it.next(); 2153 double maxOfColumn = getColumnMaxWidth(i); 2154 double prefOfColumn = getColumnPrefWidth(i); 2155 double actualPortion = colPortion; 2156 2157 for (Entry<Interval, Double> ms : widths.multiSizes()) { 2158 final Interval interval = ms.getKey(); 2159 if (interval.contains(i)) { 2160 int intervalColumns = 0; 2161 for (int j = interval.begin; j < interval.end; ++j) { 2162 if (columnsSometimes.contains(j)) { 2163 intervalColumns++; 2164 } 2165 } 2166 double curLength = widths.computeTotal(interval.begin, interval.end); 2167 actualPortion = Math.min(Math.floor(Math.max(0, (ms.getValue() - curLength) / intervalColumns)), 2168 actualPortion); 2169 } 2170 } 2171 2172 final double current = widths.getSize(i); 2173 double bounded = maxOfColumn >= 0 ? boundedSize(0, current + actualPortion, maxOfColumn) : 2174 maxOfColumn == USE_PREF_SIZE && prefOfColumn > 0 ? boundedSize(0, current + actualPortion, prefOfColumn) : 2175 current + actualPortion; 2176 final double portionUsed = bounded - current; 2177 remaining -= portionUsed; 2178 if (portionUsed != actualPortion || portionUsed == 0) { 2179 it.remove(); 2180 } 2181 widths.setSize(i, bounded); 2182 } 2183 } 2184 2185 2186 while (lastColumns.size() > 0 && remaining > lastColumns.size()) { 2187 double colPortion = Math.floor(remaining / lastColumns.size()); 2188 for (Iterator<Integer> it = lastColumns.iterator(); it.hasNext();) { 2189 int i = it.next(); 2190 double maxOfColumn = getColumnMaxWidth(i); 2191 double prefOfColumn = getColumnPrefWidth(i); 2192 double actualPortion = colPortion; 2193 2194 for (Entry<Interval, Double> ms : widths.multiSizes()) { 2195 final Interval interval = ms.getKey(); 2196 if (interval.end - 1 == i) { 2197 double curLength = widths.computeTotal(interval.begin, interval.end); 2198 actualPortion = Math.min(Math.max(0, ms.getValue() - curLength), 2199 actualPortion); 2200 } 2201 } 2202 2203 final double current = widths.getSize(i); 2204 double bounded = maxOfColumn >= 0 ? boundedSize(0, current + actualPortion, maxOfColumn) : 2205 maxOfColumn == USE_PREF_SIZE && prefOfColumn > 0 ? boundedSize(0, current + actualPortion, prefOfColumn) : 2206 current + actualPortion; 2207 final double portionUsed = bounded - current; 2208 remaining -= portionUsed; 2209 if (portionUsed != actualPortion || portionUsed == 0) { 2210 it.remove(); 2211 } 2212 widths.setSize(i, bounded); 2213 } 2214 } 2215 return remaining; 2216 } 2217 2218 private double growOrShrinkColumnWidths(CompositeSize widths, Priority priority, double extraWidth) { 2219 if (extraWidth == 0) { 2220 return 0; 2221 } 2222 final boolean shrinking = extraWidth < 0; 2223 List<Integer> adjusting = new ArrayList<>(); 2224 2225 for (int i = 0; i < columnGrow.length; i++) { 2226 if (columnPercentWidth[i] < 0 && (shrinking || columnGrow[i] == priority)) { 2227 adjusting.add(i); 2228 } 2229 } 2230 2231 double available = extraWidth; // will be negative in shrinking case 2232 boolean handleRemainder = false; 2233 double portion = 0; 2234 2235 // RT-25684: We have to be careful that when subtracting change 2236 // that we don't jump right past 0 - this leads to an infinite 2237 // loop 2238 final boolean wasPositive = available >= 0.0; 2239 boolean isPositive = wasPositive; 2240 2241 CompositeSize limitSize = shrinking? computeMinWidths(null) : 2242 computeMaxWidths(); 2243 while (available != 0 && wasPositive == isPositive && adjusting.size() > 0) { 2244 if (!handleRemainder) { 2245 portion = available > 0 ? Math.floor(available / adjusting.size()) : 2246 Math.ceil(available / adjusting.size()); // negative in shrinking case 2247 } 2248 if (portion != 0) { 2249 for (Iterator<Integer> i = adjusting.iterator(); i.hasNext();) { 2250 final int index = i.next(); 2251 double limit = snapSpace(limitSize.getProportionalMinOrMaxSize(index, shrinking)) 2252 - widths.getSize(index); // negative in shrinking case 2253 if (shrinking && limit > 0 2254 || !shrinking && limit < 0) { // Limit completely if current size 2255 // (originally based on preferred) already passed the computed limit 2256 limit = 0; 2257 } 2258 final double change = Math.abs(limit) <= Math.abs(portion)? limit : portion; 2259 widths.addSize(index, change); 2260 available -= change; 2261 isPositive = available >= 0.0; 2262 if (Math.abs(change) < Math.abs(portion)) { 2263 i.remove(); 2264 } 2265 if (available == 0) { 2266 break; 2267 } 2268 } 2269 } else { 2270 // Handle the remainder 2271 portion = (int)(available) % adjusting.size(); 2272 if (portion == 0) { 2273 break; 2274 } else { 2275 // We have a remainder evenly distribute it. 2276 portion = shrinking ? -1 : 1; 2277 handleRemainder = true; 2278 } 2279 } 2280 } 2281 2282 return available; // might be negative in shrinking case 2283 } 2284 2285 private void layoutGridLines(CompositeSize columnWidths, CompositeSize rowHeights, double x, double y, double columnHeight, double rowWidth) { 2286 if (!isGridLinesVisible()) { 2287 return; 2288 } 2289 if (!gridLines.getChildren().isEmpty()) { 2290 gridLines.getChildren().clear(); 2291 } 2292 double hgap = snapSpace(getHgap()); 2293 double vgap = snapSpace(getVgap()); 2294 2295 // create vertical lines 2296 double linex = x; 2297 double liney = y; 2298 for (int i = 0; i <= columnWidths.getLength(); i++) { 2299 gridLines.getChildren().add(createGridLine(linex, liney, linex, liney + columnHeight)); 2300 if (i > 0 && i < columnWidths.getLength() && getHgap() != 0) { 2301 linex += getHgap(); 2302 gridLines.getChildren().add(createGridLine(linex, liney, linex, liney + columnHeight)); 2303 } 2304 if (i < columnWidths.getLength()) { 2305 linex += columnWidths.getSize(i); 2306 } 2307 } 2308 // create horizontal lines 2309 linex = x; 2310 for (int i = 0; i <= rowHeights.getLength(); i++) { 2311 gridLines.getChildren().add(createGridLine(linex, liney, linex + rowWidth, liney)); 2312 if (i > 0 && i < rowHeights.getLength() && getVgap() != 0) { 2313 liney += getVgap(); 2314 gridLines.getChildren().add(createGridLine(linex, liney, linex + rowWidth, liney)); 2315 } 2316 if (i < rowHeights.getLength()) { 2317 liney += rowHeights.getSize(i); 2318 } 2319 } 2320 } 2321 2322 private Line createGridLine(double startX, double startY, double endX, double endY) { 2323 Line line = new Line(); 2324 line.setStartX(startX); 2325 line.setStartY(startY); 2326 line.setEndX(endX); 2327 line.setEndY(endY); 2328 line.setStroke(GRID_LINE_COLOR); 2329 line.setStrokeDashOffset(GRID_LINE_DASH); 2330 2331 return line; 2332 } 2333 2334 /** 2335 * Returns a string representation of this {@code GridPane} object. 2336 * @return a string representation of this {@code GridPane} object. 2337 */ 2338 @Override public String toString() { 2339 return "Grid hgap="+getHgap()+", vgap="+getVgap()+", alignment="+getAlignment(); 2340 } 2341 2342 private CompositeSize createCompositeRows(double initSize) { 2343 return new CompositeSize(getNumberOfRows(), rowPercentHeight, rowPercentTotal, 2344 snapSpace(getVgap()), initSize); 2345 } 2346 2347 private CompositeSize createCompositeColumns(double initSize) { 2348 return new CompositeSize(getNumberOfColumns(), columnPercentWidth, columnPercentTotal, 2349 snapSpace(getHgap()), initSize); 2350 } 2351 2352 private int getNodeRowEndConvertRemaining(Node child) { 2353 int rowSpan = getNodeRowSpan(child); 2354 return rowSpan != REMAINING? getNodeRowIndex(child) + rowSpan - 1 : getNumberOfRows() - 1; 2355 } 2356 2357 private int getNodeColumnEndConvertRemaining(Node child) { 2358 int columnSpan = getNodeColumnSpan(child); 2359 return columnSpan != REMAINING? getNodeColumnIndex(child) + columnSpan - 1 : getNumberOfColumns() - 1; 2360 } 2361 2362 2363 // This methods are inteded to be used by GridPaneDesignInfo 2364 private CompositeSize currentHeights; 2365 private CompositeSize currentWidths; 2366 2367 double[][] getGrid() { 2368 if (currentHeights == null || currentWidths == null) { 2369 return null; 2370 } 2371 return new double[][] {currentWidths.asArray(), currentHeights.asArray()}; 2372 } 2373 2374 /*************************************************************************** 2375 * * 2376 * Stylesheet Handling * 2377 * * 2378 **************************************************************************/ 2379 2380 /** 2381 * Super-lazy instantiation pattern from Bill Pugh. 2382 * @treatAsPrivate implementation detail 2383 */ 2384 private static class StyleableProperties { 2385 2386 private static final CssMetaData<GridPane,Boolean> GRID_LINES_VISIBLE = 2387 new CssMetaData<GridPane,Boolean>("-fx-grid-lines-visible", 2388 BooleanConverter.getInstance(), Boolean.FALSE) { 2389 2390 @Override 2391 public boolean isSettable(GridPane node) { 2392 return node.gridLinesVisible == null || 2393 !node.gridLinesVisible.isBound(); 2394 } 2395 2396 @Override 2397 public StyleableProperty<Boolean> getStyleableProperty(GridPane node) { 2398 return (StyleableProperty<Boolean>)node.gridLinesVisibleProperty(); 2399 } 2400 }; 2401 2402 private static final CssMetaData<GridPane,Number> HGAP = 2403 new CssMetaData<GridPane,Number>("-fx-hgap", 2404 SizeConverter.getInstance(), 0.0){ 2405 2406 @Override 2407 public boolean isSettable(GridPane node) { 2408 return node.hgap == null || !node.hgap.isBound(); 2409 } 2410 2411 @Override 2412 public StyleableProperty<Number> getStyleableProperty(GridPane node) { 2413 return (StyleableProperty<Number>)node.hgapProperty(); 2414 } 2415 2416 }; 2417 2418 private static final CssMetaData<GridPane,Pos> ALIGNMENT = 2419 new CssMetaData<GridPane,Pos>("-fx-alignment", 2420 new EnumConverter<Pos>(Pos.class), Pos.TOP_LEFT) { 2421 2422 @Override 2423 public boolean isSettable(GridPane node) { 2424 return node.alignment == null || !node.alignment.isBound(); 2425 } 2426 2427 @Override 2428 public StyleableProperty<Pos> getStyleableProperty(GridPane node) { 2429 return (StyleableProperty<Pos>)node.alignmentProperty(); 2430 } 2431 2432 }; 2433 2434 private static final CssMetaData<GridPane,Number> VGAP = 2435 new CssMetaData<GridPane,Number>("-fx-vgap", 2436 SizeConverter.getInstance(), 0.0){ 2437 2438 @Override 2439 public boolean isSettable(GridPane node) { 2440 return node.vgap == null || !node.vgap.isBound(); 2441 } 2442 2443 @Override 2444 public StyleableProperty<Number> getStyleableProperty(GridPane node) { 2445 return (StyleableProperty<Number>)node.vgapProperty(); 2446 } 2447 2448 }; 2449 2450 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 2451 static { 2452 2453 final List<CssMetaData<? extends Styleable, ?>> styleables = 2454 new ArrayList<CssMetaData<? extends Styleable, ?>>(Region.getClassCssMetaData()); 2455 styleables.add(GRID_LINES_VISIBLE); 2456 styleables.add(HGAP); 2457 styleables.add(ALIGNMENT); 2458 styleables.add(VGAP); 2459 2460 STYLEABLES = Collections.unmodifiableList(styleables); 2461 } 2462 } 2463 2464 /** 2465 * @return The CssMetaData associated with this class, which may include the 2466 * CssMetaData of its super classes. 2467 * @since JavaFX 8.0 2468 */ 2469 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 2470 return StyleableProperties.STYLEABLES; 2471 } 2472 2473 /** 2474 * {@inheritDoc} 2475 * 2476 * @since JavaFX 8.0 2477 */ 2478 2479 2480 @Override 2481 public List<CssMetaData<? extends Styleable, ?>> getCssMetaData() { 2482 return getClassCssMetaData(); 2483 } 2484 2485 private static final class Interval implements Comparable<Interval> { 2486 2487 public final int begin; 2488 public final int end; 2489 2490 public Interval(int begin, int end) { 2491 this.begin = begin; 2492 this.end = end; 2493 } 2494 2495 @Override 2496 public int compareTo(Interval o) { 2497 return begin != o.begin ? begin - o.begin : end - o.end; 2498 } 2499 2500 private boolean contains(int position) { 2501 return begin <= position && position < end; 2502 } 2503 2504 private int size() { 2505 return end - begin; 2506 } 2507 2508 } 2509 2510 private static final class CompositeSize implements Cloneable { 2511 2512 // These variables will be modified during the computations 2513 double singleSizes[]; 2514 private SortedMap<Interval, Double> multiSizes; 2515 private BitSet preset; 2516 2517 // Preset metrics for this dimension 2518 private final double fixedPercent[]; 2519 private final double totalFixedPercent; 2520 private final double gap; 2521 2522 public CompositeSize(int capacity, double fixedPercent[], double totalFixedPercent, double gap, double initSize) { 2523 singleSizes = new double[capacity]; 2524 Arrays.fill(singleSizes, initSize); 2525 2526 this.fixedPercent = fixedPercent; 2527 this.totalFixedPercent = totalFixedPercent; 2528 this.gap = gap; 2529 } 2530 2531 private void setSize(int position, double size) { 2532 singleSizes[position] = size; 2533 } 2534 2535 private void setPresetSize(int position, double size) { 2536 setSize(position, size); 2537 if (preset == null) { 2538 preset = new BitSet(singleSizes.length); 2539 } 2540 preset.set(position); 2541 } 2542 2543 private boolean isPreset(int position) { 2544 if (preset == null) { 2545 return false; 2546 } 2547 return preset.get(position); 2548 } 2549 2550 private void addSize(int position, double change) { 2551 singleSizes[position] = singleSizes[position] + change; 2552 } 2553 2554 private double getSize(int position) { 2555 return singleSizes[position]; 2556 } 2557 2558 private void setMaxSize(int position, double size) { 2559 singleSizes[position] = Math.max(singleSizes[position], size); 2560 } 2561 2562 private void setMultiSize(int startPosition, int endPosition, double size) { 2563 if (multiSizes == null) { 2564 multiSizes = new TreeMap<>(); 2565 } 2566 Interval i = new Interval(startPosition, endPosition); 2567 multiSizes.put(i, size); 2568 } 2569 2570 private Iterable<Entry<Interval, Double>> multiSizes() { 2571 if (multiSizes == null) { 2572 return Collections.EMPTY_LIST; 2573 } 2574 return multiSizes.entrySet(); 2575 } 2576 2577 private void setMaxMultiSize(int startPosition, int endPosition, double size) { 2578 if (multiSizes == null) { 2579 multiSizes = new TreeMap<>(); 2580 } 2581 Interval i = new Interval(startPosition, endPosition); 2582 Double sz = multiSizes.get(i); 2583 if (sz == null) { 2584 multiSizes.put(i, size); 2585 } else { 2586 multiSizes.put(i, Math.max(size, sz)); 2587 } 2588 } 2589 2590 private double getProportionalMinOrMaxSize(int position, boolean min) { 2591 double result = singleSizes[position]; 2592 if (!isPreset(position) && multiSizes != null) { 2593 for (Interval i : multiSizes.keySet()) { 2594 if (i.contains(position)) { 2595 double segment = multiSizes.get(i) / i.size(); 2596 double propSize = segment; 2597 for (int j = i.begin; j < i.end; ++j) { 2598 if (j != position) { 2599 if (min ? singleSizes[j] > segment : singleSizes[j] < segment) { 2600 propSize += segment - singleSizes[j]; 2601 } 2602 } 2603 } 2604 result = min ? Math.max(result, propSize) : Math.min(result, propSize); 2605 } 2606 } 2607 } 2608 return result; 2609 } 2610 2611 private double computeTotal(final int from, final int to) { 2612 double total = gap * (to - from - 1); 2613 for (int i = from; i < to; ++i) { 2614 total += singleSizes[i]; 2615 } 2616 return total; 2617 } 2618 2619 private double computeTotal() { 2620 return computeTotal(0, singleSizes.length); 2621 } 2622 2623 private boolean allPreset(int begin, int end) { 2624 if (preset == null) { 2625 return false; 2626 } 2627 for (int i = begin; i < end; ++i) { 2628 if (!preset.get(i)) { 2629 return false; 2630 } 2631 } 2632 return true; 2633 } 2634 2635 private double computeTotalWithMultiSize() { 2636 double total = computeTotal(); 2637 if (multiSizes != null) { 2638 for (Entry<Interval, Double> e: multiSizes.entrySet()) { 2639 final Interval i = e.getKey(); 2640 if (!allPreset(i.begin, i.end)) { 2641 double subTotal = computeTotal(i.begin, i.end); 2642 if (e.getValue() > subTotal) { 2643 total += e.getValue() - subTotal; 2644 } 2645 } 2646 } 2647 } 2648 if (totalFixedPercent > 0) { 2649 double totalNotFixed = 0; 2650 // First, remove the sizes that are fixed to be 0 2651 for (int i = 0; i < fixedPercent.length; ++i) { 2652 if (fixedPercent[i] == 0) { 2653 total -= singleSizes[i]; 2654 } 2655 } 2656 for (int i = 0; i < fixedPercent.length; ++i) { 2657 if (fixedPercent[i] > 0) { 2658 // Grow the total so that every size at it's value corresponds at least to it's fixedPercent of the total 2659 // i.e. total * fixedPercent[i] >= singleSizes[i] 2660 total = Math.max(total, singleSizes[i] * (100 / fixedPercent[i])); 2661 } else if (fixedPercent[i] < 0){ 2662 totalNotFixed += singleSizes[i]; 2663 } 2664 } 2665 if (totalFixedPercent < 100) { 2666 total = Math.max(total, totalNotFixed * 100 / (100 - totalFixedPercent)); 2667 } 2668 } 2669 return total; 2670 } 2671 2672 private int getLength() { 2673 return singleSizes.length; 2674 } 2675 2676 @Override 2677 protected Object clone() { 2678 try { 2679 CompositeSize clone = (CompositeSize) super.clone(); 2680 clone.singleSizes = clone.singleSizes.clone(); 2681 if (multiSizes != null) 2682 clone.multiSizes = new TreeMap<>(clone.multiSizes); 2683 return clone; 2684 } catch (CloneNotSupportedException ex) { 2685 throw new RuntimeException(ex); 2686 } 2687 } 2688 2689 private double[] asArray() { 2690 return singleSizes; 2691 } 2692 2693 } 2694 2695 /** 2696 * Copied from GridPaneDesignInfo for SceneBuilder. 2697 * 2698 * @treatAsPrivate implementation detail 2699 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2700 */ 2701 @Deprecated // SB-dependency: RT-33381 has been filed to track this 2702 public final int impl_getRowCount() { 2703 int nRows = this.getRowConstraints().size(); 2704 for (int i = 0; i < this.getChildren().size(); i++) { 2705 Node child = this.getChildren().get(i); 2706 if (child.isManaged()) { 2707 int rowIndex = GridPane.getNodeRowIndex(child); 2708 int rowEnd = GridPane.getNodeRowEnd(child); 2709 nRows = Math.max(nRows, (rowEnd != GridPane.REMAINING? rowEnd : rowIndex) + 1); 2710 } 2711 } 2712 return nRows; 2713 } 2714 2715 /** 2716 * Copied from GridPaneDesignInfo for SceneBuilder. 2717 * 2718 * @treatAsPrivate implementation detail 2719 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2720 */ 2721 @Deprecated // SB-dependency: RT-33381 has been filed to track this 2722 public final int impl_getColumnCount() { 2723 int nColumns = this.getColumnConstraints().size(); 2724 for (int i = 0; i < this.getChildren().size(); i++) { 2725 Node child = this.getChildren().get(i); 2726 if (child.isManaged()) { 2727 int columnIndex = GridPane.getNodeColumnIndex(child); 2728 int columnEnd = GridPane.getNodeColumnEnd(child); 2729 nColumns = Math.max(nColumns, (columnEnd != GridPane.REMAINING? columnEnd : columnIndex) + 1); 2730 } 2731 } 2732 return nColumns; 2733 } 2734 2735 /** 2736 * Copied from GridPaneDesignInfo for SceneBuilder. 2737 * 2738 * @treatAsPrivate implementation detail 2739 * @deprecated This is an internal API that is not intended for use and will be removed in the next version 2740 */ 2741 @Deprecated // SB-dependency: RT-33381 has been filed to track this 2742 public final Bounds impl_getCellBounds(int columnIndex, int rowIndex) { 2743 final double snaphgap = this.snapSpace(this.getHgap()); 2744 final double snapvgap = this.snapSpace(this.getVgap()); 2745 final double top = this.snapSpace(this.getInsets().getTop()); 2746 final double right = this.snapSpace(this.getInsets().getRight()); 2747 final double bottom = this.snapSpace(this.getInsets().getBottom()); 2748 final double left = this.snapSpace(this.getInsets().getLeft()); 2749 final double gridPaneHeight = this.snapSize(this.getHeight()) - (top + bottom); 2750 final double gridPaneWidth = this.snapSize(this.getWidth()) - (left + right); 2751 2752 // Compute grid. Result contains two double arrays, first for columns, second for rows 2753 double[] columnWidths; 2754 double[] rowHeights; 2755 2756 double[][] grid = this.getGrid(); 2757 if (grid == null) { 2758 rowHeights = new double[] {0}; 2759 rowIndex = 0; 2760 columnWidths = new double[] {0}; 2761 columnIndex = 0; 2762 } else { 2763 columnWidths = grid[0]; 2764 rowHeights = grid[1]; 2765 } 2766 2767 // Compute the total row height 2768 double rowTotal = 0; 2769 for (int i = 0; i < rowHeights.length; i++) { 2770 rowTotal += rowHeights[i]; 2771 } 2772 rowTotal += ((rowHeights.length - 1) * snapvgap); 2773 2774 // Adjust for alignment 2775 double minY = top + Region.computeYOffset(gridPaneHeight, rowTotal, this.getAlignment().getVpos()); 2776 double height = rowHeights[rowIndex]; 2777 for (int j = 0; j < rowIndex; j++) { 2778 minY += rowHeights[j] + snapvgap; 2779 } 2780 2781 // Compute the total column width 2782 double columnTotal = 0; 2783 for (int i = 0; i < columnWidths.length; i++) { 2784 columnTotal += columnWidths[i]; 2785 } 2786 columnTotal += ((columnWidths.length - 1) * snaphgap); 2787 2788 // Adjust for alignment 2789 double minX = left + Region.computeXOffset(gridPaneWidth, columnTotal, this.getAlignment().getHpos()); 2790 double width = columnWidths[columnIndex]; 2791 for (int j = 0; j < columnIndex; j++) { 2792 minX += columnWidths[j] + snaphgap; 2793 } 2794 2795 return new BoundingBox(minX, minY, width, height); 2796 } 2797 2798 }