GUIToolkit

From The Battle for Wesnoth Wiki
An Example of a Dialog, created using Wesnoth's new GUI toolkit, GUI2

Introduction to GUI2

This is a new toolkit aiming to make it possible to make the look of Wesnoth fully skinnable. As such, it separates the actual appearance and layout of the user interface (defined using WML) and the control logic (such as event handling, done using C++ or Lua). The C++ or Lua code is also reponsible for actually loading the WML and showing the widget on the screen.

This is being used in Wesnoth to make it easier to optimize for different screen resolutions, and to simplify and make it easier to maintain GUI code.

Initially released with Wesnoth 1.6, it is now the default and being used for the majority of dialogs as of Wesnoth 1.18.

The engine has two parts:

  • Widget definition, which defines how a widget looks and how it is laid out, if it contains multiple widgets inside (containers, see below). (Reference)
  • Window layout, which defines how a certain window looks and how it uses the widget definitions. (Reference)

Note: It may be slightly confusing, but a "window definition", defined by the [window_definition] tag, is also a type of "widget definition", as "window" is a type of "widget".

These Widget and Window definitions are contained within a toplevel [gui] tag. Each [gui] tag corresponds to a GUI2 Theme. The [gui] tag also contains some keys to specify various properties of this new theme.

Basic Structure

The basic structure of a dialog definition is that it contains one or more nested [resolution] tags, inside which the actual content of the window resides. The multiple resolution tags allow the designer to specify various interfaces for various screen sizes and resolutions. For dialogs defined in C++, all of this goes in a [window] tag. So a basic template for a WML file that defines the layout of a user interface would be :

[window]
    # Window attributes/keys
    # such as id and description
    
    [resolution]
        # Resolution keys
        
        # Tooltip and Helptip subtags, mandatory
        # just specifying an id is enough.
        [tooltip]
            id = "tooltip"
        [/tooltip]
        
        [helptip]
            id = "tooltip"
        [/helptip]
        
        # the main contents
        [grid]
        [/grid]
    [/resolution]
    
    # More resolution tags can be added here
    # to support different layouts for different
    # screen resolutions.
[/window]

Layout

Wesnoth's UI toolkit, GUI2, has a rather simple layout system. Instead of allowing dialogs to specify exact coordinates, it only deals with relative sizes and locations. It is based on a grid based system, with complicated layouts being specified by multiple levels of nested [grid]s.

The UI is constructed from various widgets. Some of them can contain other widgets, such as window or grid, so we will call them container-type widgets or just containers for short, and we will use simple widgets to refer to the rest of them, which cannot contain other widgets inside themselves. There can also be a third type, a widget that contains other widgets inside it, but this group of widgets behave as a cohesive group that performs a specific task, examples being scroll_label or spinner. We will call this third type compound widgets.

There are 5 keys relevant to positioning widgets in a GUI2 container:

Where these keys can be used is detailed below. Usually, they are used inside a [row] or [column].

It is to be kept in mind that GUI2 functions on a grid system. Each grid is made up of rows, and every row must have the same number of columns. The columns may contain either other grids or widgets, such as buttons.

[grid]
    [row]
    # row keys here
        [column]
        # column keys here
        # the widget or a subgrid goes here
            [button]
            # button keys
            [/button]
        [/column]
        # More columns here
    [/row]
    # More rows here
[/grid]


Now, let's talk about each of the above keys:

grow_factor

This key can be present in either rows or cells, as so:

[row]
    grow_factor = 1
[/row]

Or

[column]
    grow_factor = 1
[/column]

Growth factors control the relative growth size of the row or column. It's important to remember that the grow_factor of a row controls vertical growth, while the grow_factor of a column controls horizontal growth.

For example, if two columns were positioned next to each other, the first with a growth factor of 0 and the second with 1, the second column would grow at a regular rate, while the first would not grow at all. This holds true for rows, and it is important to get grow_factor values right. An easy way to remember how to use this key is to assign it a value of 0 if you want that row or column to grow much less relative to another.

Keep in mind, this key only controls the growth of the rows and columns themselves. Control over the widgets' sizes is dictated by the remaining keys.

horizontal_grow and vertical_grow

Boolean keys that decide whether the contained widgets grow or not. Mutually exclusive with horizontal_alignment and vertical_alignment.

horizontal_alignment and vertical_alignment

