/*
* Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores
* CA 94065 USA or visit www.oracle.com if you need additional information or
* have any questions.
*/
package com.codename1.ui.layouts;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
import com.codename1.ui.geom.*;
import com.codename1.ui.plaf.Style;
import com.codename1.ui.plaf.UIManager;
/**
* <p>Layout manager that places elements in a row (<code>X_AXIS</code>) or column (<code>Y_AXIS</code>)
* according to box orientation. Box is a very simple and predictable layout that serves as the "workhorse" of
* component lists in Codename One<br>
* You can create a box layout Y UI using syntax such as this</p>
*
* <script src="https://gist.github.com/codenameone/99f5e43061b4c6413d16.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/box-layout-y.png" alt="Box Layout Y" />
*
* <p>
* This can also be expressed with more terse syntax e.g. an X axis layout like this:
* </p>
* <script src="https://gist.github.com/codenameone/5d2908517241126b5803.js"></script>
* <img src="https://www.codenameone.com/img/developer-guide/box-layout-x.png" alt="Box Layout X" />
*
* <p>
* The <code>BoxLayout</code> keeps the preferred size of its destination orientation and scales elements on the other axis.
* Specifically <code>X_AXIS</code> will keep the preferred width of the component while growing all
* the components vertically to match in size. Its <code>Y_AXIS</code> counterpart keeps the preferred height
* while growing the components horizontally.<br>
* This behavior is very useful since it allows elements to align as they would all have the same size.
* </p>
* <p>
* In some cases the growing behavior in the X axis is undesired, for these cases we can use the <code>X_AXIS_NO_GROW</code>
* variant.
* </p>
* <img src="https://www.codenameone.com/img/developer-guide/box-layout-x-no-grow.png" alt="Box Layout X No Grow" />
*
* <h4>FlowLayout vs. BoxLayout.X_AXIS/X_AXIS_NO_GROW</h4>
* <p>
* There are quite a few differences between {@link FlowLayout} and <code>BoxLayout</code>. When it doesn't
* matter to you we tend to recommend <code>BoxLayout</code> as it acts more consistently in all situations since
* its far simpler. Another advantage of <code>BoxLayout</code> is the fact that it grows and thus aligns nicely.
* </p>
*
* @author Chen Fishbein
*/
public class BoxLayout extends Layout{
/**
* Horizontal layout where components are arranged from left to right
*/
public static final int X_AXIS = 1;
/**
* Vertical layout where components are arranged from top to bottom
*/
public static final int Y_AXIS = 2;
/**
* Horizontal layout where components are arranged from left to right but don't grow vertically beyond their preferred size
*/
public static final int X_AXIS_NO_GROW = 3;
private int axis;
/**
* Creates a new instance of BoxLayout
*
* @param axis the axis to lay out components along.
* Can be: BoxLayout.X_AXIS or BoxLayout.Y_AXIS
*/
public BoxLayout(int axis) {
this.axis = axis;
}
/**
* Shorthand for {@code new BoxLayout(BoxLayout.Y_AXIS)}
* @return a new Y axis {@code BoxLayout}
*/
public static BoxLayout y() {
return new BoxLayout(BoxLayout.Y_AXIS);
}
/**
* Shorthand for {@code new BoxLayout(BoxLayout.X_AXIS)}
* @return a new X axis {@code BoxLayout}
*/
public static BoxLayout x() {
return new BoxLayout(BoxLayout.X_AXIS);
}
/**
* {@inheritDoc}
*/
public void layoutContainer(Container parent) {
Style ps = parent.getStyle();
int width = parent.getLayoutWidth() - parent.getSideGap() - ps.getHorizontalPadding();
int height = parent.getLayoutHeight() - parent.getBottomGap() - ps.getVerticalPadding();
int x = ps.getPaddingLeft(parent.isRTL());
int y = ps.getPaddingTop();
int numOfcomponents = parent.getComponentCount();
boolean rtl = parent.isRTL();
if(rtl) {
x += parent.getSideGap();
}
int initX = x;
for(int i=0; i< numOfcomponents; i++){
Component cmp = parent.getComponentAt(i);
Style stl = cmp.getStyle();
switch(axis) {
case Y_AXIS:
int cmpBottom = height;
cmp.setWidth(width - stl.getHorizontalMargins());
int cmpH = cmp.getPreferredH();
y += stl.getMarginTop();
if(y - ps.getPaddingTop() >= cmpBottom && !parent.isScrollableY()){
cmpH = 0;
}else if(y + cmpH - ps.getPaddingTop() > cmpBottom){
if(!parent.isScrollableY()) {
cmpH = cmpBottom - y - stl.getMarginBottom();
}
}
cmp.setHeight(cmpH);
cmp.setX(x + stl.getMarginLeft(parent.isRTL()));
cmp.setY(y);
y += cmp.getHeight() + stl.getMarginBottom();
break;
case X_AXIS_NO_GROW: {
int cmpRight = width;
height = Math.min(getPreferredSize(parent).getHeight(), height);
int cmpW = cmp.getPreferredW();
x += stl.getMarginLeftNoRTL();
if(x >= cmpRight && !parent.isScrollableX()){
cmpW = 0;
} else {
if(x + cmpW - ps.getPaddingLeftNoRTL() > cmpRight){
cmpW = cmpRight - x - stl.getMarginRightNoRTL();
}
}
cmp.setWidth(cmpW);
cmp.setHeight(height- stl.getMarginTop() - stl.getMarginBottom());
if(rtl) {
cmp.setX(width + initX - (x - initX) - cmpW);
} else {
cmp.setX(x);
}
cmp.setY(y + stl.getMarginTop());
x += cmp.getWidth() + stl.getMarginRightNoRTL();
break;
}
default:
int cmpRight = width;
int cmpW = cmp.getPreferredW();
x += stl.getMarginLeftNoRTL();
if(x >= cmpRight && !parent.isScrollableX()){
cmpW = 0;
} else {
if(x + cmpW - ps.getPaddingLeftNoRTL()> cmpRight){
cmpW = cmpRight - x - stl.getMarginRightNoRTL();
}
}
cmp.setWidth(cmpW);
cmp.setHeight(height- stl.getVerticalMargins());
if(rtl) {
cmp.setX(width + initX - (x - initX) - cmpW);
} else {
cmp.setX(x);
}
cmp.setY(y + stl.getMarginTop());
x += cmp.getWidth() + stl.getMarginRightNoRTL();
break;
}
}
}
private Dimension dim = new Dimension(0, 0);
/**
* {@inheritDoc}
*/
public Dimension getPreferredSize(Container parent) {
int width = 0;
int height = 0;
int numOfcomponents = parent.getComponentCount();
for(int i=0; i< numOfcomponents; i++){
Component cmp = parent.getComponentAt(i);
Style stl = cmp.getStyle();
if(axis == Y_AXIS){
int cmpH = cmp.getPreferredH() + stl.getVerticalMargins();
height += cmpH;
width = Math.max(width , cmp.getPreferredW()+ stl.getHorizontalMargins());
}else{
int cmpW = cmp.getPreferredW() + stl.getHorizontalMargins();
width += cmpW;
height = Math.max(height, cmp.getPreferredH() + stl.getVerticalMargins());
}
}
Style s = parent.getStyle();
dim.setWidth(width + s.getHorizontalPadding());
dim.setHeight(height + s.getVerticalPadding());
return dim;
}
/**
* Returns the layout axis x/y
*
* @return the layout axis
*/
public int getAxis() {
return axis;
}
/**
* {@inheritDoc}
*/
public String toString() {
if(axis == X_AXIS) {
return "BoxLayout X";
}
return "BoxLayout Y";
}
/**
* {@inheritDoc}
*/
public boolean equals(Object o) {
return super.equals(o) && axis == ((BoxLayout)o).axis;
}
/**
* The equivalent of Container.enclose() with a box layout Y
* <img src="https://www.codenameone.com/img/developer-guide/box-layout-x.png" alt="Box Layout X" />
* @param cmps the set of components
* @return the newly created container
*/
public static Container encloseY(Component... cmps) {
return Container.encloseIn(new BoxLayout(BoxLayout.Y_AXIS), cmps);
}
/**
* The equivalent of Container.enclose() with a box layout X
* <img src="https://www.codenameone.com/img/developer-guide/box-layout-x.png" alt="Box Layout X" />
* @param cmps the set of components
* @return the newly created container
*/
public static Container encloseX(Component... cmps) {
return Container.encloseIn(new BoxLayout(BoxLayout.X_AXIS), cmps);
}
/**
* The equivalent of Container.enclose() with a box layout X no grow option
* <img src="https://www.codenameone.com/img/developer-guide/box-layout-x.png" alt="Box Layout X" />
* @param cmps the set of components
* @return the newly created container
*/
public static Container encloseXNoGrow(Component... cmps) {
return Container.encloseIn(new BoxLayout(BoxLayout.X_AXIS_NO_GROW), cmps);
}
}