FlowCellConf.java
- package swingtree.layout;
- import com.google.errorprone.annotations.Immutable;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import swingtree.UI;
- import swingtree.UIForAnySwing;
- import swingtree.api.Configurator;
- import java.util.Arrays;
- import java.util.Objects;
- /**
- * An immutable configuration object used to define how a {@link FlowCell} should
- * place its associated component within a {@link ResponsiveGridFlowLayout}.<br>
- * Instances of this are passed to the {@link swingtree.api.Configurator} of a {@link FlowCell}
- * so that you can dynamically assign the number of cells a component should span,
- * based on the size category of the parent container.<br>
- * <br>
- * Use {@link UI#AUTO_SPAN(Configurator)} to create a {@link FlowCell} from a {@link Configurator}
- * and pass it to the {@link swingtree.UIForAnySwing#add(AddConstraint, UIForAnySwing[])}
- * of a SwingTree UI declaration.<br>
- * Here an example demonstrating how this might be used:
- * <pre>{@code
- * UI.panel().withPrefSize(400, 300)
- * .withFlowLayout()
- * .add(UI.AUTO_SPAN( it->it.small(12).medium(6).large(8) ),
- * html("A red cell").withStyle(it->it
- * .backgroundColor(UI.Color.RED)
- * )
- * )
- * .add(UI.AUTO_SPAN( it->it.small(12).medium(6).large(4) ),
- * html("a green cell").withStyle(it->it
- * .backgroundColor(Color.GREEN)
- * )
- * )
- * }</pre>
- * In the above example, the {@code it} parameter of the {@code Configurator} is an instance of this class.
- * The {@link Configurator} is called every time the {@link ResponsiveGridFlowLayout} updates the layout
- * of the parent container, so that the number of cells a component spans can be adjusted dynamically.<br>
- * The {@link UIForAnySwing#withFlowLayout()} creates a {@link ResponsiveGridFlowLayout} and attaches it to the panel.<br>
- * <p><b>
- * Note that the {@link FlowCell} configuration may only take effect if the parent
- * container has a {@link ResponsiveGridFlowLayout} as a {@link java.awt.LayoutManager} installed.
- * </b>
- * <br><br>
- * <p>
- * Besides configuring the number of cells to span, you can also
- * define how the cell should be filled vertically by the component
- * and how the component should be aligned vertically within the cell.<br>
- * Use the {@link #fill(boolean)} method to set the fill flag to {@code true}
- * if you want the component to fill the cell vertically.<br>
- * Use the {@link #align(UI.VerticalAlignment)} method to set the vertical alignment
- * of the component within the cell to either {@link UI.VerticalAlignment#TOP},
- * {@link UI.VerticalAlignment#CENTER}, or {@link UI.VerticalAlignment#BOTTOM}.
- */
- @Immutable
- public final class FlowCellConf
- {
- private static final Logger log = LoggerFactory.getLogger(FlowCellConf.class);
- private final int _maxCellsToFill;
- private final Size _parentSize;
- private final ParentSizeClass _parentSizeCategory;
- @SuppressWarnings("Immutable")
- private final FlowCellSpanPolicy[] _autoSpans;
- private final boolean _fill;
- private final UI.VerticalAlignment _verticalAlignment;
- FlowCellConf(
- int maxCellsToFill,
- Size parentSize,
- ParentSizeClass parentSizeCategory,
- FlowCellSpanPolicy[] autoSpans,
- boolean fill,
- UI.VerticalAlignment alignVertically
- ) {
- _maxCellsToFill = maxCellsToFill;
- _parentSize = Objects.requireNonNull(parentSize);
- _parentSizeCategory = Objects.requireNonNull(parentSizeCategory);
- _autoSpans = Objects.requireNonNull(autoSpans.clone());
- _fill = fill;
- _verticalAlignment = alignVertically;
- }
- FlowCellSpanPolicy[] autoSpans() {
- return _autoSpans.clone();
- }
- /**
- * Returns the maximum number of cells that a component can span
- * in a grid layout managed by a {@link ResponsiveGridFlowLayout}.<br>
- * The default value is 12, which is the maximum number of cells in a row
- * of the {@link ResponsiveGridFlowLayout}.
- * You may use this value to respond to it dynamically in the {@link Configurator}.
- *
- * @return The maximum number of cells that a component can span in a grid layout.
- */
- public int maxCellsToFill() {
- return _maxCellsToFill;
- }
- /**
- * Returns the {@link Size} object that represents the parent container's size.
- * The cell exposes this information so that you can use for a more
- * informed decision on how many cells a component should span.
- *
- * @return The width and height of the parent container of the component
- * associated with this cell span configuration.
- */
- public Size parentSize() {
- return _parentSize;
- }
- /**
- * Returns the {@link ParentSizeClass} category that represents the parent container's width
- * relative to its preferred width.<br>
- * The cell exposes this information so that you can use for a more
- * informed decision on how many cells a component should span.<br>
- * <p>
- * The {@link ParentSizeClass} category is calculated by dividing the parent container's width
- * by the preferred width of the parent container. <br>
- * So a parent container is considered large if its size is close to the preferred size,
- * and {@link ParentSizeClass#OVERSIZE} if it is significantly larger.
- *
- * @return The category of the parent container's size.
- */
- public ParentSizeClass parentSizeCategory() {
- return _parentSizeCategory;
- }
- /**
- * Returns a new and updated {@link FlowCellConf} instance with an additional
- * {@link FlowCellSpanPolicy} that specifies the number of cells to fill
- * for a given {@link ParentSizeClass} category.<br>
- *
- * @param size The {@link ParentSizeClass} category to set.
- * @param cellsToFill The number of cells to fill.
- * @return A new {@link FlowCellConf} instance with the given {@link ParentSizeClass} and number of cells to fill.
- */
- public FlowCellConf with( ParentSizeClass size, int cellsToFill ) {
- Objects.requireNonNull(size);
- if ( cellsToFill < 0 ) {
- log.warn(
- "Encountered negative number '"+cellsToFill+"' for cells " +
- "to fill as part of the '"+ResponsiveGridFlowLayout.class.getSimpleName()+"' layout.",
- new Throwable()
- );
- cellsToFill = 0;
- } else if ( cellsToFill > _maxCellsToFill ) {
- log.warn(
- "Encountered number '"+cellsToFill+"' for cells to fill that is greater " +
- "than the maximum number of cells to fill '"+_maxCellsToFill+"' " +
- "as part of the '"+ResponsiveGridFlowLayout.class.getSimpleName()+"' layout.",
- new Throwable()
- );
- cellsToFill = _maxCellsToFill;
- }
- FlowCellSpanPolicy[] autoSpans = new FlowCellSpanPolicy[_autoSpans.length+1];
- System.arraycopy(_autoSpans, 0, autoSpans, 0, _autoSpans.length);
- autoSpans[_autoSpans.length] = FlowCellSpanPolicy.of(size, cellsToFill);
- return new FlowCellConf(_maxCellsToFill, _parentSize, _parentSizeCategory, autoSpans, _fill, _verticalAlignment);
- }
- /**
- * Returns a new and updated {@link FlowCellConf} instance with an additional
- * {@link FlowCellSpanPolicy} that specifies the number of cells to fill
- * when the parent container is categorized as {@link ParentSizeClass#VERY_SMALL}.<br>
- * A parent container is considered "very small" if its width
- * is between 0/5 and 1/5 of its preferred width.<br>
- * <p>
- * The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
- * Values outside this range will be clamped to the nearest valid value and a warning will be logged.
- *
- * @param span The number of cells to span as part of a grid
- * with a total of {@link #maxCellsToFill()} number of cells horizontally.
- * @return A new {@link FlowCellConf} instance with the given number of cells to fill
- * for the {@link ParentSizeClass#VERY_SMALL} category.
- */
- public FlowCellConf verySmall( int span ) {
- return with(ParentSizeClass.VERY_SMALL, span);
- }
- /**
- * Returns a new and updated {@link FlowCellConf} instance with an additional
- * {@link FlowCellSpanPolicy} that specifies the number of cells to fill
- * when the parent container is categorized as {@link ParentSizeClass#SMALL}.<br>
- * A parent container is considered "small" if its width
- * is between 1/5 and 2/5 of its preferred width.<br>
- * <p>
- * The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
- * Values outside this range will be clamped to the nearest valid value and a warning will be logged.
- *
- * @param span The number of cells to span as part of a grid
- * with a total of {@link #maxCellsToFill()} number of cells horizontally.
- * @return A new {@link FlowCellConf} instance with the given number of cells to fill
- * for the {@link ParentSizeClass#SMALL} category.
- */
- public FlowCellConf small( int span ) {
- return with(ParentSizeClass.SMALL, span);
- }
- /**
- * Returns a new and updated {@link FlowCellConf} instance with an additional
- * {@link FlowCellSpanPolicy} that specifies the number of cells to fill
- * when the parent container is categorized as {@link ParentSizeClass#MEDIUM}.<br>
- * A parent container is considered "medium" if its width
- * is between 2/5 and 3/5 of its preferred width.<br>
- * <p>
- * The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
- * Values outside this range will be clamped to the nearest valid value and a warning will be logged.
- *
- * @param span The number of cells to span as part of a grid
- * with a total of {@link #maxCellsToFill()} number of cells horizontally.
- * @return A new {@link FlowCellConf} instance with the given number of cells to fill
- * for the {@link ParentSizeClass#MEDIUM} category.
- */
- public FlowCellConf medium( int span ) {
- return with(ParentSizeClass.MEDIUM, span);
- }
- /**
- * Returns a new and updated {@link FlowCellConf} instance with an additional
- * {@link FlowCellSpanPolicy} that specifies the number of cells to fill
- * when the parent container is categorized as {@link ParentSizeClass#LARGE}.<br>
- * A parent container is considered "large" if its width
- * is between 3/5 and 4/5 of its preferred width.<br>
- * <p>
- * The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
- * Values outside this range will be clamped to the nearest valid value and a warning will be logged.
- *
- * @param span The number of cells to span as part of a grid
- * with a total of {@link #maxCellsToFill()} number of cells horizontally.
- * @return A new {@link FlowCellConf} instance with the given number of cells to fill
- * for the {@link ParentSizeClass#LARGE} category.
- */
- public FlowCellConf large( int span ) {
- return with(ParentSizeClass.LARGE, span);
- }
- /**
- * Returns a new and updated {@link FlowCellConf} instance with an additional
- * {@link FlowCellSpanPolicy} that specifies the number of cells to fill
- * when the parent container is categorized as {@link ParentSizeClass#VERY_LARGE}.<br>
- * A parent container is considered "very large" if its width
- * is between 4/5 and 5/5 of its preferred width.<br>
- * <p>
- * The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
- * Values outside this range will be clamped to the nearest valid value and a warning will be logged.
- *
- * @param span The number of cells to span as part of a grid
- * with a total of {@link #maxCellsToFill()} number of cells horizontally.
- * @return A new {@link FlowCellConf} instance with the given number of cells to fill
- * for the {@link ParentSizeClass#VERY_LARGE} category.
- */
- public FlowCellConf veryLarge( int span ) {
- return with(ParentSizeClass.VERY_LARGE, span);
- }
- /**
- * Returns a new and updated {@link FlowCellConf} instance with an additional
- * {@link FlowCellSpanPolicy} that specifies the number of cells to fill
- * when the parent container is categorized as {@link ParentSizeClass#OVERSIZE}.<br>
- * A parent container is considered "oversize" if its width is greater than its preferred width.<br>
- * <p>
- * The supplied number of cells to fill should be in the inclusive range of 0 to {@link #maxCellsToFill()}.<br>
- * Values outside this range will be clamped to the nearest valid value and a warning will be logged.
- *
- * @param span The number of cells to span as part of a grid
- * with a total of {@link #maxCellsToFill()} number of cells horizontally.
- * @return A new {@link FlowCellConf} instance with the given number of cells to fill
- * for the {@link ParentSizeClass#OVERSIZE} category.
- */
- public FlowCellConf oversize( int span ) {
- return with(ParentSizeClass.OVERSIZE, span);
- }
- boolean fill() {
- return _fill;
- }
- /**
- * This flag defines how the grid cell in the flow layout should be filled
- * by the component to which this cell configuration belongs.<br>
- * The default behavior is to not fill the cell, but rather to align the component
- * vertically in the center of the cell and use the component's preferred height.<br>
- * If this flag is set to {@code true}, the component will fill the cell vertically
- * and use the full height of the cell, which is also the full height of the row.<br>
- * Note that this will ignore the component's preferred height!
- *
- * @param fill Whether the cell should be filled by the component.
- * @return A new {@link FlowCellConf} instance with the given fill flag.
- */
- public FlowCellConf fill(boolean fill) {
- return new FlowCellConf(_maxCellsToFill, _parentSize, _parentSizeCategory, _autoSpans, fill, _verticalAlignment);
- }
- UI.VerticalAlignment verticalAlignment() {
- return _verticalAlignment;
- }
- /**
- * The {@link UI.VerticalAlignment} of a flow cell tells the
- * {@link ResponsiveGridFlowLayout} how to place the component
- * vertically within the cell.<br>
- * So if you want the component to be aligned at the top,
- * use {@link UI.VerticalAlignment#TOP}, if you want it to be
- * centered, use {@link UI.VerticalAlignment#CENTER}, and if you
- * want it to be aligned at the bottom, use {@link UI.VerticalAlignment#BOTTOM}.
- *
- * @param alignment The vertical alignment of the component within the cell.
- * @return A new {@link FlowCellConf} instance with the given vertical alignment policy.
- */
- public FlowCellConf align(UI.VerticalAlignment alignment) {
- return new FlowCellConf(_maxCellsToFill, _parentSize, _parentSizeCategory, _autoSpans, _fill, alignment);
- }
- @Override
- public String toString() {
- return this.getClass().getSimpleName() + "[" +
- "maxCellsToFill=" + _maxCellsToFill + ", " +
- "parentSize=" + _parentSize + ", " +
- "parentSizeCategory=" + _parentSizeCategory + ", " +
- "autoSpans=" + _autoSpans.length + ", " +
- "fill=" + _fill + ", " +
- "alignVertically=" + _verticalAlignment +
- "]";
- }
- @Override
- public boolean equals(Object obj) {
- if ( obj == this ) {
- return true;
- }
- if ( obj == null || obj.getClass() != this.getClass() ) {
- return false;
- }
- FlowCellConf that = (FlowCellConf)obj;
- return this._maxCellsToFill == that._maxCellsToFill
- && this._parentSize.equals(that._parentSize)
- && this._parentSizeCategory == that._parentSizeCategory
- && this._autoSpans.length == that._autoSpans.length
- && Arrays.deepEquals(this._autoSpans, that._autoSpans)
- && this._fill == that._fill
- && this._verticalAlignment == that._verticalAlignment;
- }
- @Override
- public int hashCode() {
- return Objects.hash(_maxCellsToFill, _parentSize, _parentSizeCategory, Arrays.hashCode(_autoSpans), _fill, _verticalAlignment);
- }
- }