Presenting: ArithmeticLayoutManager

The ArithmeticLayoutManager is a layout manager which can be used to specify the bounds of a component as a series of mathematical expressions. The expressions are evaluated every time the container is resized and consist of simple calculations with references to fields of the containing component and other components within the same container.

ArithmeticLayoutManager was born out of dissatisfaction with existing layout managers. Although some extremely powerful layout managers exist, most of them present the programmer with a horrendous amount of complexity by requiring intricate chains of invocations, an unintuive String syntax, or a complex configuration procedure (or all of the above). As a result most developers simply use the null layout, which decreases the usability of the program. ArithmeticLayoutManager aims combine an intuitive Java/CSS-like syntax with powerful arithmetic expressions.

Lets start by doing a trick that is extremely hard to do in most layout managers: Container panel = getContentPane(); panel.setLayout(new ArithmeticLayoutManager()); JLabel nameLabel = new JLabel("Name:"); panel.add(nameLabel, "name = nameLabel; "+ "top = 20; "+ "left = 20; "); JTextField nameField = new JTextField(); panel.add(nameField, "top = 20; "+ "left = nameLabel.rRight + 20; "+ "right = 20; ");

We have now placed a label and a textbox with 20 pixels in between. The exact location of the textbox depends on the size of the label and the textbox automatically resizes with the window. (Web Start Demo - Full source)

The syntax

ArithmeticLayoutManager (ALM) works by using the constraints parameter when adding a component. The constraint has to be a String of the form: <field> = <expression>; <field> = <expression>; ... A field can be one of left, top, right, bottom, rleft, rtop, rright, rbottom, width, height, name or an alias. The meaning of the fields is displayed in the overview below.

Overview of the fields
Figure 1. Overview of fields
Field Aliases Description
left
  • left
  • l
  • x
the distance between the left side of this component and the left side of the parent
top
  • top
  • t
  • y
the distance between the top side of this component and the top side of the parent
right
  • right
  • r
the distance between the right side of this component and the right side of the parent
bottom
  • bottom
  • b
the distance between the bottom side of this component and the bottom side of the parent
rleft
  • rleft
  • rl
the distance between the left side of this component and the right side of the parent
rtop
  • rtop
  • rt
the distance between the top side of this component and the bottom side of the parent
rright
  • rright
  • rr
the distance between the right side of this component and the left side of the parent
rbottom
  • rbottom
  • rb
the distance between the bottom side of this component and the top side of the parent
width
  • width
  • w
the width of the component
height
  • height
  • h
the height of the component
name
  • name
  • n
used to name a component

When invoked (e.g. window resized) ALM will evaluate all expressions and resize/reposition the component accordingly. Missing fields can be derived from other fields. For example: When setting bottom and height, top is derived. When setting left and rright, width is derived. Ambiguous assignments will not be accepted. When a field is not set and can not be derived the original position is used with the preferred size of the component.

Expressions are arithmetic expressions in infix notation with operators: *, /, +, -, %. Brackets can be used for precedence. Additionally you can define references to other components and the component itself. References are of the form <componentName>.<field>. A componentName is the name of a sibling component and can be defined by setting the name field when adding the component. Two names have been predefined: parent is the containing component, this is the component itself.

A note on insets:

The insets of containers are respected when setting positional fields. This means that if you set the left inset to 10 pixels and define left = 5; then the component will appear 15 pixels from the left side of the parent. The insets are not taken into account when referring to the size of the parent (e.g. parent.width). This may lead to confusion when trying to make a component the full width or height of the parent while there are non-zero insets: component.getInsets().set(10, 10, 10, 10); panel.add(component, "left = 0;"+ "width = parent.width;"); /* wrong, sticks out on the right side */ To correct the width we could substract the insets, but if they are non constant this leads to very messy (string concatenation) code. We recommend not to use parent.width in such situations, but rather to use ALM's ability to 'stretch' a component by specifying a position on both sides: component.getInsets().set(10, 10, 10, 10); panel.add(component, "left = 0;"+ "right = 0;"); /* correct (full) width is calculated automagically */

Some important things to know:

Examples

The following examples are taken from a sample application (Web Start Demo - Full source).

Centering a component: panel.add(title, "x = 0.5 * parent.width - 0.5 * this.width;"+ "y = 20; ");

A close button: JButton button = new JButton("X"); panel.add(button, "top = 5; " + "right = 20; ");

Dynamic width: JLabel label = new JLabel("Name:"); panel.add(label, "name = label; " + "left = 15; " + "top = 90; " + "width = 0.2 * parent.width; "); JTextField field = new JTextField(""); panel.add(nameField, "name = nameField; " + "left = label.rRight + 20; " + "top = label.top; " + "right = 20; ");

Finally: the important stuff

Author

Marco Slot. A Computer Science student from the Netherlands currently doing a Master's in Parallel and Distributed Computer Systems at the Vrije Universiteit in Amsterdam.