Class UniformGridLayout

java.lang.Object
swingtree.layout.UniformGridLayout
All Implemented Interfaces:
LayoutManager

public final class UniformGridLayout extends Object implements LayoutManager
A layout manager which divides a container into a grid of cells of equal size and puts every component of the container into a cell of its own. The components fill the cells row by row, starting with the top row, and every component is resized to exactly the size of its cell, whatever size it would prefer.

In a SwingTree UI declaration, you install it through UIForAnySwing.withGridLayout(int, int, int, int):


  UI.panel().withGridLayout(2, 3, 5, 5)
  .add(UI.button("1")).add(UI.button("2")).add(UI.button("3"))
  .add(UI.button("4")).add(UI.button("5"))
  
This panel has a grid of 2 rows and 3 columns, with a gap of 5 pixels between neighbouring cells. The buttons 1, 2 and 3 fill the top row from left to right, the buttons 4 and 5 take the first two cells of the bottom row, and the last cell of the bottom row stays empty. If the panel only held the buttons 1 and 2, they would sit side by side in a single row of 2 columns, as high as the whole panel, because by default the rows and columns which no component occupies are left out of the grid.

In the style API and in a reactive Var<Layout>, Layout.grid(int, int, int, int) installs the same layout manager, and both withGridLayout(..) and Layout.grid(..) accept a UniformGridLayout.Mode, a UniformGridLayout.CollapseEmpty and an UniformGridLayout.OverflowGrowth setting in front of the numbers of rows and columns. Outside of a SwingTree UI declaration, you install it like any other layout manager: panel.setLayout(new UniformGridLayout(2, 3, 5, 5)), or with every setting spelled out through UniformGridLayout(Mode, CollapseEmpty, OverflowGrowth, int, int, int, int).

How the grid is built

You declare a number of rows and a number of columns, and three settings decide what the layout makes of them: A number of rows of 0 means "as many rows as the components need", and a number of columns of 0 means "as many columns as the components need". So 10 components in a grid of 0 rows and 4 columns are laid out in 3 rows, and 10 components in a grid of 2 rows and 0 columns are laid out in 5 columns. The two numbers cannot both be 0, and negative numbers are not supported.

This is how a grid which is declared with 2 rows and 5 columns lays out 3, 8 and 11 components, with the default UniformGridLayout.OverflowGrowth.ADD_ROWS:

Rows × columns laid out for a grid declared with 2 rows and 5 columns
ModeCollapseEmpty3 components8 components11 components
WRAP_AFTER_COLUMNSROWS_AND_COLUMNS1 × 32 × 53 × 5
WRAP_AFTER_COLUMNSCOLUMNS 2 × 32 × 53 × 5
WRAP_AFTER_COLUMNSROWS 1 × 52 × 53 × 5
WRAP_AFTER_COLUMNSNONE 2 × 52 × 53 × 5
SPREAD_OVER_ROWS any of them 2 × 22 × 43 × 5
Only the last column of that table holds more components than the declared 10 cells, so only there does the UniformGridLayout.OverflowGrowth setting make a difference, and there it makes the same difference in both modes: once a grid overflows, the mode has nothing left to say and the growth policy alone decides the shape. This is how the same grid of 2 rows and 5 columns grows for 11, 13, 20 and 30 components, in either mode and whichever rows and columns it collapses:
Rows × columns laid out for an overflowing grid declared with 2 rows and 5 columns
OverflowGrowth11 components13 components20 components30 components
ADD_ROWS 3 × 53 × 54 × 5 6 × 5
ADD_COLUMNS 2 × 62 × 72 × 102 × 15
ADD_ROWS_AND_COLUMNS2 × 63 × 63 × 7 4 × 9
  • The default settings leave no row and no column empty. 3 components share the whole container in a single row, and as soon as there are enough components to fill a row, the grid has exactly the declared number of columns. While there are fewer components than declared columns, every added component makes the cells narrower. Because empty rows are left out, the declared number of rows only makes a difference while the declared number of columns is 0.
  • With UniformGridLayout.CollapseEmpty.NONE, the cells of a grid of 2 rows and 5 columns have the same size whether the grid holds 1 component or 10, and two such grids of the same size have cells which line up. With UniformGridLayout.CollapseEmpty.ROWS, the cells keep their width while components are added, but the grid only has as many rows as the components fill.
  • In the mode UniformGridLayout.Mode.SPREAD_OVER_ROWS, an added component can change the width of every cell. This mode never leaves a column empty, so collapsing empty columns moves no component, but it can leave rows empty: 2 components in a grid of 3 rows get 1 column and only fill 2 of the 3 rows, and UniformGridLayout.CollapseEmpty.ROWS leaves out the third one. At a UI scale factor of 1, a UniformGridLayout in this mode, with UniformGridLayout.CollapseEmpty.NONE and UniformGridLayout.OverflowGrowth.ADD_COLUMNS, lays out and measures a container exactly like a GridLayout with the same numbers of rows and columns and the same gaps.
