FlowCellConf.java

  1. package swingtree.layout;

  2. import com.google.errorprone.annotations.Immutable;
  3. import org.slf4j.Logger;
  4. import org.slf4j.LoggerFactory;
  5. import swingtree.UI;
  6. import swingtree.UIForAnySwing;
  7. import swingtree.api.Configurator;

  8. import java.util.Arrays;
  9. import java.util.Objects;

  10. /**
  11.  *  An immutable configuration object used to define how a {@link FlowCell} should
  12.  *  place its associated component within a {@link ResponsiveGridFlowLayout}.<br>
  13.  *  Instances of this are passed to the {@link swingtree.api.Configurator} of a {@link FlowCell}
  14.  *  so that you can dynamically assign the number of cells a component should span,
  15.  *  based on the size category of the parent container.<br>
  16.  *  <br>
  17.  *  Use {@link UI#AUTO_SPAN(Configurator)} to create a {@link FlowCell} from a {@link Configurator}
  18.  *  and pass it to the {@link swingtree.UIForAnySwing#add(AddConstraint, UIForAnySwing[])}
  19.  *  of a SwingTree UI declaration.<br>
  20.  *  Here an example demonstrating how this might be used:
  21.  *  <pre>{@code
  22.  *    UI.panel().withPrefSize(400, 300)
  23.  *    .withFlowLayout()
  24.  *    .add(UI.AUTO_SPAN( it->it.small(12).medium(6).large(8) ),
  25.  *         html("A red cell").withStyle(it->it
  26.  *             .backgroundColor(UI.Color.RED)
  27.  *         )
  28.  *    )
  29.  *    .add(UI.AUTO_SPAN( it->it.small(12).medium(6).large(4) ),
  30.  *         html("a green cell").withStyle(it->it
  31.  *             .backgroundColor(Color.GREEN)
  32.  *         )
  33.  *    )
  34.  *  }</pre>
  35.  *  In the above example, the {@code it} parameter of the {@code Configurator} is an instance of this class.
  36.  *  The {@link Configurator} is called every time the {@link ResponsiveGridFlowLayout} updates the layout
  37.  *  of the parent container, so that the number of cells a component spans can be adjusted dynamically.<br>
  38.  *  The {@link UIForAnySwing#withFlowLayout()} creates a {@link ResponsiveGridFlowLayout} and attaches it to the panel.<br>
  39.  *  <p><b>
  40.  *      Note that the {@link FlowCell} configuration may only take effect if the parent
  41.  *      container has a {@link ResponsiveGridFlowLayout} as a {@link java.awt.LayoutManager} installed.
  42.  *  </b>
  43.  *  <br><br>
  44.  *  <p>
  45.  *  Besides configuring the number of cells to span, you can also
  46.  *  define how the cell should be filled vertically by the component
  47.  *  and how the component should be aligned vertically within the cell.<br>
  48.  *  Use the {@link #fill(boolean)} method to set the fill flag to {@code true}
  49.  *  if you want the component to fill the cell vertically.<br>
  50.  *  Use the {@link #align(UI.VerticalAlignment)} method to set the vertical alignment
  51.  *  of the component within the cell to either {@link UI.VerticalAlignment#TOP},
  52.  *  {@link UI.VerticalAlignment#CENTER}, or {@link UI.VerticalAlignment#BOTTOM}.
  53.  */
  54. @Immutable
  55. public final class FlowCellConf
  56. {
  57.     private static final Logger log = LoggerFactory.getLogger(FlowCellConf.class);
  58.     private final int                    _maxCellsToFill;
  59.     private final Size                   _parentSize;
  60.     private final ParentSizeClass        _parentSizeCategory;
  61.     @SuppressWarnings("Immutable")
  62.     private final FlowCellSpanPolicy[]   _autoSpans;
  63.     private final boolean                _fill;
  64.     private final UI.VerticalAlignment   _verticalAlignment;


  65.     FlowCellConf(
  66.             int                  maxCellsToFill,
  67.             Size                 parentSize,
  68.             ParentSizeClass      parentSizeCategory,
  69.             FlowCellSpanPolicy[] autoSpans,
  70.             boolean              fill,
  71.             UI.VerticalAlignment alignVertically
  72.     ) {
  73.         _maxCellsToFill     = maxCellsToFill;
  74.         _parentSize         = Objects.requireNonNull(parentSize);
  75.         _parentSizeCategory = Objects.requireNonNull(parentSizeCategory);
  76.         _autoSpans          = Objects.requireNonNull(autoSpans.clone());
  77.         _fill               = fill;
  78.         _verticalAlignment = alignVertically;
  79.     }

  80.     FlowCellSpanPolicy[] autoSpans() {
  81.         return _autoSpans.clone();
  82.     }

  83.     /**
  84.      *  Returns the maximum number of cells that a component can span
  85.      *  in a grid layout managed by a {@link ResponsiveGridFlowLayout}.<br>
  86.      *  The default value is 12, which is the maximum number of cells in a row
  87.      *  of the {@link ResponsiveGridFlowLayout}.
  88.      *  You may use this value to respond to it dynamically in the {@link Configurator}.
  89.      *
  90.      *  @return The maximum number of cells that a component can span in a grid layout.
  91.      */
  92.     public int maxCellsToFill() {
  93.         return _maxCellsToFill;
  94.     }

  95.     /**
  96.      *  Returns the {@link Size} object that represents the parent container's size.
  97.      *  The cell exposes this information so that you can use for a more
  98.      *  informed decision on how many cells a component should span.
  99.      *
  100.      *  @return The width and height of the parent container of the component
  101.      *          associated with this cell span configuration.
  102.      */
  103.     public Size parentSize() {
  104.         return _parentSize;
  105.     }

  106.     /**
  107.      *  Returns the {@link ParentSizeClass} category that represents the parent container's width
  108.      *  relative to its preferred width.<br>
  109.      *  The cell exposes this information so that you can use for a more
  110.      *  informed decision on how many cells a component should span.<br>
  111.      *  <p>
  112.      *  The {@link ParentSizeClass} category is calculated by dividing the parent container's width
  113.      *  by the preferred width of the parent container. <br>
  114.      *  So a parent container is considered large if its size is close to the preferred size,
  115.      *  and {@link ParentSizeClass#OVERSIZE} if it is significantly larger.
  116.      *
  117.      *  @return The category of the parent container's size.
  118.      */
  119.     public ParentSizeClass parentSizeCategory() {
  120.         return _parentSizeCategory;
  121.     }

  122.     /**
  123.      *  Returns a new and updated {@link FlowCellConf} instance with an additional
  124.      *  {@link FlowCellSpanPolicy} that specifies the number of cells to fill
  125.      *  for a given {@link ParentSizeClass} category.<br>
  126.      *
  127.      *  @param size The {@link ParentSizeClass} category to set.
  128.      *  @param cellsToFill The number of cells to fill.
  129.      *  @return A new {@link FlowCellConf} instance with the given {@link ParentSizeClass} and number of cells to fill.
  130.      */
  131.     public FlowCellConf with( ParentSizeClass size, int cellsToFill ) {
  132.         Objects.requireNonNull(size);
  133.         if ( cellsToFill < 0 ) {
  134.             log.warn(
  135.                     "Encountered negative number '"+cellsToFill+"' for cells " +
  136.                     "to fill as part of the '"+ResponsiveGridFlowLayout.class.getSimpleName()+"' layout.",
  137.                     new Throwable()
  138.                 );
  139.             cellsToFill = 0;
  140.         } else if ( cellsToFill > _maxCellsToFill ) {
  141.             log.warn(
  142.                     "Encountered number '"+cellsToFill+"' for cells to fill that is greater " +
  143.                     "than the maximum number of cells to fill '"+_maxCellsToFill+"' " +
  144.                     "as part of the '"+ResponsiveGridFlowLayout.class.getSimpleName()+"' layout.",
  145.                     new Throwable()
  146.                 );
  147.             cellsToFill = _maxCellsToFill;
  148.         }
  149.         FlowCellSpanPolicy[] autoSpans = new FlowCellSpanPolicy[_autoSpans.length+1];
  150.         System.arraycopy(_autoSpans, 0, autoSpans, 0, _autoSpans.length);
  151.         autoSpans[_autoSpans.length] = FlowCellSpanPolicy.of(size, cellsToFill);
  152.         return new FlowCellConf(_maxCellsToFill, _parentSize, _parentSizeCategory, autoSpans, _fill, _verticalAlignment);
  153.     }

  154.     /**
  155.      *  Returns a new and updated {@link FlowCellConf} instance with an additional
  156.      *  {@link FlowCellSpanPolicy} that specifies the number of cells to fill
  157.      *  when the parent container is categorized as {@link ParentSizeClass#VERY_SMALL}.<br>
  158.      *  A parent container is considered "very small" if its width
  159.      *  is between 0/5 and 1/5 of its preferred width.<br>
  160.      *  <p>
  161.      *  The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
  162.      *  Values outside this range will be clamped to the nearest valid value and a warning will be logged.
  163.      *
  164.      *  @param span The number of cells to span as part of a grid
  165.      *                     with a total of {@link #maxCellsToFill()} number of cells horizontally.
  166.      *  @return A new {@link FlowCellConf} instance with the given number of cells to fill
  167.      *          for the {@link ParentSizeClass#VERY_SMALL} category.
  168.      */
  169.     public FlowCellConf verySmall( int span ) {
  170.         return with(ParentSizeClass.VERY_SMALL, span);
  171.     }

  172.     /**
  173.      *  Returns a new and updated {@link FlowCellConf} instance with an additional
  174.      *  {@link FlowCellSpanPolicy} that specifies the number of cells to fill
  175.      *  when the parent container is categorized as {@link ParentSizeClass#SMALL}.<br>
  176.      *  A parent container is considered "small" if its width
  177.      *  is between 1/5 and 2/5 of its preferred width.<br>
  178.      *  <p>
  179.      *  The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
  180.      *  Values outside this range will be clamped to the nearest valid value and a warning will be logged.
  181.      *
  182.      *  @param span The number of cells to span as part of a grid
  183.      *                     with a total of {@link #maxCellsToFill()} number of cells horizontally.
  184.      *  @return A new {@link FlowCellConf} instance with the given number of cells to fill
  185.      *          for the {@link ParentSizeClass#SMALL} category.
  186.      */
  187.     public FlowCellConf small( int span ) {
  188.         return with(ParentSizeClass.SMALL, span);
  189.     }

  190.     /**
  191.      *  Returns a new and updated {@link FlowCellConf} instance with an additional
  192.      *  {@link FlowCellSpanPolicy} that specifies the number of cells to fill
  193.      *  when the parent container is categorized as {@link ParentSizeClass#MEDIUM}.<br>
  194.      *  A parent container is considered "medium" if its width
  195.      *  is between 2/5 and 3/5 of its preferred width.<br>
  196.      *  <p>
  197.      *  The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
  198.      *  Values outside this range will be clamped to the nearest valid value and a warning will be logged.
  199.      *
  200.      *  @param span The number of cells to span as part of a grid
  201.      *                     with a total of {@link #maxCellsToFill()} number of cells horizontally.
  202.      *  @return A new {@link FlowCellConf} instance with the given number of cells to fill
  203.      *          for the {@link ParentSizeClass#MEDIUM} category.
  204.      */
  205.     public FlowCellConf medium( int span ) {
  206.         return with(ParentSizeClass.MEDIUM, span);
  207.     }

  208.     /**
  209.      *  Returns a new and updated {@link FlowCellConf} instance with an additional
  210.      *  {@link FlowCellSpanPolicy} that specifies the number of cells to fill
  211.      *  when the parent container is categorized as {@link ParentSizeClass#LARGE}.<br>
  212.      *  A parent container is considered "large" if its width
  213.      *  is between 3/5 and 4/5 of its preferred width.<br>
  214.      *  <p>
  215.      *  The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
  216.      *  Values outside this range will be clamped to the nearest valid value and a warning will be logged.
  217.      *
  218.      *  @param span The number of cells to span as part of a grid
  219.      *                     with a total of {@link #maxCellsToFill()} number of cells horizontally.
  220.      *  @return A new {@link FlowCellConf} instance with the given number of cells to fill
  221.      *          for the {@link ParentSizeClass#LARGE} category.
  222.      */
  223.     public FlowCellConf large( int span ) {
  224.         return with(ParentSizeClass.LARGE, span);
  225.     }

  226.     /**
  227.      *  Returns a new and updated {@link FlowCellConf} instance with an additional
  228.      *  {@link FlowCellSpanPolicy} that specifies the number of cells to fill
  229.      *  when the parent container is categorized as {@link ParentSizeClass#VERY_LARGE}.<br>
  230.      *  A parent container is considered "very large" if its width
  231.      *  is between 4/5 and 5/5 of its preferred width.<br>
  232.      *  <p>
  233.      *  The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
  234.      *  Values outside this range will be clamped to the nearest valid value and a warning will be logged.
  235.      *
  236.      *  @param span The number of cells to span as part of a grid
  237.      *                     with a total of {@link #maxCellsToFill()} number of cells horizontally.
  238.      *  @return A new {@link FlowCellConf} instance with the given number of cells to fill
  239.      *          for the {@link ParentSizeClass#VERY_LARGE} category.
  240.      */
  241.     public FlowCellConf veryLarge( int span ) {
  242.         return with(ParentSizeClass.VERY_LARGE, span);
  243.     }

  244.     /**
  245.      *  Returns a new and updated {@link FlowCellConf} instance with an additional
  246.      *  {@link FlowCellSpanPolicy} that specifies the number of cells to fill
  247.      *  when the parent container is categorized as {@link ParentSizeClass#OVERSIZE}.<br>
  248.      *  A parent container is considered "oversize" if its width is greater than its preferred width.<br>
  249.      *  <p>
  250.      *  The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
  251.      *  Values outside this range will be clamped to the nearest valid value and a warning will be logged.
  252.      *
  253.      *  @param span The number of cells to span as part of a grid
  254.      *                     with a total of {@link #maxCellsToFill()} number of cells horizontally.
  255.      *  @return A new {@link FlowCellConf} instance with the given number of cells to fill
  256.      *          for the {@link ParentSizeClass#OVERSIZE} category.
  257.      */
  258.     public FlowCellConf oversize( int span ) {
  259.         return with(ParentSizeClass.OVERSIZE, span);
  260.     }

  261.     boolean fill() {
  262.         return _fill;
  263.     }

  264.     /**
  265.      *  This flag defines how the grid cell in the flow layout should be filled
  266.      *  by the component to which this cell configuration belongs.<br>
  267.      *  The default behavior is to not fill the cell, but rather to align the component
  268.      *  vertically in the center of the cell and use the component's preferred height.<br>
  269.      *  If this flag is set to {@code true}, the component will fill the cell vertically
  270.      *  and use the full height of the cell, which is also the full height of the row.<br>
  271.      *  Note that this will ignore the component's preferred height!
  272.      *
  273.      * @param fill Whether the cell should be filled by the component.
  274.      * @return A new {@link FlowCellConf} instance with the given fill flag.
  275.      */
  276.     public FlowCellConf fill(boolean fill) {
  277.         return new FlowCellConf(_maxCellsToFill, _parentSize, _parentSizeCategory, _autoSpans, fill, _verticalAlignment);
  278.     }

  279.     UI.VerticalAlignment verticalAlignment() {
  280.         return _verticalAlignment;
  281.     }

  282.     /**
  283.      *  The {@link UI.VerticalAlignment} of a flow cell tells the
  284.      *  {@link ResponsiveGridFlowLayout} how to place the component
  285.      *  vertically within the cell.<br>
  286.      *  So if you want the component to be aligned at the top,
  287.      *  use {@link UI.VerticalAlignment#TOP}, if you want it to be
  288.      *  centered, use {@link UI.VerticalAlignment#CENTER}, and if you
  289.      *  want it to be aligned at the bottom, use {@link UI.VerticalAlignment#BOTTOM}.
  290.      *
  291.      * @param alignment The vertical alignment of the component within the cell.
  292.      * @return A new {@link FlowCellConf} instance with the given vertical alignment policy.
  293.      */
  294.     public FlowCellConf align(UI.VerticalAlignment alignment) {
  295.         return new FlowCellConf(_maxCellsToFill, _parentSize, _parentSizeCategory, _autoSpans, _fill, alignment);
  296.     }

  297.     @Override
  298.     public String toString() {
  299.         return this.getClass().getSimpleName() + "[" +
  300.                     "maxCellsToFill="     + _maxCellsToFill + ", " +
  301.                     "parentSize="         + _parentSize + ", " +
  302.                     "parentSizeCategory=" + _parentSizeCategory + ", " +
  303.                     "autoSpans="          + _autoSpans.length + ", " +
  304.                     "fill="               + _fill + ", " +
  305.                     "alignVertically="    + _verticalAlignment +
  306.                 "]";
  307.     }

  308.     @Override
  309.     public boolean equals(Object obj) {
  310.         if ( obj == this ) {
  311.             return true;
  312.         }
  313.         if ( obj == null || obj.getClass() != this.getClass() ) {
  314.             return false;
  315.         }
  316.         FlowCellConf that = (FlowCellConf)obj;
  317.         return this._maxCellsToFill == that._maxCellsToFill
  318.             && this._parentSize.equals(that._parentSize)
  319.             && this._parentSizeCategory == that._parentSizeCategory
  320.             && this._autoSpans.length == that._autoSpans.length
  321.             && Arrays.deepEquals(this._autoSpans, that._autoSpans)
  322.             && this._fill == that._fill
  323.             && this._verticalAlignment == that._verticalAlignment;
  324.     }

  325.     @Override
  326.     public int hashCode() {
  327.         return Objects.hash(_maxCellsToFill, _parentSize, _parentSizeCategory, Arrays.hashCode(_autoSpans), _fill, _verticalAlignment);
  328.     }
  329. }