Skip to content
Paul Speed edited this page Sep 19, 2015 · 25 revisions

GUI Components

GUI Elements are constructed from a stack of GUI Components that implement the GuiComponent interface. The stacks are composed in a way where each layer acts as a kind of 'decorator' for the next layer (from bottom up).

For example, a Button is built from several different possible layers and the application can change any of these layers.

Button Breakout

Therefore, to understand how to tweak a GUI element to get a specific look, it is necessary to understand the GUI components that Lemur provides. Or to know how to write your own.

Component Layout

Lemur uses a flexible layer-based approach to how components are sized and positioned relative to their next and previous layers. Each lower layer acts as a decorator to the layer above. Thus the top layer controls the 'preferred size' of the whole stack while ultimately the bottom layer controls the 'actual size'.

For common component usage, it is not necessary to know this information in detail. It is provided as background as it helps explain some of the other properties like offsets and margins, etc.. It is also vitally useful to anyone writing their own custom components.

Here is the test button in more detail:
Test Button
The second image shows hard borders for how all of the different layers might be sized, from background layer, to icon layer, to text box and text within it.

In a shrunk down expanded view, these might look like this: Test Button Expanded

Calculating Preferred Size

Lemur calculates preferred size by starting at the top component layer and working down. Each new component has a chance to add to the preferred size.
Preferred Size

This is not necessarily a straight forward process. For example, the icon component layer might add to a different axis if it is aligned to the top instead of the left. It might add to both axes if it is in the upper left, for example.

Reshape

The parent of the GUI element, when taking all preferred size into account and jiving that with whatever other constraints it is under (container layouts, etc.) will then reshape the components starting from the bottom. Lemur's component stack then performs the reshape process as follows:

reshape

Each layer in this case gets a chance to move the 'origin' offset and shrink the size that the next layer will use. So in this example, the size starts out as the full size of the component and the offset starts at 0,0 at the bottom layer. The background layer nudges the offset in by the margin and shrinks the bounds by margin * 2. The icon layer moves its geometry to the origin and makes sure it is sized appropriately and then moves the origin by the rendered icon width and subtracts that same width from the size. The text layers are now setup to put the text in the appropriate location.

TextComponent is a fully expanding component and so doesn't adjust the offset of size at all. There is technically no space left after it has rendered so the values wouldn't have any meaning. However, it is useful to be able to stack these layers as seen. In this way, top-level layers will often consume all of the rest of the size but otherwise not adjust the offset or size for the next layers. This is what allows the shadow text and regular text to use the same position and offset.

In this example, the shadow layer will actually render its text at an offset from the supplied offset. This points out how the layers can choose to move outside of this 'bounds' if they want to for whatever reason. They are technically breaking the layout at that point but it's useful for some kinds of layers like text shadows or icon overlays and so on. The two step preferred size calculation and then reshape is more of a strong guideline.

Base Components

QuadBackgroundComponent

This component renders a simple colored or textured 2D quad that shrinks/expands with the GUI element. (The texture is stretched directly without any special processing. For a texture that stretches only part and leaves the borders a common scale, see: TbtQuadBackgroundComponent)

The quad is currently always rendered in the x/y plane.

QuadBackgroundComponent can create either a lit or an unlit quad depending on the options specified when created. There is currently no way to change this option once the component has been created. Being 'lit' means that the quad will use Lighting.j3md and have proper normals.

Properties:

  • color: the color of the quad. If there is a texture then this will be combined with the texture.
  • alpha: the overall alpha of the quad. This is multiplied by whatever alpha is in the color value.
  • texture: an optional texture for the quad.
  • margin: a margin that controls how much size the next component in the stack gets. For example, a GUI element that is size 100x100 and has a QuadBackgroundComponent for its 'border' layer and a 5,5 margin would render the 'background' layer at 90x90 with a 5,5 x/y offset.
  • zOffset: the z-size of the quad. Useful when layering to create slight stand-offs for subsequent layers.

See also: javadoc

TbtQuadBackgroundComponent

This is similar to QuadBackgroundComponent except that it always has a texture and it implements 9-patch style stretching.

Nine patch
The reason it's called a 'nine patch'.

Lemur calls it a TBT Quad which is short for 'Three-By-Three' Quad and is shorter to type than 'NinePatchQuadBackgroundComponent' and more unique than NpQuadBackgroundComponent would be. A rose by any other name...

Nine patches are useful for UI Element backgrounds because they provide smarter stretching at the borders.

Nine Patch Stretching

They can be somewhat tricky to setup but Lemur tries to make it easier. It's at least easier than manually calculating texture coordinates versus image size versus target size would be.

When creating a TBT Quad, Lemur has reduced the parameters down to imageScale and two corner coordinate values:

  • imageScale: defines the scale between visual units and image units. For example, if the image is 128x128 then a scale of 1 means that on-screen the border widths will be scaled 1:1 to the pixels. So if the border is 16 image pixels then it will be 16 screen pixels. If the imageScale is 2 then those same borders would end up as 32 on-screen pixels.
  • x1, y1: defines the lower left corner of the center full-stretch area in image pixels.
  • x2, y2: defines the upper right corner of the center full-stretch area in image pixels.

The texture image used in the nine-patch example above is the 'com/simsilica/lemur/icons/bevel-quad.png' included in Lemur by default. It is a 128x128 image and the borders are 8 image pixels wide. Thus we could create a TBT quad component with the following parameters: imageScale=1, x1=8, y1=8, x2=119, y2=119

If that component is drawn on screen as 100x50 in size then the borders would still be 8 pixels wide but the stretched area would be 84x34 pixels. If the imageScale is changed to 2 then the borders would be 16 pixels wide and the stretched area would be 52x2.

