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