//- Copyright © 2008-2011 8th Light, Inc. All Rights Reserved.
//- Limelight and all included source files are distributed under terms of the MIT License.
package limelight.ui.model;
import limelight.LimelightException;
import limelight.Log;
import limelight.styles.Style;
import limelight.styles.abstrstyling.VerticalAlignmentValue;
import limelight.styles.values.AutoDimensionValue;
import limelight.styles.values.GreedyDimensionValue;
import limelight.ui.Panel;
import limelight.ui.model.inputs.ScrollBarPanel;
import limelight.util.Box;
import java.awt.*;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class PropPanelLayout implements Layout
{
public static PropPanelLayout instance = new PropPanelLayout();
public void doExpansion(Panel thePanel)
{
PropPanel panel = (PropPanel) thePanel;
FloaterLayout.instance.doFinalization(panel);
// Style style = panel.getStyle();
// if(panel.isSizeChangePending() || style.hasDynamicDimension())
snapToSize(panel);
}
public void doContraction(Panel thePanel)
{
PropPanel panel = (PropPanel) thePanel;
establishScrollBars(panel);
LinkedList<Row> rows = buildRows(panel);
distributeGreediness(panel, rows);
Dimension consumedDimensions = new Dimension();
calculateConsumedDimentions(rows, consumedDimensions);
collapseAutoDimensions(panel, consumedDimensions);
layoutRows(panel, consumedDimensions, rows);
layoutScrollBars(panel, consumedDimensions);
}
public void doFinalization(Panel thePanel)
{
PropPanel panel = (PropPanel) thePanel;
panel.updateBorder();
panel.markAsDirty();
}
// TODO MDM This gets called A LOT! Possible speed up by re-using objects, rather then reallocating them. (rows list, rows)
private void distributeGreediness(PropPanel panel, LinkedList<Row> rows)
{
for(Row row : rows)
row.distributeGreeyWidth();
distributeGreedyHeight(panel, rows);
}
private void distributeGreedyHeight(PropPanel panel, LinkedList<Row> rows)
{
int consumedHeight = 0;
int greedyRows = 0;
for(Row row : rows)
{
consumedHeight += row.height;
if(row.isGreedy())
greedyRows++;
}
int leftOver = panel.getChildConsumableBounds().height - consumedHeight;
if(greedyRows > 0)
{
int[] split = splitEvenly(leftOver, greedyRows);
int splitIndex = 0;
for(Row row : rows)
{
if(row.isGreedy())
row.applyGreedyHeight(split[splitIndex++]);
}
}
}
private int[] splitEvenly(int amount, int parts)
{
int[] split = new int[parts];
int part = amount / parts;
int remainder = amount % parts;
for(int i = 0; i < split.length; i++)
{
split[i] = part;
if(remainder > 0)
{
split[i] += 1;
remainder--;
}
}
return split;
}
public boolean overides(Layout other)
{
return true;
}
// private boolean hasNonScrollBarChildren(PropPanel panel)
// {
// List<Panel> children = panel.getChildren();
// if(children.size() == 0)
// return false;
// else if(children.size() == 1)
// return !(children.contains(panel.getVerticalScrollbar()) || children.contains(panel.getHorizontalScrollbar()));
// else if(children.size() == 2)
// return !(children.contains(panel.getVerticalScrollbar()) && children.contains(panel.getHorizontalScrollbar()));
// else
// return true;
// }
private void layoutScrollBars(PropPanel panel, Dimension consumedDimensions)
{
ScrollBarPanel vertical = panel.getVerticalScrollbar();
ScrollBarPanel horizontal = panel.getHorizontalScrollbar();
Box area = panel.getChildConsumableBounds();
if(vertical != null)
{
vertical.setHeight(area.height);
vertical.setLocation(area.right() + 1, area.y);
vertical.configure(area.height, consumedDimensions.height);
}
if(horizontal != null)
{
horizontal.setWidth(area.width);
horizontal.setLocation(area.x, area.bottom() + 1);
horizontal.configure(area.width, consumedDimensions.width);
}
}
private void collapseAutoDimensions(PropPanel panel, Dimension consumedDimensions)
{
Style style = panel.getStyle();
int width = style.getCompiledWidth().collapseExcess(panel.getWidth(), consumedDimensions.width + horizontalInsets(panel), style.getCompiledMinWidth(), style.getCompiledMaxWidth());
int height = style.getCompiledHeight().collapseExcess(panel.getHeight(), consumedDimensions.height + verticalInsets(panel), style.getCompiledMinHeight(), style.getCompiledMaxHeight());
panel.setSize(width, height);
}
public int horizontalInsets(PropPanel panel)
{
return panel.getBounds().width - panel.getPaddedBounds().width;
}
public int verticalInsets(PropPanel panel)
{
return panel.getBounds().height - panel.getPaddedBounds().height;
}
// protected void doPreliminaryLayoutOnChildren(PropPanel panel, Map<Panel, Layout> panelsToLayout)
// {
// for(Panel child : panel.getChildren())
// {
// if(panelsToLayout != null && panelsToLayout.containsKey(child))
// {
// if(!(child instanceof PropPanel) || child.getStyle().getCompiledWidth() instanceof AutoDimensionValue)
// {
// child.getDefaultLayout().doLayout(child, panelsToLayout, true);
// }
// else
// {
// ((PropPanel) child).greediness.setSize(0, 0);
// snapToSize((PropPanel) child);
// }
// }
// }
// }
//
// protected void doPostLayoutOnChildren(PropPanel panel, Map<Panel, Layout> panelsToLayout)
// {
// for(Panel child : panel.getChildren())
// {
// if(panelsToLayout != null && panelsToLayout.containsKey(child))
// {
// final Layout layout = child.getDefaultLayout();
// layout.doLayout(child, panelsToLayout, false);
// }
// }
// } 1
public void layoutRows(PropPanel panel, Dimension consumedDimension, LinkedList<Row> rows)
{
Style style = panel.getStyle();
int y = style.getCompiledVerticalAlignment().getY(consumedDimension.height, panel.getChildConsumableBounds());
if(panel.getVerticalScrollbar() != null)
{
y = Math.max(0, y);
y -= panel.getVerticalScrollbar().getValue();
}
for(Row row : rows)
{
int x = style.getCompiledHorizontalAlignment().getX(row.width, panel.getChildConsumableBounds());
if(panel.getHorizontalScrollbar() != null)
{
x = Math.max(0, x);
x -= panel.getHorizontalScrollbar().getValue();
}
row.layoutComponents(x, y, style.getCompiledVerticalAlignment());
y += row.height;
}
}
protected LinkedList<Row> buildRows(PropPanel panel)
{
LinkedList<Row> rows = new LinkedList<Row>();
Row currentRow = newRow(panel, rows);
for(Panel child : panel.getChildren())
{
if(!(child instanceof ScrollBarPanel) && !child.isFloater())
{
if(!currentRow.isEmpty() && !currentRow.fits(child))
{
currentRow = newRow(panel, rows);
}
currentRow.add(child);
}
}
return rows;
}
private Row newRow(PropPanel panel, LinkedList<Row> rows)
{
Row currentRow = new Row(panel.getChildConsumableBounds().width);
rows.add(currentRow);
return currentRow;
}
protected void calculateConsumedDimentions(LinkedList<Row> rows, Dimension consumedDimensions)
{
for(Row row : rows)
{
row.reCalculatedHeight();
consumedDimensions.height += row.height;
if(row.width > consumedDimensions.width)
consumedDimensions.width = row.width;
}
}
public void establishScrollBars(PropPanel panel)
{
Style style = panel.getStyle();
if(panel.getVerticalScrollbar() == null && style.getCompiledVerticalScrollbar().isOn())
panel.addVerticalScrollBar();
else if(panel.getVerticalScrollbar() != null && style.getCompiledVerticalScrollbar().isOff())
panel.removeVerticalScrollBar();
if(panel.getHorizontalScrollbar() == null && style.getCompiledHorizontalScrollbar().isOn())
panel.addHorizontalScrollBar();
else if(panel.getHorizontalScrollbar() != null && style.getCompiledHorizontalScrollbar().isOff())
panel.removeHorizontalScrollBar();
}
public void snapToSize(PropPanel panel)
{
if(panel.getParent() == null) // can happen if removed from tree
return;
Box maxArea = panel.getParent().getChildConsumableBounds();
Style style = panel.getStyle();
if(style.getCompiledWidth() instanceof AutoDimensionValue && style.getCompiledHeight() instanceof GreedyDimensionValue)
throw new LimelightException("A greedy height is not allowed with auto width.");
int newWidth = style.getCompiledWidth().calculateDimension(maxArea.width, style.getCompiledMinWidth(), style.getCompiledMaxWidth(), panel.greediness.width);
int newHeight = style.getCompiledHeight().calculateDimension(maxArea.height, style.getCompiledMinHeight(), style.getCompiledMaxHeight(), panel.greediness.height);
// TODO MDM - Hacky Hack!!!! More thought needs to go into the way layouts are down and how greedy fits into it all
// if(topLevel && style.getCompiledWidth() instanceof GreedyDimensionValue && panel.getWidth() > newWidth)
// newWidth = panel.getWidth();
// if(topLevel && style.getCompiledHeight() instanceof GreedyDimensionValue && panel.getHeight() > newHeight)
// newHeight = panel.getHeight();
panel.setSize(newWidth, newHeight);
panel.resetPendingSizeChange();
}
protected class Row
{
private final LinkedList<Panel> items;
private final int maxWidth;
public int width;
public int height;
private int greedyWidths;
private int greedyHeights;
public Row(int maxWidth)
{
this.maxWidth = maxWidth;
width = 0;
height = 0;
items = new LinkedList<Panel>();
}
public void add(Panel panel)
{
items.add(panel);
width += panel.getWidth();
if(panel.getHeight() > height)
height = panel.getHeight();
if(hasGreedyWidth(panel))
greedyWidths += 1;
if(hasGreedyHeight(panel))
{
greedyHeights += 1;
}
}
public boolean isEmpty()
{
return items.size() == 0;
}
public boolean fits(Panel panel)
{
return (width + panel.getWidth()) <= maxWidth;
}
public void layoutComponents(int x, int y, VerticalAlignmentValue verticalAlignment)
{
Box area = new Box(x, y, width, height);
for(Panel panel : items)
{
int alignedY = verticalAlignment.getY(panel.getHeight(), area);
panel.setLocation(x, alignedY);
x += panel.getWidth();
}
}
public void distributeGreeyWidth()
{
int leftOver = maxWidth - width;
if(leftOver > 0 && greedyWidths > 0)
{
width = maxWidth;
int[] splits = splitEvenly(leftOver, greedyWidths);
int splitIndex = 0;
for(Panel item : items)
{
if(hasGreedyWidth(item))
{
// PropPanel panel = (PropPanel) item;
int greedyWidth = splits[splitIndex++];
// panel.greediness.width = greedyWidth;
item.setSize(item.getWidth() + greedyWidth, item.getHeight());
}
}
}
}
private boolean hasGreedyWidth(Panel item)
{
return item instanceof PropPanel && item.getStyle().getCompiledWidth() instanceof GreedyDimensionValue;
}
private boolean hasGreedyHeight(Panel item)
{
return item instanceof PropPanel && item.getStyle().getCompiledHeight() instanceof GreedyDimensionValue;
}
public boolean isGreedy()
{
return greedyHeights > 0;
}
public void applyGreedyHeight(int extraHeight)
{
height += extraHeight;
for(Panel item : items)
{
if(hasGreedyHeight(item))
{
// PropPanel panel = (PropPanel) item;
// panel.greediness.height = Math.max(extraHeight, (height - panel.getHeight()));
// item.setSize(item.getWidth(), height);
item.setSize(item.getWidth(), height);
}
}
}
public void reCalculatedHeight()
{
height = 0;
for(Panel item : items)
{
if(item.getHeight() > height)
height = item.getHeight();
}
}
}
}