# Module to calculate item positions for the Tk Grid geometry
# manager, returning the results as an anonymous AoA such that the
# data structure represents [ [ item 0 row, item0 column ], [ item 1
# row, item 1 column ] ... [ item n row, item n column ] ]. The Grid
# geometry manager numbers rows and columns from zero.
#
# Subroutine references are only created for fitting elements to a
# number of columns. When fitting to rows the same routines can be
# used but each pair of elements, "x" and "y" if you will, have to
# be reversed, see the two fitToRows.....() subroutines below.
#
# When arranging items to fit a certain number of columns and the
# items are ordered along the rows the algorithm is simple, keep
# filling rows until you run out of items, the last row might be
# short but that's fine.
#
# However, things get more complicated when fitting to columns and the
# order is down the columns as well. Just filling columns until you
# run out of items no longer works in all cases. For example, if we
# want to fit nine items to four columns we will need three rows (two
# times four is only eight) but filling columns willy-nilly means we
# run out of items before we get to the fourth column, leaving it
# empty. Instead we have to calculate how many columns will be full
# ones from items modulo columns, the number of rows being the
# truncated division of items by columns, with one row added if the
# modulo was positive.
#
# ==========
package GridLayout;
# ==========
use strict;
use warnings;
# Only integer maths required.
#
use integer;
# Set up Exporter to make subroutines available.
#
use Exporter qw{ import };
our @EXPORT_OK = qw{
fitToColsHSort
fitToColsVSort
fitToRowsHSort
fitToRowsVSort
};
our %EXPORT_TAGS = (
ALL => [ @EXPORT_OK ],
);
# Subroutine to calculate grid positions of elements that are to be
# fitted to a number of columns with the element order sorted
# vertically.
#
# -----------------
my $rcColsSortAligned = sub
# -----------------
{
# Get number of items and number of columns to fit them to then
#initialise the anonymous AoA tha will be returned.
#
my( $nItems, $colsToFit ) = @_;
my $raOrder = [];
# Calculate the number of rows required; we are using integer
# arithmetic so dividing number of items by number of columns
# gives an "at least" number for rows. However, if number of
# items modulo number of columns is positive then we need
# another row which will contain that number of full columns
# with the remaining columns being one item shorter. If not, all
# columns are full so set number of full columns to match columns
# to fit.
#
my $nRows = $nItems / $colsToFit;
my $nFullCols = $nItems % $colsToFit;
$nRows ++ if $nFullCols;
$nFullCols ||= $colsToFit;
# Populate the columns that are full, looping row within column.
#
foreach my $col ( 0 .. $nFullCols - 1 )
{
foreach my $row ( 0 .. $nRows - 1 )
{
push @{ $raOrder }, [ $row, $col ];
}
}
# If all columns are full columns then we are done, return the
# anonymous AoA.
#
return $raOrder if $nFullCols == $colsToFit;
# For the remaining columns populate all but the last row. Loop
# row within column again.
#
foreach my $col ( $nFullCols .. $colsToFit - 1 )
{
foreach my $row ( 0 .. $nRows - 2 )
{
push @{ $raOrder }, [ $row, $col ];
}
}
# Now all columns are populated we can return the anonymous AoA.
#
return $raOrder;
};
# Subroutine to calculate grid positions of elements that are to be
# fitted to a number of columns with the element order sorted
# horizontally.
#
# -----------------
my $rcColsSortOpposed = sub
# -----------------
{
# Get number of items and number of columns to fit them to then
#initialise the anonymous AoA that will be returned.
#
my( $nItems, $colsToFit ) = @_;
my $raOrder = [];
# When fitting items to, say, four columns the row number will
# be the truncated integer division of item number by number of
# columns. So, items 0, 1, 2 and 3 go into row 0, then 4, 5, 6
# and 7 into row 1 etc. The column position is simply the item
# number modulo the number of columns, cycling 0, 1, 2, 3, 0, 1,
# 2, 3 etc.
#
foreach my $item ( 0 .. ( $nItems - 1 ) )
{
push @{ $raOrder }, [
$item / $colsToFit,
$item % $colsToFit
];
}
# Now all columns are populated we can return the anonymous AoA.
#
return $raOrder;
};
# Exported subroutines
# ====================
#
# Fit $nItems items into $nCols columns with items ordered along
# the rows.
#
# --------------
sub fitToColsHSort
# --------------
{
my( $nItems, $nCols ) = @_;
# We are fitting to columns so the anonymous AoA returned by
# $rcColsSortOpposed->() is all that's needed.
#
return $rcColsSortOpposed->( $nItems, $nCols );
}
# Fit $nItems items into $nCols columns with items ordered down
# the columns.
#
# --------------
sub fitToColsVSort
# --------------
{
my( $nItems, $nCols ) = @_;
# We are fitting to columns so the anonymous AoA returned by
# $rcColsSortAligned->() is all that's needed.
#
return $rcColsSortAligned->( $nItems, $nCols );
}
# Fit $nItems items into $nRows rows with items ordered along
# the rows.
#
# --------------
sub fitToRowsHSort
# --------------
{
my( $nItems, $nRows ) = @_;
# We are fitting to rows so the anonymous AoA returned by
# $rcColsSortAligned->() has to be modified by swapping the
# row and column values for each item.
#
return [
map { [ reverse @{ $_ } ] }
@{ $rcColsSortAligned->( $nItems, $nRows ) }
];
}
# Fit $nItems items into $nRows rows with items ordered down
# the columns.
#
# --------------
sub fitToRowsVSort
# --------------
{
my( $nItems, $nRows ) = @_;
# We are fitting to rows so the anonymous AoA returned by
# $rcColsSortOpposed->() has to be modified by swapping the
# row and column values for each item.
#
return [
map { [ reverse @{ $_ } ] }
@{ $rcColsSortOpposed->( $nItems, $nRows ) }
];
}
1;