ResponsiveGridFlowLayout.java

  1. package swingtree.layout;

  2. import net.miginfocom.layout.LC;
  3. import net.miginfocom.swing.MigLayout;
  4. import org.jspecify.annotations.Nullable;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import swingtree.UI;

  8. import javax.swing.JComponent;
  9. import java.awt.*;
  10. import java.util.Objects;
  11. import java.util.Optional;
  12. import java.util.concurrent.atomic.AtomicInteger;

  13. /**
  14.  * A flow layout arranges components in a directional flow, much
  15.  * like lines of text in a paragraph.
  16.  */
  17. public final class ResponsiveGridFlowLayout implements LayoutManager2 {

  18.     private static final int NUMBER_OF_COLUMNS = 12;
  19.     private static final Logger log = LoggerFactory.getLogger(ResponsiveGridFlowLayout.class);

  20.     private UI.HorizontalAlignment _alignmentCode;
  21.     private int                    _horizontalGapSize;
  22.     private int                    _verticalGapSize;
  23.     private boolean                _alignOnBaseline;

  24.     /**
  25.      * Constructs a new {@code FlowLayout} with a centered alignment and a
  26.      * default 5-unit horizontal and vertical gap.
  27.      */
  28.     public ResponsiveGridFlowLayout() {
  29.         this(UI.HorizontalAlignment.CENTER, 5, 5);
  30.     }

  31.     /**
  32.      * Constructs a new {@code FlowLayout} with the specified
  33.      * alignment and a default 5-unit horizontal and vertical gap.
  34.      * The value of the alignment argument must be one of
  35.      * {@code FlowLayout.LEFT}, {@code FlowLayout.RIGHT},
  36.      * {@code FlowLayout.CENTER}, {@code FlowLayout.LEADING},
  37.      * or {@code FlowLayout.TRAILING}.
  38.      *
  39.      * @param align the alignment value
  40.      */
  41.     public ResponsiveGridFlowLayout(UI.HorizontalAlignment align) {
  42.         this(align, 5, 5);
  43.     }

  44.     /**
  45.      * Creates a new flow layout manager with the indicated alignment
  46.      * and the indicated horizontal and vertical gaps.
  47.      * <p>
  48.      * The value of the alignment argument must be one of
  49.      * {@code FlowLayout.LEFT}, {@code FlowLayout.RIGHT},
  50.      * {@code FlowLayout.CENTER}, {@code FlowLayout.LEADING},
  51.      * or {@code FlowLayout.TRAILING}.
  52.      *
  53.      * @param align the alignment value
  54.      * @param horizontalGapSize  the horizontal gap between components
  55.      *              and between the components and the
  56.      *              borders of the {@code Container}
  57.      * @param verticalGapSize  the vertical gap between components
  58.      *              and between the components and the
  59.      *              borders of the {@code Container}
  60.      */
  61.     public ResponsiveGridFlowLayout(
  62.         UI.HorizontalAlignment align,
  63.         int horizontalGapSize,
  64.         int verticalGapSize
  65.     ) {
  66.         _alignmentCode     = align;
  67.         _horizontalGapSize = horizontalGapSize;
  68.         _verticalGapSize   = verticalGapSize;
  69.     }

  70.     /**
  71.      * Gets the alignment for this layout.
  72.      * Possible values are {@code UI.HorizontalAlignment.LEFT},
  73.      * {@code UI.HorizontalAlignment.RIGHT}, {@code UI.HorizontalAlignment.CENTER},
  74.      * {@code UI.HorizontalAlignment.LEADING},
  75.      * or {@code UI.HorizontalAlignment.TRAILING}.
  76.      *
  77.      * @return the alignment value for this layout
  78.      * @see #setAlignment
  79.      */
  80.     public UI.HorizontalAlignment getAlignment() {
  81.         return _alignmentCode;
  82.     }

  83.     /**
  84.      * Sets the alignment for this layout.
  85.      * Possible values are
  86.      * <ul>
  87.      * <li>{@code UI.HorizontalAlignment.LEFT}
  88.      * <li>{@code UI.HorizontalAlignment.RIGHT}
  89.      * <li>{@code UI.HorizontalAlignment.CENTER}
  90.      * <li>{@code UI.HorizontalAlignment.LEADING}
  91.      * <li>{@code UI.HorizontalAlignment.TRAILING}
  92.      * </ul>
  93.      *
  94.      * @param align one of the alignment values shown above
  95.      * @see #getAlignment()
  96.      */
  97.     public void setAlignment(UI.HorizontalAlignment align) {
  98.         _alignmentCode = align;
  99.     }

  100.     /**
  101.      * Gets the horizontal gap between components
  102.      * and between the components and the borders
  103.      * of the {@code Container}
  104.      *
  105.      * @return the horizontal gap between components
  106.      * and between the components and the borders
  107.      * of the {@code Container}
  108.      * @see ResponsiveGridFlowLayout#setHorizontalGapSize(int)
  109.      */
  110.     public int horizontalGapSize() {
  111.         return UI.scale(_horizontalGapSize);
  112.     }

  113.     /**
  114.      * Sets the horizontal gap between components and
  115.      * between the components and the borders of the
  116.      * {@code Container}.
  117.      *
  118.      * @param size the horizontal gap between components
  119.      *             and between the components and the borders
  120.      *             of the {@code Container}
  121.      * @see ResponsiveGridFlowLayout#horizontalGapSize()
  122.      */
  123.     public void setHorizontalGapSize(int size) {
  124.         _horizontalGapSize = size;
  125.     }

  126.     /**
  127.      * Gets the vertical gap between components and
  128.      * between the components and the borders of the
  129.      * {@code Container}.
  130.      *
  131.      * @return the vertical gap between components
  132.      * and between the components and the borders
  133.      * of the {@code Container}
  134.      * @see ResponsiveGridFlowLayout#setVerticalGapSize(int)
  135.      */
  136.     public int verticalGapSize() {
  137.         return UI.scale(_verticalGapSize);
  138.     }

  139.     /**
  140.      * Sets the vertical gap between components and between
  141.      * the components and the borders of the {@code Container}.
  142.      *
  143.      * @param size the vertical gap between components
  144.      *             and between the components and the borders
  145.      *             of the {@code Container}
  146.      * @see ResponsiveGridFlowLayout#verticalGapSize()
  147.      */
  148.     public void setVerticalGapSize(int size) {
  149.         _verticalGapSize = size;
  150.     }

  151.     /**
  152.      * Sets whether or not components should be vertically aligned along their
  153.      * baseline.  Components that do not have a baseline will be centered.
  154.      * The default is false.
  155.      *
  156.      * @param alignOnBaseline whether or not components should be
  157.      *                        vertically aligned on their baseline
  158.      */
  159.     public void setAlignOnBaseline(boolean alignOnBaseline) {
  160.         this._alignOnBaseline = alignOnBaseline;
  161.     }

  162.     /**
  163.      * Returns true if components are to be vertically aligned along
  164.      * their baseline.  The default is false.
  165.      *
  166.      * @return true if components are to be vertically aligned along
  167.      * their baseline
  168.      */
  169.     public boolean getAlignOnBaseline() {
  170.         return _alignOnBaseline;
  171.     }

  172.     /**
  173.      * Adds the specified component to the layout.
  174.      * Not used by this class.
  175.      *
  176.      * @param name the name of the component
  177.      * @param comp the component to be added
  178.      */
  179.     @Override
  180.     public void addLayoutComponent( String name, Component comp ) {
  181.     }

  182.     /**
  183.      * Removes the specified component from the layout.
  184.      * Not used by this class.
  185.      *
  186.      * @param comp the component to remove
  187.      * @see java.awt.Container#removeAll
  188.      */
  189.     @Override
  190.     public void removeLayoutComponent( Component comp ) {
  191.     }

  192.     /**
  193.      * Returns the preferred dimensions for this layout given the
  194.      * <i>visible</i> components in the specified target container.
  195.      *
  196.      * @param target the container that needs to be laid out
  197.      * @return the preferred dimensions to lay out the
  198.      * subcomponents of the specified container
  199.      * @see java.awt.Container
  200.      * @see #minimumLayoutSize
  201.      * @see java.awt.Container#getPreferredSize
  202.      */
  203.     @Override
  204.     public Dimension preferredLayoutSize( Container target ) {
  205.         synchronized (target.getTreeLock()) {
  206.             Dimension dim = new Dimension(0, 0);
  207.             int nmembers = target.getComponentCount();
  208.             boolean firstVisibleComponent = true;
  209.             boolean useBaseline = getAlignOnBaseline();
  210.             int maxAscent = 0;
  211.             int maxDescent = 0;
  212.             int hgap = UI.scale(_horizontalGapSize);
  213.             int vgap = UI.scale(_verticalGapSize);

  214.             for (int i = 0; i < nmembers; i++) {
  215.                 Component m = target.getComponent(i);
  216.                 if (m.isVisible()) {
  217.                     Dimension d = m.getPreferredSize();
  218.                     dim.height = Math.max(dim.height, d.height);
  219.                     if (firstVisibleComponent) {
  220.                         firstVisibleComponent = false;
  221.                     } else {
  222.                         dim.width += hgap;
  223.                     }
  224.                     dim.width += d.width;
  225.                     if (useBaseline) {
  226.                         int baseline = m.getBaseline(d.width, d.height);
  227.                         if (baseline >= 0) {
  228.                             maxAscent = Math.max(maxAscent, baseline);
  229.                             maxDescent = Math.max(maxDescent, d.height - baseline);
  230.                         }
  231.                     }
  232.                 }
  233.             }
  234.             if (useBaseline) {
  235.                 dim.height = Math.max(maxAscent + maxDescent, dim.height);
  236.             }
  237.             Insets insets = target.getInsets();
  238.             dim.width += insets.left + insets.right + hgap * 2;
  239.             dim.height += insets.top + insets.bottom + vgap * 2;
  240.             return dim;
  241.         }
  242.     }

  243.     /**
  244.      * Returns the minimum dimensions needed to layout the <i>visible</i>
  245.      * components contained in the specified target container.
  246.      *
  247.      * @param target the container that needs to be laid out
  248.      * @return the minimum dimensions to lay out the
  249.      * subcomponents of the specified container
  250.      * @see #preferredLayoutSize
  251.      * @see java.awt.Container
  252.      * @see java.awt.Container#doLayout
  253.      */
  254.     @Override
  255.     public Dimension minimumLayoutSize( Container target ) {
  256.         synchronized (target.getTreeLock()) {
  257.             boolean useBaseline = getAlignOnBaseline();
  258.             Dimension dim = new Dimension(0, 0);
  259.             int nmembers = target.getComponentCount();
  260.             int maxAscent = 0;
  261.             int maxDescent = 0;
  262.             boolean firstVisibleComponent = true;
  263.             int hgap = UI.scale(_horizontalGapSize);
  264.             int vgap = UI.scale(_verticalGapSize);

  265.             for (int i = 0; i < nmembers; i++) {
  266.                 Component m = target.getComponent(i);
  267.                 if (m.isVisible()) {
  268.                     Dimension d = m.getMinimumSize();
  269.                     dim.height = Math.max(dim.height, d.height);
  270.                     if (firstVisibleComponent) {
  271.                         firstVisibleComponent = false;
  272.                     } else {
  273.                         dim.width += hgap;
  274.                     }
  275.                     dim.width += d.width;
  276.                     if (useBaseline) {
  277.                         int baseline = m.getBaseline(d.width, d.height);
  278.                         if (baseline >= 0) {
  279.                             maxAscent = Math.max(maxAscent, baseline);
  280.                             maxDescent = Math.max(maxDescent,
  281.                                     dim.height - baseline);
  282.                         }
  283.                     }
  284.                 }
  285.             }

  286.             if (useBaseline) {
  287.                 dim.height = Math.max(maxAscent + maxDescent, dim.height);
  288.             }

  289.             Insets insets = target.getInsets();
  290.             dim.width += insets.left + insets.right + hgap * 2;
  291.             dim.height += insets.top + insets.bottom + vgap * 2;
  292.             return dim;
  293.         }
  294.     }

  295.     /**
  296.      * Centers the elements in the specified row, if there is any slack.
  297.      *
  298.      * @param target      the component which needs to be moved
  299.      * @param cells       an array of cells, one for each component of the target
  300.      * @param x           the x coordinate
  301.      * @param y           the y coordinate
  302.      * @param width       the width dimensions
  303.      * @param height      the height dimensions
  304.      * @param rowStart    the beginning of the row
  305.      * @param rowEnd      the ending of the row
  306.      * @param useBaseline Whether or not to align on baseline.
  307.      * @param ascent      Ascent for the components. This is only valid if
  308.      *                    useBaseline is true.
  309.      * @param descent     Ascent for the components. This is only valid if
  310.      *                    useBaseline is true.
  311.      * @return actual row height
  312.      */
  313.     private int moveComponents(
  314.             Container target, Cell[] cells,
  315.             int x, int y, int width, int height,
  316.             int rowStart, int rowEnd, boolean ltr,
  317.             boolean useBaseline, @Nullable int[] ascent,
  318.             @Nullable int[] descent
  319.     ) {
  320.         int hgap = UI.scale(_horizontalGapSize);
  321.         switch (_alignmentCode) {
  322.             case LEFT:
  323.                 x += ltr ? 0 : width;
  324.                 break;
  325.             case CENTER:
  326.                 x += width / 2;
  327.                 break;
  328.             case RIGHT:
  329.                 x += ltr ? width : 0;
  330.                 break;
  331.             case LEADING:
  332.                 break;
  333.             case TRAILING:
  334.                 x += width;
  335.                 break;
  336.         }
  337.         int maxAscent = 0;
  338.         int nonbaselineHeight = 0;
  339.         int baselineOffset = 0;
  340.         if (useBaseline) {
  341.             Objects.requireNonNull(ascent);
  342.             Objects.requireNonNull(descent);
  343.             int maxDescent = 0;
  344.             for (int i = rowStart; i < rowEnd; i++) {
  345.                 Component m = target.getComponent(i);
  346.                 if (m.isVisible()) {
  347.                     if (ascent[i] >= 0) {
  348.                         maxAscent = Math.max(maxAscent, ascent[i]);
  349.                         maxDescent = Math.max(maxDescent, descent[i]);
  350.                     } else {
  351.                         nonbaselineHeight = Math.max(m.getHeight(),
  352.                                 nonbaselineHeight);
  353.                     }
  354.                 }
  355.             }
  356.             height = Math.max(maxAscent + maxDescent, nonbaselineHeight);
  357.             baselineOffset = (height - maxAscent - maxDescent) / 2;
  358.         }
  359.         for (int i = rowStart; i < rowEnd; i++) {
  360.             Component m = target.getComponent(i);
  361.             if (m.isVisible()) {
  362.                 Optional<FlowCellConf> optionalFlowCellConf = cells[i].flowCell();
  363.                 boolean fillHeight = optionalFlowCellConf.map(FlowCellConf::fill).orElse(false);
  364.                 UI.VerticalAlignment verticalAlignment = optionalFlowCellConf.map(FlowCellConf::verticalAlignment).orElse(UI.VerticalAlignment.CENTER);
  365.                 int cy;
  366.                 if (ascent != null && useBaseline && ascent[i] >= 0) {
  367.                     cy = y + baselineOffset + maxAscent - ascent[i];
  368.                 } else {
  369.                     if (fillHeight) {
  370.                         cy = y;
  371.                     } else {
  372.                         if ( verticalAlignment == UI.VerticalAlignment.TOP )
  373.                             cy = y;
  374.                         else if ( verticalAlignment == UI.VerticalAlignment.BOTTOM )
  375.                             cy = y + height - m.getHeight();
  376.                         else // centered:
  377.                             cy = y + (height - m.getHeight()) / 2;
  378.                     }
  379.                 }
  380.                 if (ltr) {
  381.                     m.setLocation(x, cy);
  382.                 } else {
  383.                     m.setLocation(target.getWidth() - x - m.getWidth(), cy);
  384.                 }
  385.                 x += m.getWidth() + hgap;

  386.                 if ( fillHeight ) {
  387.                     m.setSize(m.getWidth(), height);
  388.                 }
  389.             }
  390.         }
  391.         return height;
  392.     }

  393.     /**
  394.      * Lays out the container. This method lets each
  395.      * <i>visible</i> component take
  396.      * its preferred size by reshaping the components in the
  397.      * target container in order to satisfy the alignment of
  398.      * this layout manager.
  399.      *
  400.      * @param target the specified component being laid out
  401.      * @see Container
  402.      * @see java.awt.Container#doLayout
  403.      */
  404.     @Override
  405.     public void layoutContainer(Container target) {
  406.         synchronized (target.getTreeLock()) {
  407.             final int hgap = UI.scale(_horizontalGapSize);
  408.             final int vgap = UI.scale(_verticalGapSize);
  409.             final Insets insets = target.getInsets();
  410.             final int maxwidth = target.getWidth() - (insets.left + insets.right + hgap * 2);
  411.             final int generalMaxWidth = target.getPreferredSize().width - (insets.left + insets.right + hgap * 2);
  412.             final int nmembers = target.getComponentCount();
  413.             int x = 0, y = insets.top + vgap;
  414.             int rowh = 0, start = 0;

  415.             Cell[] cells = _createCells(target, nmembers, maxwidth, generalMaxWidth);

  416.             boolean ltr = target.getComponentOrientation().isLeftToRight();
  417.             boolean useBaseline = getAlignOnBaseline();
  418.             int[] ascent = null;
  419.             int[] descent = null;

  420.             if (useBaseline) {
  421.                 ascent = new int[nmembers];
  422.                 descent = new int[nmembers];
  423.             }

  424.             for (int i = 0; i < nmembers; i++) {
  425.                 Component m = cells[i].component();
  426.                 if (m.isVisible()) {
  427.                     Dimension d = m.getPreferredSize();
  428.                     try {
  429.                         d = _dimensionsFromCellConf(cells[i], maxwidth).orElse(d);
  430.                     } catch (Exception e) {
  431.                         log.error("Error applying cell configuration", e);
  432.                     }
  433.                     m.setSize(d.width, d.height);

  434.                     if (useBaseline ) {
  435.                         Objects.requireNonNull(ascent);
  436.                         Objects.requireNonNull(descent);
  437.                         int baseline = m.getBaseline(d.width, d.height);
  438.                         if (baseline >= 0) {
  439.                             ascent[i] = baseline;
  440.                             descent[i] = d.height - baseline;
  441.                         } else {
  442.                             ascent[i] = -1;
  443.                         }
  444.                     }
  445.                     if ((x == 0) || ((x + d.width) <= maxwidth)) {
  446.                         if (x > 0) {
  447.                             x += hgap;
  448.                         }
  449.                         x += d.width;
  450.                         rowh = Math.max(rowh, d.height);
  451.                     } else {
  452.                         rowh = moveComponents(
  453.                                 target, cells,
  454.                                 insets.left + hgap, y,
  455.                                 maxwidth - x, rowh, start, i, ltr,
  456.                                 useBaseline, ascent, descent
  457.                         );
  458.                         x = d.width;
  459.                         y += vgap + rowh;
  460.                         rowh = d.height;
  461.                         start = i;
  462.                     }
  463.                 }
  464.             }
  465.             moveComponents(
  466.                     target, cells,
  467.                     insets.left + hgap, y, maxwidth - x, rowh,
  468.                     start, nmembers, ltr, useBaseline, ascent, descent
  469.             );
  470.         }
  471.     }

  472.     private Cell[] _createCells(
  473.             Container target,
  474.             int nmembers,
  475.             int maxwidth,
  476.             int generalMaxWidth
  477.     ) {
  478.         Cell[] cells = new Cell[nmembers];
  479.         AtomicInteger componentsInRow = new AtomicInteger(0);
  480.         double currentRowSize = 0;
  481.         for (int i = 0; i < nmembers; i++) {
  482.             Component m = target.getComponent(i);
  483.             Optional<Cell> optionalCell = Optional.empty();
  484.             double rowSizeIncrease = 0;
  485.             if (m instanceof JComponent) {
  486.                 JComponent jc = (JComponent) m;
  487.                 AddConstraint addConstraint = (AddConstraint) jc.getClientProperty(AddConstraint.class);
  488.                 if (addConstraint instanceof FlowCell) {
  489.                     FlowCell cell = (FlowCell) addConstraint;
  490.                     optionalCell = cellFromCellConf(target, cell, jc, componentsInRow, maxwidth, generalMaxWidth);
  491.                     rowSizeIncrease += optionalCell.flatMap(Cell::autoSpan)
  492.                                                     .map(FlowCellSpanPolicy::cellsToFill)
  493.                                                     .orElse(0);
  494.                 }
  495.             }
  496.             if ( !optionalCell.isPresent() ) {
  497.                 double prefComponentWidth = m.getPreferredSize().getWidth();
  498.                 if ( maxwidth > 0 && prefComponentWidth > 0 ) {
  499.                     rowSizeIncrease += NUMBER_OF_COLUMNS * prefComponentWidth / maxwidth;
  500.                 }
  501.             }

  502.             cells[i] = optionalCell.orElse(new Cell(m, componentsInRow, null, null));

  503.             double newRowSize = currentRowSize + rowSizeIncrease;
  504.             if ( newRowSize < NUMBER_OF_COLUMNS ) {
  505.                 // Still room in the row for new components...
  506.                 componentsInRow.set(componentsInRow.get() + 1);
  507.                 currentRowSize += rowSizeIncrease;
  508.             } else if ( Math.round(newRowSize) == NUMBER_OF_COLUMNS ) {
  509.                 // We have a new row with no leftovers.
  510.                 componentsInRow.set(componentsInRow.get() + 1);
  511.                 componentsInRow = new AtomicInteger(0);
  512.                 currentRowSize = 0;
  513.             } else if ( newRowSize > NUMBER_OF_COLUMNS ) {
  514.                 // The row does not fit the new component. We need to start a new row.
  515.                 componentsInRow = new AtomicInteger(1);
  516.                 cells[i].setNumberOfComponents(componentsInRow);
  517.                 currentRowSize = rowSizeIncrease; // We have a new row with the current component.
  518.             }
  519.         }
  520.         return cells;
  521.     }

  522.     /**
  523.      * Returns a string representation of this {@code FlowLayout}
  524.      * object and its values.
  525.      *
  526.      * @return a string representation of this layout
  527.      */
  528.     public String toString() {
  529.         String str = "";
  530.         int hgap = UI.scale(_horizontalGapSize);
  531.         int vgap = UI.scale(_verticalGapSize);
  532.         switch (_alignmentCode) {
  533.             case LEFT:
  534.                 str = ",align=left";
  535.                 break;
  536.             case CENTER:
  537.                 str = ",align=center";
  538.                 break;
  539.             case RIGHT:
  540.                 str = ",align=right";
  541.                 break;
  542.             case LEADING:
  543.                 str = ",align=leading";
  544.                 break;
  545.             case TRAILING:
  546.                 str = ",align=trailing";
  547.                 break;
  548.             case UNDEFINED:
  549.                 str = ",align=?";
  550.                 break;
  551.         }
  552.         return getClass().getName() + "[horizontalGap=" + hgap + ",verticalGap=" + vgap + str + "]";
  553.     }

  554.     @Override
  555.     public void addLayoutComponent(Component comp, Object constraints) {
  556.         if (constraints instanceof AddConstraint) {
  557.             if (comp instanceof JComponent) {
  558.                 JComponent jc = (JComponent) comp;
  559.                 jc.putClientProperty(AddConstraint.class, constraints);
  560.             }
  561.         }
  562.     }

  563.     @Override
  564.     public Dimension maximumLayoutSize(Container target) {
  565.         return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  566.     }

  567.     @Override
  568.     public float getLayoutAlignmentX(Container target) {
  569.         return 0;
  570.     }

  571.     @Override
  572.     public float getLayoutAlignmentY(Container target) {
  573.         return 0;
  574.     }

  575.     @Override
  576.     public void invalidateLayout(Container target) {

  577.     }

  578.     public Optional<Cell> cellFromCellConf(
  579.             Component parent,
  580.             FlowCell flowCell,
  581.             Component child,
  582.             AtomicInteger componentCounter,
  583.             int maxWidth,
  584.             int generalMaxWidth
  585.     ) {
  586.         if ( maxWidth <= 0 ) {
  587.             return Optional.empty();
  588.         }
  589.         // How much preferred width the parent actually fills:
  590.         ParentSizeClass currentParentSizeCategory = ParentSizeClass.of(maxWidth, generalMaxWidth);

  591.         boolean shouldFillHeight = false;
  592.         LayoutManager childLayout = ( child instanceof JComponent ) ? ((JComponent) child).getLayout() : null;
  593.         if ( childLayout instanceof MigLayout ) {
  594.             Object layoutConstraints = ((MigLayout) childLayout).getLayoutConstraints();
  595.             // If the child has the "fill" or "filly" constraint, we should fill the height.
  596.             if ( layoutConstraints instanceof String ) {
  597.                 String constraints = (String) layoutConstraints;
  598.                 shouldFillHeight = constraints.contains("fill") || constraints.contains("filly");
  599.             } else if ( layoutConstraints instanceof LC ) {
  600.                 LC lc = (LC) layoutConstraints;
  601.                 shouldFillHeight = lc.isFillY();
  602.             }
  603.         }
  604.         Size parentSize = Size.of(parent.getWidth(), parent.getHeight());
  605.         FlowCellConf cellConf = flowCell.fetchConfig(NUMBER_OF_COLUMNS, parentSize, currentParentSizeCategory, shouldFillHeight);
  606.         Optional<FlowCellSpanPolicy> autoSpan = _findNextBestAutoSpan(cellConf, currentParentSizeCategory);
  607.         return autoSpan.map(autoCellSpanPolicy -> new Cell(child, componentCounter, autoCellSpanPolicy, cellConf));
  608.     }

  609.     private Optional<Dimension> _dimensionsFromCellConf( Cell cell, int maxWidth ) {

  610.         if ( maxWidth <= 0 ) {
  611.             return Optional.empty();
  612.         }

  613.         Optional<FlowCellSpanPolicy> autoSpan = cell.autoSpan();
  614.         if (!autoSpan.isPresent()) {
  615.             return Optional.empty();
  616.         }

  617.         int cellsToFill = autoSpan.get().cellsToFill();
  618.         int unusableSpace = ((cell.numberOfComponentsInRow()-1) * UI.scale(_horizontalGapSize));
  619.         int width = ((maxWidth - unusableSpace) * cellsToFill) / NUMBER_OF_COLUMNS;
  620.         Dimension newSize = new Dimension(width, cell.component().getPreferredSize().height);
  621.         return Optional.of(newSize);
  622.     }

  623.     private static Optional<FlowCellSpanPolicy> _findNextBestAutoSpan( FlowCellConf cell, ParentSizeClass targetSize ) {
  624.         Optional<FlowCellSpanPolicy> autoSpan = _find(targetSize.ordinal(), cell);
  625.         if ( autoSpan.isPresent() )
  626.             return autoSpan;

  627.         // We did not find the exact match. Let's try to find the closest match.

  628.         int numberOfSizeClasses = ParentSizeClass.values().length;
  629.         int targetOrdinal = targetSize.ordinal();
  630.         /*
  631.             We want to find the enum value which is closed to the target ordinal.
  632.          */
  633.         int sign = ( targetSize.ordinal() > numberOfSizeClasses / 2 ? 1 : -1 );
  634.         for ( int offset = 1; offset < numberOfSizeClasses; offset++ ) {
  635.             sign = -sign;
  636.             autoSpan = _find(targetOrdinal + offset * sign, cell);
  637.             if ( autoSpan.isPresent() )
  638.                 return autoSpan;

  639.             sign = -sign;
  640.             autoSpan = _find(targetOrdinal + offset * sign, cell);
  641.             if ( autoSpan.isPresent() )
  642.                 return autoSpan;
  643.         }
  644.         return Optional.empty();
  645.     }

  646.     private static Optional<FlowCellSpanPolicy> _find( int ordinal, FlowCellConf cell ) {
  647.         if ( ordinal < 0 || ordinal >= ParentSizeClass.values().length ) {
  648.             return Optional.empty();
  649.         }
  650.         ParentSizeClass targetSize = ParentSizeClass.values()[ordinal];
  651.         for ( FlowCellSpanPolicy autoSpan : cell.autoSpans() ) {
  652.             if ( autoSpan.parentSize() == targetSize ) {
  653.                 return Optional.of(autoSpan);
  654.             }
  655.         }
  656.         return Optional.empty();
  657.     }

  658.     private static final class Cell {

  659.         private final Component component;
  660.         private final @Nullable FlowCellSpanPolicy autoSpan;
  661.         private final @Nullable FlowCellConf cellConf;

  662.         private AtomicInteger numberOfComponents;


  663.         Cell(
  664.                 Component component,
  665.                 AtomicInteger componentCounter,
  666.                 @Nullable FlowCellSpanPolicy autoSpan,
  667.                 @Nullable FlowCellConf cellConf
  668.         ) {
  669.             this.component          = component;
  670.             this.numberOfComponents = componentCounter;
  671.             this.autoSpan           = autoSpan;
  672.             this.cellConf           = cellConf;
  673.         }

  674.         public Component component() {
  675.             return component;
  676.         }

  677.         public Optional<FlowCellSpanPolicy> autoSpan() {
  678.             return Optional.ofNullable(autoSpan);
  679.         }

  680.         public Optional<FlowCellConf> flowCell() {
  681.             return Optional.ofNullable(cellConf);
  682.         }

  683.         public int numberOfComponentsInRow() {
  684.             return numberOfComponents.get();
  685.         }

  686.         public void setNumberOfComponents(AtomicInteger numberOfComponents) {
  687.             this.numberOfComponents = numberOfComponents;
  688.         }
  689.     }

  690. }