# 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;