/*
* This file is part of lanterna (http://code.google.com/p/lanterna/).
*
* lanterna is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
* Copyright (C) 2010-2015 Martin
*/
package com.googlecode.lanterna.gui2;
import com.googlecode.lanterna.TerminalPosition;
import com.googlecode.lanterna.TerminalSize;
import java.util.*;
/**
* BorderLayout imitates the BorderLayout class from AWT, allowing you to add a center component with optional
* components around it in top, bottom, left and right locations. The edge components will be sized at their preferred
* size and the center component will take up whatever remains.
* @author martin
*/
public class BorderLayout implements LayoutManager {
/**
* This type is what you use as the layout data for components added to a panel using {@code BorderLayout} for its
* layout manager. This values specified where inside the panel the component should be added.
*/
public enum Location implements LayoutData {
/**
* The component with this value as its layout data will occupy the center space, whatever is remaining after
* the other components (if any) have allocated their space.
*/
CENTER,
/**
* The component with this value as its layout data will occupy the left side of the container, attempting to
* allocate the preferred width of the component and at least the preferred height, but could be more depending
* on the other components added.
*/
LEFT,
/**
* The component with this value as its layout data will occupy the right side of the container, attempting to
* allocate the preferred width of the component and at least the preferred height, but could be more depending
* on the other components added.
*/
RIGHT,
/**
* The component with this value as its layout data will occupy the top side of the container, attempting to
* allocate the preferred height of the component and at least the preferred width, but could be more depending
* on the other components added.
*/
TOP,
/**
* The component with this value as its layout data will occupy the bottom side of the container, attempting to
* allocate the preferred height of the component and at least the preferred width, but could be more depending
* on the other components added.
*/
BOTTOM,
;
}
//When components don't have a location, we'll assign an available location based on this order
private static final List AUTO_ASSIGN_ORDER = Collections.unmodifiableList(Arrays.asList(
Location.CENTER,
Location.TOP,
Location.BOTTOM,
Location.LEFT,
Location.RIGHT));
@Override
public TerminalSize getPreferredSize(List components) {
EnumMap layout = makeLookupMap(components);
int preferredHeight =
(layout.containsKey(Location.TOP) ? layout.get(Location.TOP).getPreferredSize().getRows() : 0)
+
Math.max(
layout.containsKey(Location.LEFT) ? layout.get(Location.LEFT).getPreferredSize().getRows() : 0,
Math.max(
layout.containsKey(Location.CENTER) ? layout.get(Location.CENTER).getPreferredSize().getRows() : 0,
layout.containsKey(Location.RIGHT) ? layout.get(Location.RIGHT).getPreferredSize().getRows() : 0))
+
(layout.containsKey(Location.BOTTOM) ? layout.get(Location.BOTTOM).getPreferredSize().getRows() : 0);
int preferredWidth =
Math.max(
(layout.containsKey(Location.LEFT) ? layout.get(Location.LEFT).getPreferredSize().getColumns() : 0) +
(layout.containsKey(Location.CENTER) ? layout.get(Location.CENTER).getPreferredSize().getColumns() : 0) +
(layout.containsKey(Location.RIGHT) ? layout.get(Location.RIGHT).getPreferredSize().getColumns() : 0),
Math.max(
layout.containsKey(Location.TOP) ? layout.get(Location.TOP).getPreferredSize().getColumns() : 0,
layout.containsKey(Location.BOTTOM) ? layout.get(Location.BOTTOM).getPreferredSize().getColumns() : 0));
return new TerminalSize(preferredWidth, preferredHeight);
}
@Override
public void doLayout(TerminalSize area, List components) {
EnumMap layout = makeLookupMap(components);
int availableHorizontalSpace = area.getColumns();
int availableVerticalSpace = area.getRows();
//We'll need this later on
int topComponentHeight = 0;
int leftComponentWidth = 0;
//First allocate the top
if(layout.containsKey(Location.TOP)) {
Component topComponent = layout.get(Location.TOP);
topComponentHeight = Math.min(topComponent.getPreferredSize().getRows(), availableVerticalSpace);
topComponent.setPosition(TerminalPosition.TOP_LEFT_CORNER);
topComponent.setSize(new TerminalSize(availableHorizontalSpace, topComponentHeight));
availableVerticalSpace -= topComponentHeight;
}
//Next allocate the bottom
if(layout.containsKey(Location.BOTTOM)) {
Component bottomComponent = layout.get(Location.BOTTOM);
int bottomComponentHeight = Math.min(bottomComponent.getPreferredSize().getRows(), availableVerticalSpace);
bottomComponent.setPosition(new TerminalPosition(0, area.getRows() - bottomComponentHeight));
bottomComponent.setSize(new TerminalSize(availableHorizontalSpace, bottomComponentHeight));
availableVerticalSpace -= bottomComponentHeight;
}
//Now divide the remaining space between LEFT, CENTER and RIGHT
if(layout.containsKey(Location.LEFT)) {
Component leftComponent = layout.get(Location.LEFT);
leftComponentWidth = Math.min(leftComponent.getPreferredSize().getColumns(), availableHorizontalSpace);
leftComponent.setPosition(new TerminalPosition(0, topComponentHeight));
leftComponent.setSize(new TerminalSize(leftComponentWidth, availableVerticalSpace));
availableHorizontalSpace -= leftComponentWidth;
}
if(layout.containsKey(Location.RIGHT)) {
Component rightComponent = layout.get(Location.RIGHT);
int rightComponentWidth = Math.min(rightComponent.getPreferredSize().getColumns(), availableHorizontalSpace);
rightComponent.setPosition(new TerminalPosition(area.getColumns() - rightComponentWidth, topComponentHeight));
rightComponent.setSize(new TerminalSize(rightComponentWidth, availableVerticalSpace));
availableHorizontalSpace -= rightComponentWidth;
}
if(layout.containsKey(Location.CENTER)) {
Component centerComponent = layout.get(Location.CENTER);
centerComponent.setPosition(new TerminalPosition(leftComponentWidth, topComponentHeight));
centerComponent.setSize(new TerminalSize(availableHorizontalSpace, availableVerticalSpace));
}
//Set the remaining components to 0x0
for(Component component: components) {
if(!layout.values().contains(component)) {
component.setPosition(TerminalPosition.TOP_LEFT_CORNER);
component.setSize(TerminalSize.ZERO);
}
}
}
private EnumMap makeLookupMap(List components) {
EnumMap map = new EnumMap(Location.class);
List unassignedComponents = new ArrayList();
for(Component component: components) {
if(component.getLayoutData() instanceof Location) {
map.put((Location)component.getLayoutData(), component);
}
else {
unassignedComponents.add(component);
}
}
//Try to assign components to available locations
for(Component component: unassignedComponents) {
for(Location location: AUTO_ASSIGN_ORDER) {
if(!map.containsKey(location)) {
map.put(location, component);
break;
}
}
}
return map;
}
@Override
public boolean hasChanged() {
//No internal state
return false;
}
}