As with QuadBackgroundComponent, the TbtQuadBackgroundComponent can create either a lit or an unlit quad depending on the options specified when created. There is currently no way to change this option once the component has been created. Being 'lit' means that the quad will use Lighting.j3md and have proper normals.

Properties:

  • color: the color of the quad. If there is a texture then this will be combined with the texture.
  • alpha: the overall alpha of the quad. This is multiplied by whatever alpha is in the color value.
  • texture: the texture that is applied to the quad.
  • margin: a margin that controls how much size the next component in the stack gets. For example, a GUI element that is size 100x100 and has a TbtQuadBackgroundComponent for its 'border' layer and a 5,5 margin would render the 'background' layer at 90x90 with a 5,5 x/y offset. By default, TbtQuadBackgroundComponent will use the x1,y1 creation values as the margin.
  • zOffset: the z-size of the quad. Useful when layering to create slight stand-offs for subsequent layers.

See also: javadoc

IconComponent

This component renders a single image that can either carve itself out of subsequent layers or just lay under them. The alignment of the image can be set to any of the sides or corners by setting the horizontal and/or vertical alignment. This component is useful for adding icon images to things like buttons.

Properties:

  • imageTexture: the texture containing the icon image. It is set during component creation but can also be reset at runtime as needed.
  • color: the color that will be multiplied with the icon image texture colors.
  • alpha: the overall alpha for the image. This will be multiplied by the color property's alpha value.
  • iconScale: the scale of the icon image relative to the units of the destination. For example, a scale of 1 rendered to the screen will set the icon's size so that 1 image pixel is 1 screen pixel.
  • HAlignment: the horizontal alignment of the image within the layer. One of HAlignment.Left, HAlignment.Right, or HAlignment.Center. (Note: setting both VAlignment and HAlignment to Center may have strange results for subsequent layers.)
  • VAlignment: the vertical alignment of the image within the layer. One of VAlignment.Top, VAlignment.Bottom, or VAlignment.Center. (Note: setting both VAlignment and HAlignment to Center may have strange results for subsequent layers.)
  • margin: the padded border around the icon within this layer.
  • zOffset: the z-size of the layer. Useful when layering to create slight stand-offs for subsequent layers.
  • offset: the x,y,z offset relative to where the icon would normally be placed. This can be used to tweak the icon placement to account for misshapen images or just to hand align things when pure automatic alignment won't work.
  • overlay: set to true when the icon's size and positioning should not affect the next layer. It's really kind of an 'underlay' as it affects the subsequent layers. This is the one case where VAlignment and HAlignment might logically be set to Center as it means the image would be centered underneath whatever layers follow, such as text, for example.

DynamicInsetsComponent

This is a positioning component only has none of its own geometry. It is like the regular InsetsComponent except that it defines its insets dynamically based on the actual size it's given versus its own preferred size. Inset3f values represent fractions of the "gap" between actual and preferred size.

For example, if the preferred size of the component is 200x100 but the actual layout wants to render the component as 300x200 then if Insets3f is all 0.5, 0.5, 0.5, etc. the subsequent layers will be placed directly in the center, a 50 unit gap on all sides. (0.5 * (300-200)) = 50 (0.5 * (200-100)) = 50

On the other hand, if the Insets3f is top=0, bottom=1, left=0, right=1 then the subsequent layers will be placed in the upper left, ie: the lower right calculated insets become the full 100x100.

Properties:

  • insets:the dynamic insets as fractions of the difference between preferred and actual size. Note: actual values are 'balanced' so that they always add to 1.0. So if insets top=50 and bottom=50 are passed in then they are converted to 0.5 and 0.5.

Internal Components

InsetsComponent

This is a positioning component and renders no geometry on its own. The component carves out some specific subset of space for the next component layer based on the Insets3f object provided. The Insets3f object defines the amount of border space for each of the directions: top, left, bottom, right, front, and back. These correspond to +y, -x, -y, +x, +z, and -z respectively.

During preferred size calculation, the InsetsComponent will add its insets values to the preferred size. During reshape() layout, it will adjust the offset by left, bottom, and back. It will decrease the side for the child to size - all insets3f fields.

For example, if the insets are Insets3f(2, 5, 2, 5, 1, 1) and set as the 'insets' layer of the GUI element then if the next layer's preferred size is 100x100x1 then the real preferred size will be calculated as 110x104x3. Generally, the next layer is the border or background layer. If the reshape() comes in as 0,0 with a size of 200x100x5 then the InsetsComponent will pass 5, 2 as the offset to the next layer and 190, 96, 3 as the size.

Properties:

  • insets: the Insets3f values that will be added to the preferred size and subtracted from reshapes.

TextComponent

This component renders a single layer of text, potentially aligning it within the GUI element's component space. This is used as both the label text and shadow text in a Label GUI element and its subclasses.

Properties:

  • text: the text that is displayed in the component.
  • font: the font used to render the text.
  • fontSize: the size used to scale the font.
  • color: the color of the rendered text.
  • alpha: the separate alpha that is multiplied by the color's alpha value for fading the text in/out.
  • HAlignment: the horizontal alignment of the text within the total component shape.
  • VAlignment: the vertical alignment of the text within the total component shape.
  • offset: an x,y,z offset from the text's default position. Can be used to 'move' the text relative to where it would normally render as might be done for shadow or highlight layers.
  • layer: forces the text to sort above or below some other geometry with a layer value. Layers are specific to Lemur's geometry comparators that allow a crude layer-based front/back sorting within the scene graph structure.

TextEntryComponent