Software

MinimizeClose

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:

  • The unit of the values is pixels, but the computations may use doubles.
  • The layout manager will always first compute the dimensions and then the position. This means it is safe to the refer to the dimensions of the component itself when setting the position, but not the other way around.
  • Unknown references will have value 0.
  • Adding a component twice results in undefined behaviour.
  • The expressions are parsed only once and recomputed efficiently (Shunting yard algorithm).
  • ArithmeticLayoutManager only does stupid calculations, it has no notion of distance and won't mind putting components on top of each other.
  • ArithmeticLayoutManager is not meant for animations.

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

Sticky
MarcoSlot.Net now runs entirely in the Cloud! Using S3 as a storage back-end and Google App Engine & CloudFront as delivery front-ends it is fast, cheap, scalable, monitored 24/7 and I have no idea how it works anymore.