You choose the settings with setMode(Mode), setCollapseEmpty(CollapseEmpty) and setOverflowGrowth(OverflowGrowth).

The size of the cells

All cells have the same size. To find their width, the layout manager takes the width of the container, subtracts the left and right insets and the horizontal gaps between the columns, divides the rest by the number of columns and rounds the result down. The few pixels which that rounding leaves over are split in two: half of them, rounded down, come before the first column, and the rest come after the last column, which centres the grid inside the insets.

For example, a container 105 pixels wide without insets, with 3 columns and a horizontal gap of 5 pixels, has cells which are (105 − 2 × 5) / 3 = 31.67 pixels wide, rounded down to 31. The cells and the gaps take up 3 × 31 + 2 × 5 = 103 pixels, and of the 2 pixels left over, 1 comes before the first column, so the first column starts at x = 1. The height of the cells is found in the same way, from the height of the container, the top and bottom insets, the vertical gaps and the number of rows.

A few more details follow from how the cells are filled:

  • A component which is not visible still has its cell, so hiding a component leaves an empty cell behind, moves none of the other components, and does not make a row or column empty enough to be left out.
  • In a container with a right-to-left ComponentOrientation, every row is filled from right to left, so the first component of a row is in its rightmost cell. The rows are still filled from the top.
  • The preferred, minimum and maximum sizes of the components play no part in laying them out. The preferred and minimum sizes only matter for preferredLayoutSize(Container) and minimumLayoutSize(Container).
  • When a container is too small for its insets and gaps, its cells end up with a width or a height of 0 or less.

Gaps and the UI scale factor

The horizontal gap is the space between two neighbouring columns, and the vertical gap is the space between two neighbouring rows. There is no gap between the outer cells and the insets of the container, so if you want space around the grid, give the container a border, for example through a padding in its SwingTree style.