String keys that set the alignment of the contained widget. Possible values :

  • horizontal_alignment : left, center, right
  • vertical_alignment : top, center, bottom

Widget Definitions

Definitions determine the appearance and internal layout (for compound widgets and containers) of a widget. Think of them as a theme for a widget. Each widget has at least one definition, default, plus several more. They are defined using a tag like [widget_definition] (replace widget with the name of the widget). For example, [button] has its definitions defined inside corresponding [button_definition]tags. To use a particular definition, the definition key is used, when a widget is specified in a wml file. Put the definition's name in this key. If the definition key is not present, the engine assumes the definition default.

Example :

A Label with definition "title"
[label]
    id = button1
    label = _ "A Title"
    definition = title
[/label]

Showing the Dialog

There are two approaches:

  • The Internal (C++) approach: Needs a dedicated C++ class that extends from modal_dialog and an associated builder class. An older documentation is available is the tex documents here. Examples of GUI2 dialogs created this way can be found in the game's source here and their associated WML files are here.
  • The UMC (WML + Lua) approach: The recommended method for UMC content to show their own GUI2 dialogs. Here, you will have to load the corresponding WML file by passing its path (and pre/postshow) functions to the gui.show_dialog lua function.

Custom themes

(Version 1.19.4 and later only)

Add-ons can also define their own custom gui2 themes by writing their own [gui] tags. This allows reskinning most of the dialogs and UI seen in Wesnoth. Users will be able to switch between different themes using the Preferences > Display > UI Theme button, or the Lua gui.switch_theme function (inside a scenario). To use this functionality:

  1. Add a gui-theme.cfg file in the Add-on root. (This is a special filename, just like achievements.cfg or cores.cfg. This is the starting point for your custom theme.
  2. Write the [gui] tag inside this file. For the syntax of the [gui] tag, see here. Examples are the default.cfg file and the modern.cfg files from the two core themes.
  3. Include any files/folders inside your [gui] tags, or for small themes, you can put them inside the [gui] tag in the gui-theme.cfg file instead. Note that you don't need to include the gui/macros folder as it will be included automatically.
  4. Inside those folders, put your widget and dialog definitions. Pay notice to `id` keys inside window and widget definitions as these will replace the corresponding definitions from the core default. It is not necessary for a custom theme to provide definitions for all existing windows/dialogs and widgets. Anything not provided by a custom theme will use the corresponding definition from the default theme instead.
  5. You can also change any other settings inside the [gui] tag, such as UI sounds and various UI parameters. Last but not the list, you can replace the tips shown in the titlescreen by replacing the {tips.cfg} with your own file inclusion. If you want to append your own tips, include your cfg files after/before that line. For syntax of the tips files, see here.

Example from modern.cfg:

#textdomain wesnoth-lib
###
### Defines a gui, all widgets and windows used in a certain 'theme'.
###

### Definition of the modern theme

[gui]
	id = "modern"
	description = "Modern"

	[settings]
		# change any settings here
		popup_show_delay = 200
		popup_show_time = 5000
		help_show_time = 0
		double_click_time = 500
		repeat_button_repeat_time = 350

		# can also change these sounds
		sound_button_click = "button.wav"
		sound_toggle_button_click = "checkbox.wav"
		sound_toggle_panel_click = "select.wav"
		sound_slider_adjust = "slider.wav"

		# and this help message
		has_helptip_message = _" (Press ‘$hotkey’ for more information)"
	[/settings]

	{tips.cfg}

	{gui/themes/modern/widgets}
	{gui/themes/modern/dialogs}
[/gui]

Replace these lines with WML paths to your folders.

Links

  • GUIVariable - Documents the types of GUI2 variables available.
  • GUILayout - Describes how to place widgets in a window.
  • GUICanvasWML - Describes how the drawing system works. Useful if you want to use the [drawing] widget to show some custom drawing or if you're building a new widget definition (aka a new theme for a widget.)

Todo list

Here's a(n incomplete) list of items which still need to be done.

  • The layout engine can still have problems with not finding a solution and stop.
    • Make sure labels buttons etc can shrink and show ellipses to shrink small enough
    • Optimize the shrink algorithm
  • The code for the events is not entirely to my liking and I want to look at using signals instead, events are there bug not yet used everywhere.
  • EasyCoding#GUI2_related_features
  • Improve the markup for the text
  • Convert more dialogs
This page was last edited on 22 September 2024, at 15:05.