/*
* 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.List;
/**
* Simple layout manager the puts all components on a single line, either horizontally or vertically.
*/
public class LinearLayout implements LayoutManager {
/**
* This enum type will decide the alignment of a component on the counter-axis, meaning the horizontal alignment on
* vertical {@code LinearLayout}s and vertical alignment on horizontal {@code LinearLayout}s.
*/
public enum Alignment {
/**
* The component will be placed to the left (for vertical layouts) or top (for horizontal layouts)
*/
Beginning,
/**
* The component will be placed horizontally centered (for vertical layouts) or vertically centered (for
* horizontal layouts)
*/
Center,
/**
* The component will be placed to the right (for vertical layouts) or bottom (for horizontal layouts)
*/
End,
/**
* The component will be forced to take up all the horizontal space (for vertical layouts) or vertical space
* (for horizontal layouts)
*/
Fill,
}
private static class LinearLayoutData implements LayoutData {
private final Alignment alignment;
public LinearLayoutData(Alignment alignment) {
this.alignment = alignment;
}
}
/**
* Creates a {@code LayoutData} for {@code LinearLayout} that assigns a component to a particular alignment on its
* counter-axis, meaning the horizontal alignment on vertical {@code LinearLayout}s and vertical alignment on
* horizontal {@code LinearLayout}s.
* @param alignment Alignment to store in the {@code LayoutData} object
* @return {@code LayoutData} object created for {@code LinearLayout}s with the specified alignment
* @see Alignment
*/
public static LayoutData createLayoutData(Alignment alignment) {
return new LinearLayoutData(alignment);
}
private final Direction direction;
private int spacing;
private boolean changed;
/**
* Default constructor, creates a vertical {@code LinearLayout}
*/
public LinearLayout() {
this(Direction.VERTICAL);
}
/**
* Standard constructor that creates a {@code LinearLayout} with a specified direction to position the components on
* @param direction Direction for this {@code Direction}
*/
public LinearLayout(Direction direction) {
this.direction = direction;
this.spacing = direction == Direction.HORIZONTAL ? 1 : 0;
this.changed = true;
}
/**
* Sets the amount of empty space to put in between components. For horizontal layouts, this is number of columns
* (by default 1) and for vertical layouts this is number of rows (by default 0).
* @param spacing Spacing between components, either in number of columns or rows depending on the direction
* @return Itself
*/
public LinearLayout setSpacing(int spacing) {
this.spacing = spacing;
this.changed = true;
return this;
}
/**
* Returns the amount of empty space to put in between components. For horizontal layouts, this is number of columns
* (by default 1) and for vertical layouts this is number of rows (by default 0).
* @return Spacing between components, either in number of columns or rows depending on the direction
*/
public int getSpacing() {
return spacing;
}
@Override
public TerminalSize getPreferredSize(List components) {
if(direction == Direction.VERTICAL) {
return getPreferredSizeVertically(components);
}
else {
return getPreferredSizeHorizontally(components);
}
}
private TerminalSize getPreferredSizeVertically(List components) {
int maxWidth = 0;
int height = 0;
for(Component component: components) {
TerminalSize preferredSize = component.getPreferredSize();
if(maxWidth < preferredSize.getColumns()) {
maxWidth = preferredSize.getColumns();
}
height += preferredSize.getRows();
}
height += spacing * (components.size() - 1);
return new TerminalSize(maxWidth, height);
}
private TerminalSize getPreferredSizeHorizontally(List components) {
int maxHeight = 0;
int width = 0;
for(Component component: components) {
TerminalSize preferredSize = component.getPreferredSize();
if(maxHeight < preferredSize.getRows()) {
maxHeight = preferredSize.getRows();
}
width += preferredSize.getColumns();
}
width += spacing * (components.size() - 1);
return new TerminalSize(width, maxHeight);
}
@Override
public boolean hasChanged() {
return changed;
}
@Override
public void doLayout(TerminalSize area, List components) {
if(direction == Direction.VERTICAL) {
doVerticalLayout(area, components);
}
else {
doHorizontalLayout(area, components);
}
this.changed = false;
}
private void doVerticalLayout(TerminalSize area, List components) {
int remainingVerticalSpace = area.getRows();
int availableHorizontalSpace = area.getColumns();
for(Component component: components) {
if(remainingVerticalSpace <= 0) {
component.setPosition(TerminalPosition.TOP_LEFT_CORNER);
component.setSize(TerminalSize.ZERO);
}
else {
LinearLayoutData layoutData = (LinearLayoutData)component.getLayoutData();
Alignment alignment = Alignment.Beginning;
if(layoutData != null) {
alignment = layoutData.alignment;
}
TerminalSize preferredSize = component.getPreferredSize();
TerminalSize decidedSize = new TerminalSize(
Math.min(availableHorizontalSpace, preferredSize.getColumns()),
Math.min(remainingVerticalSpace, preferredSize.getRows()));
if(alignment == Alignment.Fill) {
decidedSize = decidedSize.withColumns(availableHorizontalSpace);
alignment = Alignment.Beginning;
}
TerminalPosition position = component.getPosition();
position = position.withRow(area.getRows() - remainingVerticalSpace);
switch(alignment) {
case End:
position = position.withColumn(availableHorizontalSpace - decidedSize.getColumns());
break;
case Center:
position = position.withColumn((availableHorizontalSpace - decidedSize.getColumns()) / 2);
break;
case Beginning:
default:
position = position.withColumn(0);
break;
}
component.setPosition(position);
component.setSize(component.getSize().with(decidedSize));
remainingVerticalSpace -= decidedSize.getRows() + spacing;
}
}
}
private void doHorizontalLayout(TerminalSize area, List components) {
int remainingHorizontalSpace = area.getColumns();
int availableVerticalSpace = area.getRows();
for(Component component: components) {
if(remainingHorizontalSpace <= 0) {
component.setPosition(TerminalPosition.TOP_LEFT_CORNER);
component.setSize(TerminalSize.ZERO);
}
else {
LinearLayoutData layoutData = (LinearLayoutData)component.getLayoutData();
Alignment alignment = Alignment.Beginning;
if(layoutData != null) {
alignment = layoutData.alignment;
}
TerminalSize preferredSize = component.getPreferredSize();
TerminalSize decidedSize = new TerminalSize(
Math.min(remainingHorizontalSpace, preferredSize.getColumns()),
Math.min(availableVerticalSpace, preferredSize.getRows()));
if(alignment == Alignment.Fill) {
decidedSize = decidedSize.withRows(availableVerticalSpace);
alignment = Alignment.Beginning;
}
TerminalPosition position = component.getPosition();
position = position.withColumn(area.getColumns() - remainingHorizontalSpace);
switch(alignment) {
case End:
position = position.withRow(availableVerticalSpace - decidedSize.getRows());
break;
case Center:
position = position.withRow((availableVerticalSpace - decidedSize.getRows()) / 2);
break;
case Beginning:
default:
position = position.withRow(0);
break;
}
component.setPosition(position);
component.setSize(component.getSize().with(decidedSize));
remainingHorizontalSpace -= decidedSize.getColumns() + spacing;
}
}
}
}