11package dev .ikm .komet .kview .controls .skin ;
22
33import dev .ikm .komet .kview .controls .SectionTitledPane ;
4+ import dev .ikm .komet .kview .fxutils .FXUtils ;
45import javafx .beans .binding .Bindings ;
56import javafx .collections .ObservableList ;
67import javafx .css .PseudoClass ;
@@ -203,18 +204,32 @@ protected void layoutChildren() {
203204 super .layoutChildren ();
204205 }
205206
207+ /**
208+ * The height the content wants, which is every row's preferred height stacked up. Measured
209+ * from the children rather than from the row constraints: those hold the heights the grid's
210+ * current height allowed (see {@link #applyRowConstraints()}), so a grid that was once
211+ * squeezed would go on asking for the squeezed height and never get its room back.
212+ */
213+ @ Override
214+ protected double computePrefHeight (double width ) {
215+ double contentWidth = width < 0 ? -1 : width - snappedLeftInset () - snappedRightInset ();
216+ double [] rowPrefHeights = rowPrefHeights (contentWidth );
217+ double prefHeight = Math .max (0 , rowPrefHeights .length - 1 ) * getVgap ();
218+ for (double rowPrefHeight : rowPrefHeights ) {
219+ prefHeight += rowPrefHeight ;
220+ }
221+ return snappedTopInset () + prefHeight + snappedBottomInset ();
222+ }
223+
206224 /**
207225 * Recomputes the fixed per-row heights for the current size and content and installs them
208226 * as this grid's row constraints. Guarded so constraints are only touched when the heights
209227 * actually change — installing them re-requests layout, and the guard is what lets that
210228 * follow-up pass settle.
211229 */
212230 private void applyRowConstraints () {
213- List <Node > children = getManagedChildren ();
214- int rowCount = 0 ;
215- for (Node child : children ) {
216- rowCount = Math .max (rowCount , rowIndexOf (child ) + 1 );
217- }
231+ double [] rowPrefHeights = rowPrefHeights (getWidth () - snappedLeftInset () - snappedRightInset ());
232+ int rowCount = rowPrefHeights .length ;
218233 if (rowCount == 0 ) {
219234 if (appliedRowHeights .length > 0 ) {
220235 appliedRowHeights = new double [0 ];
@@ -223,25 +238,9 @@ private void applyRowConstraints() {
223238 return ;
224239 }
225240
226- // Column widths are uniform (see numberColumnsProperty subscription: every column
227- // gets the same percentWidth), so each child's width — which its preferred height
228- // may depend on — follows from its column span alone.
229- int columnCount = Math .max (1 , getColumnConstraints ().size ());
230- double contentWidth = getWidth () - snappedLeftInset () - snappedRightInset ();
231- double columnWidth = (contentWidth - getHgap () * (columnCount - 1 )) / columnCount ;
232-
233- double [] rowPrefHeights = new double [rowCount ];
234- for (Node child : children ) {
235- Integer span = GridPane .getColumnSpan (child );
236- int columnSpan = span == null ? 1 : span ;
237- double cellWidth = columnWidth * columnSpan + getHgap () * (columnSpan - 1 );
238- int row = rowIndexOf (child );
239- rowPrefHeights [row ] = Math .max (rowPrefHeights [row ], child .prefHeight (cellWidth ));
240- }
241-
242241 double availableHeight = getHeight () - snappedTopInset () - snappedBottomInset ()
243242 - getVgap () * (rowCount - 1 );
244- double [] rowHeights = capAtFairShare (rowPrefHeights , availableHeight );
243+ double [] rowHeights = FXUtils . capAtFairShare (rowPrefHeights , availableHeight );
245244 for (int i = 0 ; i < rowHeights .length ; i ++) {
246245 rowHeights [i ] = snapSizeY (rowHeights [i ]);
247246 }
@@ -264,48 +263,46 @@ private void applyRowConstraints() {
264263 getRowConstraints ().setAll (constraints );
265264 }
266265
267- private static int rowIndexOf (Node child ) {
268- Integer rowIndex = GridPane .getRowIndex (child );
269- return rowIndex == null ? 0 : rowIndex ;
270- }
271-
272266 /**
273- * Allocates the available height among the rows: every row gets at most its preferred
274- * height, and a row only gets less when the rows that need more than an equal split
275- * cannot be satisfied — those tall rows then share what the short rows left over.
267+ * The preferred height of each row — the tallest preferred height among the children in it
268+ * — measured at the width the passed in content width leaves each cell. A negative content
269+ * width (the grid is being measured without one) leaves the children to their own
270+ * unconstrained preferred height.
276271 */
277- private static double [] capAtFairShare (double [] prefHeights , double availableHeight ) {
278- int rowCount = prefHeights .length ;
279- double [] heights = new double [rowCount ];
280- boolean [] keepsPref = new boolean [rowCount ];
281- double remaining = Math .max (0 , availableHeight );
282- int uncapped = rowCount ;
283-
284- // Settle the rows that fit within the current fair share; every row settled frees
285- // up share for the rest, so iterate until a full pass settles nothing.
286- boolean settledAny = true ;
287- while (settledAny && uncapped > 0 ) {
288- settledAny = false ;
289- double fairShare = remaining / uncapped ;
290- for (int i = 0 ; i < rowCount ; i ++) {
291- if (!keepsPref [i ] && prefHeights [i ] <= fairShare ) {
292- keepsPref [i ] = true ;
293- heights [i ] = prefHeights [i ];
294- remaining -= prefHeights [i ];
295- uncapped --;
296- settledAny = true ;
297- }
298- }
272+ private double [] rowPrefHeights (double contentWidth ) {
273+ List <Node > children = getManagedChildren ();
274+ int rowCount = 0 ;
275+ for (Node child : children ) {
276+ rowCount = Math .max (rowCount , rowIndexOf (child ) + 1 );
299277 }
300- if (uncapped > 0 ) {
301- double fairShare = remaining / uncapped ;
302- for (int i = 0 ; i < rowCount ; i ++) {
303- if (!keepsPref [i ]) {
304- heights [i ] = fairShare ;
305- }
306- }
278+ if (rowCount == 0 ) {
279+ return new double [0 ];
307280 }
308- return heights ;
281+
282+ // Column widths are uniform (see numberColumnsProperty subscription: every column
283+ // gets the same percentWidth), so each child's width — which its preferred height
284+ // may depend on — follows from its column span alone.
285+ int columnCount = Math .max (1 , getColumnConstraints ().size ());
286+ double columnWidth = contentWidth < 0
287+ ? -1
288+ : (contentWidth - getHgap () * (columnCount - 1 )) / columnCount ;
289+
290+ double [] rowPrefHeights = new double [rowCount ];
291+ for (Node child : children ) {
292+ Integer span = GridPane .getColumnSpan (child );
293+ int columnSpan = span == null ? 1 : span ;
294+ double cellWidth = columnWidth < 0
295+ ? -1
296+ : columnWidth * columnSpan + getHgap () * (columnSpan - 1 );
297+ int row = rowIndexOf (child );
298+ rowPrefHeights [row ] = Math .max (rowPrefHeights [row ], child .prefHeight (cellWidth ));
299+ }
300+ return rowPrefHeights ;
301+ }
302+
303+ private static int rowIndexOf (Node child ) {
304+ Integer rowIndex = GridPane .getRowIndex (child );
305+ return rowIndex == null ? 0 : rowIndex ;
309306 }
310307
311308 private boolean heightsChanged (double [] rowHeights ) {
0 commit comments