jMonkey LorisGUI - Visuals

Why controls look the way they do

Home Overview Visuals XML Style Model Controls Components Layouts
 

Components

Every LorisGUI control is based on a jme Node. A node has no inherent visual appearance, but rather provides a parent structure for a set of jme Geometrys with a common location. A LorisGUI control uses an ordered set of Components to provide the geometries that share a common x/y origin, and which are layered in z. Each component can provide a color, or texture, or text (and some only affect the size/position of the control). The largest (in width/height) component is the farthest away from the user. Each subsequent component is nearer to the user, but smaller, allowing some portion of component behind to be visible.

       
	    	+----------------------------------------------------------------------------+
	    	|                      Farthest/Deepest Component                            |
	    	|   +--------------------------------------------------------------------+   |
	    	|   |                      Nearer Component                              |   |
	    	|   |   +------------------------------------------------------------+   |   |
	    	|   |   |                   Closest Component                        |   |   |
	    	|   |   |                                                            |   |   |
	    	|   |   |                        (text)                              |   |   |
	    	|   |   +------------------------------------------------------------+   |   |
	    	|   |                          (background)                              |   |
	    	|   +--------------------------------------------------------------------+   |
	    	|                               (border)                                     |
	    	+----------------------------------------------------------------------------+
	    

A typical usage of these components is to define a border color in the deepest component, a background color/image in the next closer component, and display text in the closest component. There are no real restrictions on the layering, each Loris control uses any set of layers most suited to the visual appearance it is trying to achieve.

Setting the size and position of each control is a two step process. The first step is to determine a preferred size. Sizing proceeds from front-to-back. The component closest to the user defines the size it wishes to have. In the case of text, this would be the width and height needed to display a given run of text in a particular font. Each deeper layer has a chance to add to that size. So if the background behind the text wanted to include some gutter area around the text, it could increase the preferred width and/or height. Finally, if the deepest component wanted to define a 2 pixel border around the whole thing, it would add 4 to the width and height.

The second step is to define the position and actual size. Positioning proceeds from back-to-front. So the farthest component is given a position and actual size. It positions/sizes itself accordingly, and then adjusts the running position/size based on what it consumes. Each subsequent component does the same, adjusting the running position/size for the next component. In this example, the border starts at the initial position/size and displays itself. For its 2 pixel border, it moves the position left 2 and down 2, and subtracts 4 from the width and height. The background then displays itself, making adjustments for any gutters. Finally the text displays itself in the remaining space.

 

Dynamic State

A key design element of any GUI are the dynamic visuals needed to reflect enabled/disabled, active/inactive, focus/nonfocus, etc. The end user needs a visual clue that the GUI is responding to their actions. That is why a button lights up when the mouse is over it. Rather than requiring the developer to create their own set of visual effects, Loris has an in-built state mechanism defined for every control.

Not all states apply to every control, but when they do, the states are:

  • Disabled - which means the control is inert
  • Active/Inactive - when active, the control is 'on', like a CheckBox being checked
  • Focus - indicates the control will process keystrokes
  • Hover - indicates the mouse is over the control
  • Pressed - indicates the mouse button is down (over the control)
When disabled, no other display state applies. But all combinations of all other states are possible. In other words, a control can be active, with focus, with hover, and the mouse button is pressed. Loris keeps track of this state.

Sensitive to this state are a set of DisplayAdapters that manage different visual effects. Adapters can change the color, alpha, texture, and text of the component, based on the current display state. Adapters are assigned to the components so that different layers display different effects, and multiple adapters can be assigned to a single component. The result can be checkbox with a border that changes color when it has focus, with a different icon for when active or not, and an alpha fade when disabled.

 

Dynamic Size/Position

A GUI consists of more than a single control. Multiple controls are included within a Container parent control. Like any other control, a Loris container is built up of components. This includes border and background components, but its inner-most component is a layout of child controls. Different layouts provide different options for ordering the children, and for determining how the children fit into a given size.

The layout component probes each child control for its desired preferred size. This is the first step mentioned above. It then decides where the various children are positioned, and applies step two to each child. It is the layout's responsibility to account for any differences between what size is preferred, and what is actually available. Different types of layout allocate excessive size differently. For example, the border layout assigns any excess size to its 'center' element, while the tiling layout has options to spread any excess across rows and columns.

There is a special Insets component that can be included as the deepest component on any control. Insets has no visual representation. Instead it adjusts position/size for all other components. Insets can apply a fixed, static x/y adjustment. This acts like a gutter, providing a margin around the control as a whole. Insets can also define dynamic adjustments. In this case, if the control is granted more size than the control's preferred size, then the inset adjusts the initial position, and absorbs any excess. The dynamic values represent a percentage of the excess to apply to the left/right and top/bottom margins. The most typical example is a dynamic inset of 50% for both left/right and top/bottom. The result is a control centered in its available space.

A container is a standard control, and may be a child of a parent container. This nesting can be applied to any depth, with different layout types applied to the different containers. You thereby have incredible flexibility in positioning your controls without requiring any fixed, absolute coordinates. But you do have to start somewhere, so there is a special, top-level container which is a Screen. By default, a Screen is sized to match the physical dimensions of the active jme Display. You can then use the layout of the Screen to position your set of controls.

 

2D versus 3D

GUIs are typically organized for 2D display. Loris follows the standard GUI layout convention of elements starting in the upper left corner, filling in to the left and down the page. This matches the standard jme definition of x, but flips the definition of y. z is used strictly to manage the visual layering of all controls.

While Loris has been organized for 2D processing, the Lemur underpinning upon which Loris is based did not impose a 2D restriction. Loris has retained this capability. If you define the Loris controls within the jme GUI viewport, then 2D rendering takes place. But if you define the Loris controls within the standard jme 3D viewport, 3D rendering occurs. This means you can position your camera to the side and even behind the control. There may not be much to see there, but it is possible. Keep in mind that Loris is still operating on physical screen dimensions. So a Loris control is often sized at a significant fraction of of an 800x600 (or larger) screen. If elements in your 3D universe are in the 50 to 100 unit range of sizes, then all is good. But if your universe operates at much smaller sizes, then the Loris controls will be HUGE. Loris provides you with scaling support within the Screen control.

Most Loris components are 2D in nature. They have a color, or a texture, or text, but no inherent depth. The contents are provided by some kind of 'fill', which is based on a simple jme Quad or on bitmap text. But Loris does support .loris.geom.FillGeometry that renders a 3D mesh within the component. In the 2D world, the camera flattens this out and you only see the end of the solid. But in the 3D world, the mesh retains its 3D shape.

Another possibility is a 2D mini-port. This is a viewport on the display that is not full size. You have to construct such a viewport yourself, but Loris can be wired up to use it. Just remember that such a viewport is inherently scaled down to match the portion of the screen. 2D Loris components do not scale down well at all. Text gets blurry and single pixel borders can simply disappear. To counteract this, you can apply an inverse scale to the root gui node. So if your viewport scales down by 2, then scale your gui node up by 2. The Loris components will then render visually intact. But of course, you have a lot less real estate to work with.