@Override public TerminalSize getPreferredSize(List<Component> components) { TerminalSize preferredSize = TerminalSize.ZERO; if (components.isEmpty()) { return preferredSize.withRelative( leftMarginSize + rightMarginSize, topMarginSize + bottomMarginSize); } Component[][] table = buildTable(components); table = eliminateUnusedRowsAndColumns(table); // Figure out each column first, this can be done independently of the row heights int preferredWidth = 0; int preferredHeight = 0; for (int width : getPreferredColumnWidths(table)) { preferredWidth += width; } for (int height : getPreferredRowHeights(table)) { preferredHeight += height; } preferredSize = preferredSize.withRelative(preferredWidth, preferredHeight); preferredSize = preferredSize.withRelativeColumns( leftMarginSize + rightMarginSize + (table[0].length - 1) * horizontalSpacing); preferredSize = preferredSize.withRelativeRows( topMarginSize + bottomMarginSize + (table.length - 1) * verticalSpacing); return preferredSize; }
@Override public TextGraphics drawImage( TerminalPosition topLeft, TextImage image, TerminalPosition sourceImageTopLeft, TerminalSize sourceImageSize) { // If the source image position is negative, offset the whole image if (sourceImageTopLeft.getColumn() < 0) { topLeft = topLeft.withRelativeColumn(-sourceImageTopLeft.getColumn()); sourceImageSize = sourceImageSize.withRelativeColumns(sourceImageTopLeft.getColumn()); sourceImageTopLeft = sourceImageTopLeft.withColumn(0); } if (sourceImageTopLeft.getRow() < 0) { topLeft = topLeft.withRelativeRow(-sourceImageTopLeft.getRow()); sourceImageSize = sourceImageSize.withRelativeRows(sourceImageTopLeft.getRow()); sourceImageTopLeft = sourceImageTopLeft.withRow(0); } // cropping specified image-subrectangle to the image itself: int fromRow = Math.max(sourceImageTopLeft.getRow(), 0); int untilRow = Math.min( sourceImageTopLeft.getRow() + sourceImageSize.getRows(), image.getSize().getRows()); int fromColumn = Math.max(sourceImageTopLeft.getColumn(), 0); int untilColumn = Math.min( sourceImageTopLeft.getColumn() + sourceImageSize.getColumns(), image.getSize().getColumns()); // difference between position in image and position on target: int diffRow = topLeft.getRow() - sourceImageTopLeft.getRow(); int diffColumn = topLeft.getColumn() - sourceImageTopLeft.getColumn(); // top/left-crop at target(TextGraphics) rectangle: (only matters, if topLeft has a negative // coordinate) fromRow = Math.max(fromRow, -diffRow); fromColumn = Math.max(fromColumn, -diffColumn); // bot/right-crop at target(TextGraphics) rectangle: (only matters, if topLeft has a negative // coordinate) untilRow = Math.min(untilRow, getSize().getRows() - diffRow); untilColumn = Math.min(untilColumn, getSize().getColumns() - diffColumn); if (fromRow >= untilRow || fromColumn >= untilColumn) { return this; } for (int row = fromRow; row < untilRow; row++) { for (int column = fromColumn; column < untilColumn; column++) { setCharacter(column + diffColumn, row + diffRow, image.getCharacterAt(column, row)); } } return this; }
@Override public void doLayout(TerminalSize area, List<Component> components) { // Sanity check, if the area is way too small, just return Component[][] table = buildTable(components); table = eliminateUnusedRowsAndColumns(table); if (area.equals(TerminalSize.ZERO) || table.length == 0 || area.getColumns() <= leftMarginSize + rightMarginSize + ((table[0].length - 1) * horizontalSpacing) || area.getRows() <= bottomMarginSize + topMarginSize + ((table.length - 1) * verticalSpacing)) { return; } // Adjust area to the margins area = area.withRelative(-leftMarginSize - rightMarginSize, -topMarginSize - bottomMarginSize); Map<Component, TerminalSize> sizeMap = new IdentityHashMap<Component, TerminalSize>(); Map<Component, TerminalPosition> positionMap = new IdentityHashMap<Component, TerminalPosition>(); // Figure out each column first, this can be done independently of the row heights int[] columnWidths = getPreferredColumnWidths(table); // Take notes of which columns we can expand if the usable area is larger than what the // components want Set<Integer> expandableColumns = getExpandableColumns(table); // Next, start shrinking to make sure it fits the size of the area we are trying to lay out on. // Notice we subtract the horizontalSpacing to take the space between components into account TerminalSize areaWithoutHorizontalSpacing = area.withRelativeColumns(-horizontalSpacing * (table[0].length - 1)); int totalWidth = shrinkWidthToFitArea(areaWithoutHorizontalSpacing, columnWidths); // Finally, if there is extra space, make the expandable columns larger while (areaWithoutHorizontalSpacing.getColumns() > totalWidth && !expandableColumns.isEmpty()) { totalWidth = grabExtraHorizontalSpace( areaWithoutHorizontalSpacing, columnWidths, expandableColumns, totalWidth); } // Now repeat for rows int[] rowHeights = getPreferredRowHeights(table); Set<Integer> expandableRows = getExpandableRows(table); TerminalSize areaWithoutVerticalSpacing = area.withRelativeRows(-verticalSpacing * (table.length - 1)); int totalHeight = shrinkHeightToFitArea(areaWithoutVerticalSpacing, rowHeights); while (areaWithoutVerticalSpacing.getRows() > totalHeight && !expandableRows.isEmpty()) { totalHeight = grabExtraVerticalSpace( areaWithoutVerticalSpacing, rowHeights, expandableRows, totalHeight); } // Ok, all constraints are in place, we can start placing out components. To simplify, do it // horizontally first // and vertically after TerminalPosition tableCellTopLeft = TerminalPosition.TOP_LEFT_CORNER; for (int y = 0; y < table.length; y++) { tableCellTopLeft = tableCellTopLeft.withColumn(0); for (int x = 0; x < table[y].length; x++) { Component component = table[y][x]; if (component != null && !positionMap.containsKey(component)) { GridLayoutData layoutData = getLayoutData(component); TerminalSize size = component.getPreferredSize(); TerminalPosition position = tableCellTopLeft; int availableHorizontalSpace = 0; int availableVerticalSpace = 0; for (int i = 0; i < layoutData.horizontalSpan; i++) { availableHorizontalSpace += columnWidths[x + i] + (i > 0 ? horizontalSpacing : 0); } for (int i = 0; i < layoutData.verticalSpan; i++) { availableVerticalSpace += rowHeights[y + i] + (i > 0 ? verticalSpacing : 0); } // Make sure to obey the size restrictions size = size.withColumns(Math.min(size.getColumns(), availableHorizontalSpace)); size = size.withRows(Math.min(size.getRows(), availableVerticalSpace)); switch (layoutData.horizontalAlignment) { case CENTER: position = position.withRelativeColumn((availableHorizontalSpace - size.getColumns()) / 2); break; case END: position = position.withRelativeColumn(availableHorizontalSpace - size.getColumns()); break; case FILL: size = size.withColumns(availableHorizontalSpace); break; default: break; } switch (layoutData.verticalAlignment) { case CENTER: position = position.withRelativeRow((availableVerticalSpace - size.getRows()) / 2); break; case END: position = position.withRelativeRow(availableVerticalSpace - size.getRows()); break; case FILL: size = size.withRows(availableVerticalSpace); break; default: break; } sizeMap.put(component, size); positionMap.put(component, position); } tableCellTopLeft = tableCellTopLeft.withRelativeColumn(columnWidths[x] + horizontalSpacing); } tableCellTopLeft = tableCellTopLeft.withRelativeRow(rowHeights[y] + verticalSpacing); } // Apply the margins here for (Component component : components) { component.setPosition(positionMap.get(component).withRelative(leftMarginSize, topMarginSize)); component.setSize(sizeMap.get(component)); } this.changed = false; }