You declare the gaps in pixels at a UI scale factor of 1. Every time the layout manager lays out or measures a container, it scales the gaps with UI.scale(int), which rounds to the nearest whole pixel. So a gap of 5 is 10 pixels wide at a UI scale factor of 2 and 6 pixels wide at a UI scale factor of 1.25, and a change of the scale factor takes effect the next time the container is laid out. getHgap() and getVgap() return the gaps as you declared them.

  • Constructor Details

    • UniformGridLayout

      public UniformGridLayout()
      Creates a grid layout with a single row, a column for every component and no gaps, which places the components of the container side by side in cells of equal width. This is the same as new UniformGridLayout(1, 0, 0, 0).
    • UniformGridLayout

      public UniformGridLayout(int rows, int cols)
      Creates a grid layout with the given number of rows and columns and no gaps between the cells, in the mode UniformGridLayout.Mode.WRAP_AFTER_COLUMNS, which collapses UniformGridLayout.CollapseEmpty.ROWS_AND_COLUMNS. A number of 0 means "as many as the components need", so new UniformGridLayout(0, 3) arranges the components in 3 columns and as many rows as it takes to hold all of them, and arranges 2 components in a single row of 2 columns.
      Parameters:
      rows - The number of rows, or 0 for as many rows as the components need.
      cols - The number of columns, or 0 for as many columns as the components need.
      Throws:
      IllegalArgumentException - If both rows and cols are 0.
    • UniformGridLayout

      public UniformGridLayout(int rows, int cols, int hgap, int vgap)
      Creates a grid layout with the given number of rows and columns and the given gaps between neighbouring cells, in the mode UniformGridLayout.Mode.WRAP_AFTER_COLUMNS, which collapses UniformGridLayout.CollapseEmpty.ROWS_AND_COLUMNS. A number of 0 means "as many as the components need", so new UniformGridLayout(2, 0, 5, 5) spreads the components over 2 rows, in as many columns as it takes to hold all of them, with 5 pixels between neighbouring cells, and leaves out the second row for a single component. The gaps are scaled with UI.scale(int) every time the container is laid out or measured.
      Parameters:
      rows - The number of rows, or 0 for as many rows as the components need.
      cols - The number of columns, or 0 for as many columns as the components need.
      hgap - The space between two neighbouring columns, in pixels at a UI scale factor of 1.
      vgap - The space between two neighbouring rows, in pixels at a UI scale factor of 1.
      Throws:
      IllegalArgumentException - If both rows and cols are 0.
    • UniformGridLayout

      public UniformGridLayout(UniformGridLayout.Mode mode, UniformGridLayout.CollapseEmpty collapseEmpty, UniformGridLayout.OverflowGrowth overflowGrowth, int rows, int cols, int hgap, int vgap)
      Creates a grid layout with every one of its settings spelled out: how the grid is built from the given numbers of rows and columns, how it grows when it holds more components than cells, which of its empty rows and columns it leaves out, and the gaps between neighbouring cells. The other constructors leave the three settings at UniformGridLayout.Mode.WRAP_AFTER_COLUMNS, UniformGridLayout.CollapseEmpty.ROWS_AND_COLUMNS and UniformGridLayout.OverflowGrowth.ADD_ROWS.

      For example, new UniformGridLayout(Mode.WRAP_AFTER_COLUMNS, CollapseEmpty.NONE, OverflowGrowth.ADD_COLUMNS, 2, 5, 5, 5) keeps all 10 cells of a grid of 2 rows and 5 columns for 3 components, and widens the grid to 2 rows of 7 columns for 13 components, with 5 pixels between neighbouring cells at a UI scale factor of 1.

      Parameters:
      mode - How the grid is built from the numbers of rows and columns.
      collapseEmpty - Which of the rows and columns that no component occupies are left out.
      overflowGrowth - How the grid grows when it holds more components than cells.
      rows - The number of rows, or 0 for as many rows as the components need.
      cols - The number of columns, or 0 for as many columns as the components need.
      hgap - The space between two neighbouring columns, in pixels at a UI scale factor of 1.
      vgap - The space between two neighbouring rows, in pixels at a UI scale factor of 1.
      Throws:
      IllegalArgumentException - If both rows and cols are 0.
      NullPointerException - If mode, collapseEmpty or overflowGrowth is null.
  • Method Details

    • getRows

      public int getRows()
      Returns the number of rows you gave this layout, where 0 means "as many rows as the components need". The container can be laid out in a different number of rows: in the mode UniformGridLayout.Mode.WRAP_AFTER_COLUMNS, rows are added when there are more components than cells, and when empty rows are collapsed, the rows which no component reaches are left out.
      Returns:
      The declared number of rows, which may be 0.
    • setRows

      public void setRows(int rows)
      Sets the number of rows of the grid, where 0 means "as many rows as the components need". In the mode UniformGridLayout.Mode.WRAP_AFTER_COLUMNS with empty rows collapsed, which are the default settings, this number only makes a difference while the number of columns is 0.

      The number of rows and the number of columns cannot both be 0, and this method checks that against the number of columns this layout has at the moment it is called. So to switch a layout of 0 rows and 3 columns to 2 rows and 0 columns, call setRows(2) first and setColumns(0) second, and to switch it back, call setColumns(3) first and setRows(0) second.

      This method does not lay out the container again. Call Component.revalidate() on the container to apply the new number.

      Parameters:
      rows - The number of rows, or 0 for as many rows as the components need.
      Throws:
      IllegalArgumentException - If rows is 0 while the number of columns is 0 as well.
    • getColumns

      public int getColumns()
      Returns the number of columns you gave this layout, where 0 means "as many columns as the components need". The container can be laid out in a different number of columns: in the mode UniformGridLayout.Mode.SPREAD_OVER_ROWS, the number of columns is worked out from the number of rows whenever that is greater than 0, and when empty columns are collapsed, the grid never has more columns than components.
      Returns:
      The declared number of columns, which may be 0.
    • setColumns

      public void setColumns(int cols)
      Sets the number of columns of the grid, where 0 means "as many columns as the components need". In the mode UniformGridLayout.Mode.SPREAD_OVER_ROWS, this number is ignored while the number of rows is greater than 0, and when empty columns are collapsed, the grid never has more columns than components.

      The number of rows and the number of columns cannot both be 0, and this method checks that against the number of rows this layout has at the moment it is called. So to switch a layout of 2 rows and 0 columns to 0 rows and 3 columns, call setColumns(3) first and setRows(0) second, and to switch it back, call setRows(2) first and setColumns(0) second.

      This method does not lay out the container again. Call Component.revalidate() on the container to apply the new number.

      Parameters:
      cols - The number of columns, or 0 for as many columns as the components need.
      Throws:
      IllegalArgumentException - If cols is 0 while the number of rows is 0 as well.
    • getMode

      public UniformGridLayout.Mode getMode()
      Returns the mode which decides how the grid is built from the declared numbers of rows and columns. See UniformGridLayout.Mode for what each mode does.
      Returns:
      The mode of this layout, which is UniformGridLayout.Mode.WRAP_AFTER_COLUMNS unless you changed it.
    • setMode

      public void setMode(UniformGridLayout.Mode mode)
      Sets the mode which decides how the grid is built from the declared numbers of rows and columns, for as long as it still has a cell for every component: A grid of 2 rows and 5 columns builds 2 rows of 5 columns for 8 components in the first mode, and 2 rows of 4 columns in the second. Once a grid holds more components than its declared numbers multiply to, the mode no longer decides its shape and setOverflowGrowth(OverflowGrowth) does. The layout manager reads the mode every time it lays out or measures the container, but this method does not lay out the container again. Call Component.revalidate() on the container to apply the new mode.
      Parameters:
      mode - The mode of this layout.
      Throws:
      NullPointerException - If mode is null, in which case the mode stays as it was.
    • getCollapseEmpty

      public UniformGridLayout.CollapseEmpty getCollapseEmpty()
      Returns the setting which decides which of the rows and columns that no component occupies are left out of the grid. See UniformGridLayout.CollapseEmpty for what each setting does.
      Returns:
      The setting of this layout, which is UniformGridLayout.CollapseEmpty.ROWS_AND_COLUMNS unless you changed it.
    • setCollapseEmpty

      public void setCollapseEmpty(UniformGridLayout.CollapseEmpty collapseEmpty)
      Sets which of the rows and columns that no component occupies are left out of the grid: UniformGridLayout.CollapseEmpty.NONE, UniformGridLayout.CollapseEmpty.COLUMNS, UniformGridLayout.CollapseEmpty.ROWS, or UniformGridLayout.CollapseEmpty.ROWS_AND_COLUMNS, which is the default. In the mode UniformGridLayout.Mode.WRAP_AFTER_COLUMNS, a grid of 2 rows and 5 columns lays out 3 components in 2 × 5, 2 × 3, 1 × 5 and 1 × 3 rows × columns with these settings.

      The layout manager reads the setting every time it lays out or measures the container, but this method does not lay out the container again. Call Component.revalidate() on the container to apply the new setting.

      Parameters:
      collapseEmpty - Which empty rows and columns this layout leaves out.
      Throws:
      NullPointerException - If collapseEmpty is null, in which case the setting stays as it was.
    • getOverflowGrowth

      public UniformGridLayout.OverflowGrowth getOverflowGrowth()
      Returns the setting which decides how the grid grows when the container holds more components than cells. See UniformGridLayout.OverflowGrowth for what each setting does, and for the grids which can overflow at all.
      Returns:
      The setting of this layout, which is UniformGridLayout.OverflowGrowth.ADD_ROWS unless you changed it.
    • setOverflowGrowth

      public void setOverflowGrowth(UniformGridLayout.OverflowGrowth overflowGrowth)
      Sets how the grid grows when the container holds more components than the declared number of rows times the declared number of columns: UniformGridLayout.OverflowGrowth.ADD_ROWS, which is the default, UniformGridLayout.OverflowGrowth.ADD_COLUMNS, or UniformGridLayout.OverflowGrowth.ADD_ROWS_AND_COLUMNS, which keeps the ratio of rows to columns as close to the declared ratio as whole rows and columns allow. A grid of 2 rows and 5 columns lays out 13 components in 3 × 5, 2 × 7 and 3 × 6 rows × columns with these settings.

      This setting makes a difference in both modes, but only while the declared numbers of rows and columns are both greater than 0, because a grid with a number of 0 grows that axis as far as the components need and can therefore never hold more components than it has cells.

      The layout manager reads the setting every time it lays out or measures the container, but this method does not lay out the container again. Call Component.revalidate() on the container to apply the new setting.

      Parameters:
      overflowGrowth - How this layout grows a grid which holds more components than cells.
      Throws:
      NullPointerException - If overflowGrowth is null, in which case the setting stays as it was.
    • getHgap

      public int getHgap()
      Returns the space between two neighbouring columns as you declared it, in pixels at a UI scale factor of 1. In a laid out container, the space is this number scaled with UI.scale(int).
      Returns:
      The declared horizontal gap.
    • setHgap

      public void setHgap(int hgap)
      Sets the space between two neighbouring columns, in pixels at a UI scale factor of 1. The layout manager scales it with UI.scale(int) every time it lays out or measures the container, and puts no gap between the outer columns and the insets of the container. A negative gap is not rejected, and makes neighbouring cells overlap.

      This method does not lay out the container again. Call Component.revalidate() on the container to apply the new gap.

      Parameters:
      hgap - The horizontal gap, in pixels at a UI scale factor of 1.
    • getVgap

      public int getVgap()
      Returns the space between two neighbouring rows as you declared it, in pixels at a UI scale factor of 1. In a laid out container, the space is this number scaled with UI.scale(int).
      Returns:
      The declared vertical gap.
    • setVgap

      public void setVgap(int vgap)
      Sets the space between two neighbouring rows, in pixels at a UI scale factor of 1. The layout manager scales it with UI.scale(int) every time it lays out or measures the container, and puts no gap between the outer rows and the insets of the container. A negative gap is not rejected, and makes neighbouring cells overlap.

      This method does not lay out the container again. Call Component.revalidate() on the container to apply the new gap.

      Parameters:
      vgap - The vertical gap, in pixels at a UI scale factor of 1.
    • addLayoutComponent

      public void addLayoutComponent(String name, Component comp)
      Does nothing. This layout manager keeps no information about individual components: every time it lays out or measures a container, it asks the container for its components and places them in the order of their index in the container.
      Specified by:
      addLayoutComponent in interface LayoutManager
      Parameters:
      name - The name the component was added with, which this layout manager ignores.
      comp - The component which was added to the container.
    • removeLayoutComponent

      public void removeLayoutComponent(Component comp)
      Does nothing. This layout manager keeps no information about individual components, so a removed component simply no longer takes a cell the next time the container is laid out.
      Specified by:
      removeLayoutComponent in interface LayoutManager
      Parameters:
      comp - The component which was removed from the container.
    • preferredLayoutSize

      public Dimension preferredLayoutSize(Container parent)
      Computes the size a container needs to give every component a cell as wide as the widest preferred width, and as tall as the tallest preferred height, among its components.

      The widest preferred width and the tallest preferred height may belong to two different components, and components which are not visible count as well. The preferred width of the container is the cell width times the number of columns, plus the horizontal gaps between the columns, plus the left and right insets. The preferred height is computed in the same way from the cell height, the number of rows, the vertical gaps and the top and bottom insets. The gaps are scaled with UI.scale(int).

      For example, 3 components which each prefer 30 × 20 pixels, in a grid of 2 rows and 5 columns with gaps of 5 pixels, at a UI scale factor of 1 and in a container without insets, prefer

      The numbers of rows and columns are worked out exactly like layoutContainer(Container) works them out, so a container laid out at its preferred size gives every component a cell of exactly that width and height.

      In a container without components, a number of rows or columns which is worked out from the components is 0, and the gaps between 0 cells add up to minus one gap: an empty container with a grid of 2 rows and 0 columns, a horizontal gap of 5 pixels and UniformGridLayout.CollapseEmpty.NONE prefers a width of −5 pixels, exactly like it would with a GridLayout. A number of rows or columns which is collapsed is 1 instead, so with the default settings, an empty container prefers exactly the size of its insets.

      Specified by:
      preferredLayoutSize in interface LayoutManager
      Parameters:
      parent - The container to compute the preferred size for.
      Returns:
      The preferred size of parent, including its insets.
    • minimumLayoutSize

      public Dimension minimumLayoutSize(Container parent)
      Computes the size a container needs to give every component a cell as wide as the widest minimum width, and as tall as the tallest minimum height, among its components.

      The widest minimum width and the tallest minimum height may belong to two different components, and components which are not visible count as well. The minimum width of the container is the cell width times the number of columns, plus the horizontal gaps between the columns, plus the left and right insets. The minimum height is computed in the same way from the cell height, the number of rows, the vertical gaps and the top and bottom insets. The gaps are scaled with UI.scale(int), and the numbers of rows and columns are worked out exactly like layoutContainer(Container) works them out.

      layoutContainer(Container) does not enforce this size. A container which is smaller is laid out all the same, and then at least one of its components gets a cell smaller than its minimum size.

      Specified by:
      minimumLayoutSize in interface LayoutManager
      Parameters:
      parent - The container to compute the minimum size for.
      Returns:
      The minimum size of parent, including its insets.
    • layoutContainer

      public void layoutContainer(Container parent)
      Moves and resizes every component of the container into its cell of the grid.

      The grid has the declared numbers of rows and columns, with these exceptions:

      • A number of rows or columns of 0 is worked out from the number of components.
      • In the mode UniformGridLayout.Mode.SPREAD_OVER_ROWS, while the number of rows is greater than 0, the number of columns is worked out from the number of rows and the number of components.
      • When there are more components than the declared number of rows times the declared number of columns, the grid grows on the axis the UniformGridLayout.OverflowGrowth setting names, in either mode.
      • The rows and columns which no component occupies are then left out, as far as the UniformGridLayout.CollapseEmpty setting says so.
      All cells are equally large. Their width is the width inside the insets, minus the horizontal gaps scaled with UI.scale(int), divided by the number of columns and rounded down, and their height is found in the same way from the rows. The pixels which the rounding leaves over are split: half of them, rounded down, come before the first column and above the first row.

      Every component, visible or not, is given exactly the bounds of its cell. The cells are filled row by row from the top, and within a row from left to right, or from right to left in a container with a right-to-left ComponentOrientation. A container without components is left untouched.

      Specified by:
      layoutContainer in interface LayoutManager
      Parameters:
      parent - The container whose components are laid out.
    • toString

      public String toString()
      Returns the declared settings of this layout manager in a line of text, for example swingtree.layout.UniformGridLayout[hgap=5,vgap=5,rows=2,cols=5,mode=WRAP_AFTER_COLUMNS,collapseEmpty=ROWS_AND_COLUMNS,overflowGrowth=ADD_ROWS]. The gaps in it are the declared gaps, not the scaled ones.
      Overrides:
      toString in class Object
      Returns:
      A text with the class name, the gaps, the numbers of rows and columns, the mode, which empty rows and columns are left out, and how the grid grows when it overflows.