Skip to content

Instantly share code, notes, and snippets.

@x3ro
Last active October 8, 2015 00:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save x3ro/3252874 to your computer and use it in GitHub Desktop.
Save x3ro/3252874 to your computer and use it in GitHub Desktop.
WIP - Terasology Themeable GUI

This is a work-in-progress documentation for a themeable GUI prototype for Terasology, based on this forum thread and this forum thread.

Introduction

In order to provide a more easily customizable GUI, the current user interface system is to be enhanced by allowing the user interface definition through JSON files, separating the user interface logic from its actual appearance.

The current user interface system is roughly devided into the following packages:

  • gui.framework: Contains basic, abstract GUI functionality that is to be re-used throughout the entire UI. A notable subclass is UIDisplayContainer which is the base-class for all UI elements.

  • gui.widgets: Contains concrete implementations of UI elements, such as UIButton or UIText.

  • gui.menus: Contains structural menu definitions, i.e. what elements a certain menu contains and how the components making up that menu are configured.

The classes within gui.menus are to be completely replaced by JSON-style definitions.

The visual settings within gui.menus are to be completely replaced by JSON based definitions. The currently existing classes, such as UIMenuMain, shall contain only the event handling code, e.g. for click events on buttons.

Questions open for discussion

  • How should the visual states of UI elements be implemented, that is, for example, how should we implement a button switching to hover state and having the hover properties automatically applied to it.

  • If an invalid or unsupported property is used in the style definition, should there be an error or should it be ignored?

Concept

User interface definition are stored in JSON files. There are two types of definitions:

  • Style definitions, using the file extension .style.json.
  • Layout definitions, using the file extension .layout.json.

Both layout and style definitions will have a separate asset type, making them easily loadable by the existing asset system (Source).

Parsing the JSON files should be implemented using the already employed GSON parser, which Immortius knows well. A solution for the "JSON String -> Enum Value" problem is explained here.

Style definitions

Style definitions define the appearence of UI elements, and are re-usable in layout definitions. The style definition is designed to replicate CSS properties, so that it is easily adoptable. An example of a style definition might look like this :

{
    "style": {
        "size": [120, 100],
        "verticalAlign": "center"
      }
}

As Adeon stated, the system should stick close to the property names used by CSS, since it'll lower the barrier of entry, but only if the properties actually behave like they do in CSS.

TODO: Add missing, needed properties from here. {: .callout .alert}

Style Properties

There are two types of style properties that can be used on widgets, "global" and "local":

  • Global properties are available for every UI widget (but not necessarily for window styles!), and are implemented in UIDisplayContainer or one of its parent classes.

  • Local properties are those which are implemented in a specific UI element, such as UIButton.

A property for a UI element exists if there is a setter method that adheres to the naming convention set$PropertyName, e.g. if a UI element class has a public method setFoo() with a single parameter, it would have a property foo.

Additionally there are properties for the window, which are made available by the UIWindow class or its parents.

If any property is used in style definition which does not have an equivalent setter in the UI element, an exception MUST be thrown.

Layout definition

The layout definitions specifies the structure of a menu, e.g. the main menu. It contains an entry for each UI element which is to be displayed.

JSON layout definitions are loaded into the LayoutDefintion class, which can then instantiates either a UIWindow or a subclass of a UIDisplayContainer.

An example for a layout definition:

{
    "id": "main-menu",
    "controller": "MainMenuController",
    "children": [
        {
            "id": "start-game-button",
            "component": "UIButton",
            "style-class": "default-button-style",
            "style": {
                "position": [100, 100]
                "text": "Start Game"
            }
        }

        {
            "id": "exit-game-button",
            "component": "UIButton",
            "style-class": "default-button-style",
            "style": {
                "position": [100, 150],
                "text": "Exit Game"
            }
        },

        {
            "id": "version-number",
            "component": "UIText",
            "style": {
                "text": "v0.1",
                "position": [200, 200]
            }
        }
    ]
}

The root object has three mandatory parameters:

  • id: The ID of the menu. This string must uniquely identify the menu so that it may be referenced within the code.

  • controller: The controller implements the event handling logic for a menu and its widgets.

  • children: An array of children UI elements for this menu. These have a numer of required and optional arguments explained in the following sections.

Events

This part (events) is currently postponed until the central parts of the JSON GUI system are in place. It may then be re-evaluated whether or not this is necessary and how much work it'd be to re-structure the existing event handling mechanism. {: .callout .danger}

Every component may support a number of events, i.e. click or hover. By default, all events will be delegated to the layout controller's handleEvent method. If an element is often re-used across multiple layouts, a per-element controller may be defined.

In order to avoid large switch statements in the handleEvent method, one can specify event handler methods for every UI element using the events property, as illustrated above in the start-game-button. Using that technique will automatically invoke the startButtonClick method with the following signature:

public void startButtonClick(UIDisplayContainer element);

The controller then receives events from the UI elements and executes code accordingly, that is, it adds behavior to the menu. A controller for the exit-game-button might look like this:

public class ExitGameButtonController implements UIController {
    public void handleEvent(UIDisplayContainer element, String eventType) {
            // Behavior!
        }
}

Where the UIController interface would look as follows:

interface UIController {
    public void handleEvent(UIDisplayContainer element, String eventType);
}

Required arguments for UI elements

  • id: The ID is the only way to access a UI element from code after creation. UIWindow features a method getChild(Class type, String id) for that purpose.

  • component: Specifies which UIDisplayContainer implementation shall be used to render the element.

Optional arguments for UI elements

  • style: Used to set style parameters directly in the layout definition, overriding those from any style-class that may have been specified. Also see Style definition.

  • style-class: The style class to be loaded for this element.

UIDisplayContainer

Incomplete! {: .callout .danger}

This is the base class for all UI widgets. That is, when specifying a child in a layout definition, a subclass of this class will be instantiated (which one is indicated by the "component" field).

Controller

Incomplete! {: .callout .danger}

The controller is where the event (and any other) logic for a menu and its children is implemented. When a window is created from a layout definition, the specified controller is instantiated after all children have been created and all styles have been applied, and its registerEventHandlers method is invoked. Access to the window (and thus to it's children, retrievable by ID) is available to the controller's "window" field.

Caveats

  • In the JSON definition, the elements are added to the UI in the same order they are listed in the file. Elements added later are rendered above those elements that have been added earlier. This means that if you have, for example, a background image filling the entire screen, this image must be added first, or all elements that were added before will be covered by the background image.

Summary of decisions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment