blob: f502798733b1946f32d8acf94afaf87b69a8f76e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pivot.wtk.skin;
import org.apache.pivot.collections.ArrayList;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.Dimensions;
import org.apache.pivot.wtk.FlowPane;
import org.apache.pivot.wtk.HorizontalAlignment;
import org.apache.pivot.wtk.Insets;
/**
* Flow pane skin.
*/
public class FlowPaneSkin extends ContainerSkin {
private HorizontalAlignment alignment;
private Insets padding;
private int horizontalSpacing;
private int verticalSpacing;
private boolean alignToBaseline;
public FlowPaneSkin() {
alignment = HorizontalAlignment.LEFT;
padding = Insets.NONE;
horizontalSpacing = 2;
verticalSpacing = 2;
alignToBaseline = true;
}
@Override
public int getPreferredWidth(int height) {
FlowPane flowPane = (FlowPane) getComponent();
int preferredWidth = 0;
// Preferred width is the sum of the preferred widths of all components
// (height constraint is ignored)
int j = 0;
for (int i = 0, n = flowPane.getLength(); i < n; i++) {
Component component = flowPane.get(i);
if (component.isVisible()) {
preferredWidth += component.getPreferredWidth();
j++;
}
}
// Include spacing
if (j > 1) {
preferredWidth += horizontalSpacing * (j - 1);
}
// Include left and right padding values
preferredWidth += padding.left + padding.right;
return preferredWidth;
}
@Override
public int getPreferredHeight(int width) {
FlowPane flowPane = (FlowPane) getComponent();
int preferredHeight;
if (width == -1) {
if (alignToBaseline) {
// Delegate to preferred size calculations
Dimensions preferredSize = getPreferredSize();
preferredHeight = preferredSize.height;
} else {
// Preferred height is the maximum preferred height of all
// components
preferredHeight = 0;
for (int i = 0, n = flowPane.getLength(); i < n; i++) {
Component component = flowPane.get(i);
if (component.isVisible()) {
preferredHeight = Math.max(preferredHeight, component.getPreferredHeight());
}
}
}
} else {
// Break the components into multiple rows
preferredHeight = 0;
int contentWidth = Math.max(width - (padding.left + padding.right), 0);
int rowCount = 0;
int rowWidth = 0;
int rowAscent = 0;
int rowDescent = 0;
for (int i = 0, n = flowPane.getLength(); i < n; i++) {
Component component = flowPane.get(i);
if (component.isVisible()) {
Dimensions size = component.getPreferredSize();
if (rowWidth + size.width > contentWidth && rowWidth > 0) {
// The component is too big to fit in the remaining
// space,
// and it is not the only component in this row; wrap
preferredHeight += rowAscent + rowDescent;
rowCount++;
rowWidth = 0;
rowAscent = 0;
rowDescent = 0;
}
rowWidth += size.width + horizontalSpacing;
if (alignToBaseline) {
int baseline = component.getBaseline(size.width, size.height);
rowAscent = Math.max(rowAscent, baseline);
rowDescent = Math.max(rowDescent, size.height - baseline);
} else {
rowAscent = Math.max(rowAscent, size.height);
}
}
}
// Add the last row
int lastRowHeight = rowAscent + rowDescent;
if (lastRowHeight > 0) {
preferredHeight += lastRowHeight;
rowCount++;
}
// Include spacing
if (rowCount > 0) {
preferredHeight += verticalSpacing * (rowCount - 1);
}
}
// Include top and bottom padding values
preferredHeight += padding.top + padding.bottom;
return preferredHeight;
}
@Override
public Dimensions getPreferredSize() {
FlowPane flowPane = (FlowPane) getComponent();
int preferredWidth = 0;
int ascent = 0;
int descent = 0;
int j = 0;
for (int i = 0, n = flowPane.getLength(); i < n; i++) {
Component component = flowPane.get(i);
if (component.isVisible()) {
Dimensions size = component.getPreferredSize();
preferredWidth += size.width;
if (alignToBaseline) {
int baseline = component.getBaseline(size.width, size.height);
ascent = Math.max(ascent, baseline);
descent = Math.max(descent, size.height - baseline);
} else {
ascent = Math.max(ascent, size.height);
}
j++;
}
}
// Include horizontal spacing
if (j > 1) {
preferredWidth += horizontalSpacing * (j - 1);
}
// Include padding
preferredWidth += padding.left + padding.right;
return new Dimensions(preferredWidth, ascent + descent + padding.top + padding.bottom);
}
@Override
public int getBaseline(int width, int height) {
FlowPane flowPane = (FlowPane) getComponent();
int baseline = -1;
if (alignToBaseline) {
int contentWidth = Math.max(width - (padding.left + padding.right), 0);
// Break the components into multiple rows, and calculate the
// baseline of the
// first row
int rowWidth = 0;
for (int i = 0, n = flowPane.getLength(); i < n; i++) {
Component component = flowPane.get(i);
if (component.isVisible()) {
Dimensions size = component.getPreferredSize();
if (rowWidth + size.width > contentWidth && rowWidth > 0) {
// The component is too big to fit in the remaining
// space,
// and it is not the only component in this row; wrap
break;
}
baseline = Math.max(baseline, component.getBaseline(size.width, size.height));
rowWidth += size.width + horizontalSpacing;
}
}
// Include top padding value
baseline += padding.top;
}
return baseline;
}
@Override
public void layout() {
FlowPane flowPane = (FlowPane) getComponent();
int width = getWidth();
int contentWidth = Math.max(width - (padding.left + padding.right), 0);
// Break the components into multiple rows
ArrayList<ArrayList<Component>> rows = new ArrayList<>();
ArrayList<Component> row = new ArrayList<>();
int rowWidth = 0;
for (int i = 0, n = flowPane.getLength(); i < n; i++) {
Component component = flowPane.get(i);
if (component.isVisible()) {
Dimensions componentSize = component.getPreferredSize();
component.setSize(componentSize);
if (rowWidth + componentSize.width > contentWidth && rowWidth > 0) {
// The component is too big to fit in the remaining space,
// and it is not the only component in this row
rows.add(row);
row = new ArrayList<>();
rowWidth = 0;
}
// Add the component to the row
row.add(component);
rowWidth += componentSize.width + horizontalSpacing;
}
}
// Add the last row
if (row.getLength() > 0) {
rows.add(row);
}
// Lay out the rows
int rowY = padding.top;
for (int i = 0, n = rows.getLength(); i < n; i++) {
row = rows.get(i);
// Determine the row dimensions
rowWidth = 0;
int rowHeight = 0;
int baseline = -1;
for (Component component : row) {
rowWidth += component.getWidth();
rowHeight = Math.max(rowHeight, component.getHeight());
baseline = Math.max(baseline,
component.getBaseline(component.getWidth(), component.getHeight()));
}
rowWidth += horizontalSpacing * (row.getLength() - 1);
int x = 0;
switch (alignment) {
case LEFT: {
x = padding.left;
break;
}
case CENTER: {
x = (width - rowWidth) / 2;
break;
}
case RIGHT: {
x = width - rowWidth - padding.right;
break;
}
default: {
break;
}
}
for (Component component : row) {
int y;
int componentBaseline = component.getBaseline(component.getWidth(),
component.getHeight());
if (alignToBaseline && baseline != -1 && componentBaseline != -1) {
// Align to baseline
y = baseline - componentBaseline;
} else {
// Align to bottom
y = rowHeight - component.getHeight();
}
component.setLocation(x, y + rowY);
x += (component.getWidth() + horizontalSpacing);
}
rowY += (rowHeight + verticalSpacing);
}
}
public HorizontalAlignment getAlignment() {
return alignment;
}
public void setAlignment(HorizontalAlignment alignment) {
if (alignment == null) {
throw new IllegalArgumentException("alignment is null.");
}
this.alignment = alignment;
invalidateComponent();
}
/**
* @return The amount of space between the edge of the FlowPane and its
* components.
*/
public Insets getPadding() {
return padding;
}
/**
* Sets the amount of space to leave between the edge of the FlowPane and
* its components.
*
* @param padding The individual padding values for each edge.
*/
public void setPadding(Insets padding) {
if (padding == null) {
throw new IllegalArgumentException("padding is null.");
}
this.padding = padding;
invalidateComponent();
}
/**
* Sets the amount of space to leave between the edge of the FlowPane and
* its components.
*
* @param padding A dictionary with keys in the set {left, top, bottom,
* right}.
*/
public final void setPadding(Dictionary<String, ?> padding) {
if (padding == null) {
throw new IllegalArgumentException("padding is null.");
}
setPadding(new Insets(padding));
}
/**
* Sets the amount of space to leave between the edge of the FlowPane and
* its components, uniformly on all four edges.
*
* @param padding The single padding value for all four sides.
*/
public final void setPadding(int padding) {
setPadding(new Insets(padding));
}
/**
* Sets the amount of space to leave between the edge of the FlowPane and
* its components, uniformly on all four edges.
*
* @param padding The single padding value for all four sides.
*/
public final void setPadding(Number padding) {
if (padding == null) {
throw new IllegalArgumentException("padding is null.");
}
setPadding(padding.intValue());
}
/**
* Sets the amount of space to leave between the edge of the FlowPane and
* its components.
*
* @param padding A string containing an integer or a JSON dictionary with
* keys left, top, bottom, and/or right.
*/
public final void setPadding(String padding) {
if (padding == null) {
throw new IllegalArgumentException("padding is null.");
}
setPadding(Insets.decode(padding));
}
public int getHorizontalSpacing() {
return horizontalSpacing;
}
public void setHorizontalSpacing(int horizontalSpacing) {
if (horizontalSpacing < 0) {
throw new IllegalArgumentException("horizontalSpacing is negative.");
}
this.horizontalSpacing = horizontalSpacing;
invalidateComponent();
}
public final void setHorizontalSpacing(Number horizontalSpacing) {
if (horizontalSpacing == null) {
throw new IllegalArgumentException("horizontalSpacing is null.");
}
setHorizontalSpacing(horizontalSpacing.intValue());
}
public int getVerticalSpacing() {
return verticalSpacing;
}
public void setVerticalSpacing(int verticalSpacing) {
if (verticalSpacing < 0) {
throw new IllegalArgumentException("verticalSpacing is negative.");
}
this.verticalSpacing = verticalSpacing;
invalidateComponent();
}
public final void setVerticalSpacing(Number verticalSpacing) {
if (verticalSpacing == null) {
throw new IllegalArgumentException("verticalSpacing is null.");
}
setVerticalSpacing(verticalSpacing.intValue());
}
public boolean getAlignToBaseline() {
return alignToBaseline;
}
public void setAlignToBaseline(boolean alignToBaseline) {
this.alignToBaseline = alignToBaseline;
invalidateComponent();
}
}