UI Subsystem - coldrockgames/gml-raptor GitHub Wiki

Basic Information About UI in Raptor

  • All controls are implemented to auto-resolve strings through the LG Localization subsystem and format text through Scribble.
  • All controls can be docked, aligned, anchored or positioned freely in their container.
  • Available containers are the desktop/game ui layer (accessible through the UI_ROOT macro), the Window control or the invisible Panel control.

4 Ways to create your UI

1. Draw them in the Room

The most simple and visual way is to draw your UI directly on a specific layer in the Room Editor. However, this approach has several major disadvantages:

  • You lose all ability of a dynamic interface as it's drawn on static coordinates. Screen Resolution changes might become a nightmare.
  • You can not put controls in containers and build a control hierarchy.
  • You can not "dock" or "align" controls to their neighbors.
  • You can not "center" or "spread to 66% of screen size" anything, you have to tinker and calculate it all by hand.
  • Many controls change/adapt their sprites at runtime (through the skin for instance), therefore the "preview" you see in the Room Editor will never match what is actually displayed during the running game. This quite eliminates all the "visual" advantages you might see at first glance, when dragging things in the Room Editor.
  • on_click callbacks need to be public functions when set directly in the variable definitions of the room editor. Again: No dynamics here.
  • ...this list could be continued for quite a while...

2. Use the ControlTree object (classic)

Coding your UI through the ControlTree gives you flexibility with callbacks, enables docking, aligning and all other layouting features of the raptor UI subsystem and makes changes easy to implement.

Building your UI with the ControlTree is a matter of using the builder pattern in a simple and intuitive way. This example here shows the entire UI of the Unit Test Room that comes with Raptor Pro:

UI_ROOT
  .clear_children()
  .add_control(Panel, { min_height: 144 })
    .set_dock(dock.top)
    .set_padding_all(12)
    .add_control(Label, struct_join(label_struct, {  text: "[scale,1.5][ci_accent]UTE - raptor Unit Test Engine", }))
    .set_position(48, 32)
    .add_control(Label, struct_join(label_struct, {  text: "[scale,1.5]Discovered Test Suites", }))
    .set_position(48, 84)
    .add_control(Label, struct_join(label_struct, {  text: "[scale,1.5]Detailled Test Results", }))
    .set_position(900, 84)
  .build()
  .add_control(ScrollPanel, struct_join(scrpanel_struct, { min_width:1000, }))
    .set_content_autofill(true, 4)
    .set_content_object(UnitTestResultsViewer, { detail_mode: true, })
    .set_dock(dock.right)
  .build()
  .add_control(ScrollPanel, scrpanel_struct)
    .set_content_autofill(true, 4)
    .set_content_object(UnitTestResultsViewer, { detail_mode: false, })
    .set_dock(dock.fill)
  .build()
.build();

It can be "read like a book", with intuitive function names, a clear hierarchy and when you do your indendation cleanly, you can cleary see, what's going on.

3. Use the ControlTree object (styled)

As you can see in the example above, while the structure is clear, there's quite a lot function calls to write. This is not a performance problem, as the UI is built only once during the RoomStart event.

There is a second way to write your interface, with the so-called style_struct. If you're closer to web development, you might be used to such structs (or xml-attributes) like "padding_left: 25" and things like that. If you prefer this way of communication with the ControlTree, then maybe the approach shown here is more for you:

The key element here is also the .add_control method of the ControlTree. It offers a third argument, the style_struct. In this struct you can write all properties supported by the ControlTree without having to write the function calls.

The same control tree for the Unit Test Room using the style-struct would look like this (the code below delivers the same layout as the example above):

UI_ROOT
  .clear_children()
  .add_control(Panel, { min_height: 144 }, { dock: dock.top, padding: 12 })
    .add_control(Label, struct_join(label_struct, { text: "[scale,1.5][ci_accent]UTE - raptor Unit Test Engine", }), { position: [48,32]})
    .add_control(Label, struct_join(label_struct, { text: "[scale,1.5]Discovered Test Suites", }), { position: [48,84]})
    .add_control(Label, struct_join(label_struct, { text: "[scale,1.5]Detailled Test Results", }), { position: [968, 84]})
  .build()
  .add_control(ScrollPanel, struct_join(scrpanel_struct, { min_width:1000, }), { 
      dock:              dock.right,
      content_autofill:  [true, 4],
      content_object:    [UnitTestResultsViewer, { detail_mode: true, }]
    })
  .build()
  .add_control(ScrollPanel, scrpanel_struct, { 
      dock:              dock.fill,
      content_autofill:  [true, 4],
      content_object:    [UnitTestResultsViewer, { detail_mode: false, }]
    })
  .build()
.build();

4. Use RichJson and the style-array feature

The style-array feature is the most sophisticated way to create your UI. You can do this in a json struct or directly in code by creating an array for the control hierarchy, as shown here (again: It doesn't matter how you build this array. Dynamically in your game, through rich json or by simply coding the array).

Again, the code shown here produces the exact same result for the UI of the Unit Test Room as the examples above:

[
  [ Panel, { min_height: 144 }, { dock: dock.top, padding: 12 } ,
      [ Label, struct_join(label_struct, { text: "[scale,1.5][ci_accent]UTE - raptor Unit Test Engine", }), { position: [48,32]} ],
      [ Label, struct_join(label_struct, { text: "[scale,1.5]Discovered Test Suites", }), { position: [48,84], name: "middel"} ],
      [ Label, struct_join(label_struct, { text: "[scale,1.5]Detailled Test Results", }), { position: [968, 84]} ],
  ],

  [ ScrollPanel, struct_join(scrpanel_struct, { min_width:1000, }), { 
      name:        "der_rechte",
      dock:        dock.right,
      content_autofill:  [true, 4],
      content_object:    [UnitTestResultsViewer, { detail_mode: true, }]
    }
  ],

  [ ScrollPanel, scrpanel_struct, { 
      name:        "new_style",
      dock:        dock.fill,
      content_autofill:  [true, 4],
      content_object:    [UnitTestResultsViewer, { detail_mode: false, }]
    }
  ]
]

With such an array at hand, you just send it to one of the two available global functions for Styled UI:

  • styled_ui_create_room(_style_array) for creating the room UI. Use this to apply the array to the UI_ROOT of the room.
  • styled_ui_create(_container_or_tree, _layer_or_depth, _init_struct, _style_array) to apply the array to any control tree. This function can even create a new instance for you. As an example, you could create your window layout as a style-array and send it to this function, which will not only render the layout, it will even create the window instance for you and apply the layout in that window.

Read more details about this feature on the Styled UI page.

Skins and Themes

Also, there's theming and skins implemented in raptor. Basically, this is a set of predefined colors you want to use throughout your game's ui. All controls are set up to use the app theme through defined constants. Activate your initial Skin and Theme in the Game_Configuration. They can be changed later on-the-fly during the running game.

UI Themes and UI Skins tell you everything, you need to know about this feature. Skins and Themes are also capable of being dynamically loaded at runtime and fully support the Raptor RichJson Format.

Available controls

The controls are built object-oriented (as far as GML allows this) and follow this hierarchy: raptor-objects-ui drawio

Control Description
Base Controls
Label The most basic UI element, just a scribble string (text)
Containers
Panel An invisible container that helps you aligning your layout
ScrollPanel A Panel with scrollbars where you can assign any Content and it will scroll it, no matter, how big it is.
This control uses the new function gpu_set_scissor() provided by GameMaker, makes the clipping area directly on the graphics card and is therefore blazing fast!
Window A very powerful, movable, sizable dynamic window which can be used for every dialog
WindowXButton A special ImageButton, used by the Window object
MessageBoxWindow A special Window, used by the MessageBox Functions
MessageBoxWindowXButton A special WindowXButton, used by the MessageBoxWindow object
Tooltips
Tooltip Little info-tag when hovering the mouse over a control
Clickable Controls
TextButton A standard button, containing a scribble-string as caption (text)
ImageButton A standard button, containing a scribble-string as caption (text) and an image as icon (sprite_to_use)
Slider A highly configurable value slider control
Checkable Controls
CheckBox A standard checkbox
RadioButton A standard radiobutton. You can have multiple radio groups in one container
Input Controls
InputBox A comfortable text input control with lots of features. This control offers so much functionality, that I spent it its own InputBox sub page
Mouse Cursor
MouseCursor A sprite-based mouse cursor replacement for the standard mouse cursor. Just drag one into your room in any layer. It works transparently and fully automated
MouseCursorCompanion An attachment-object for your MouseCursor

Additional controls delivered with raptor

I have created some lightweight controls, which help a bit with common tasks. Nothing too special, but still things, you do over and over again, especially when you do game jams and create new games frequently.

They can be found in the _gml_raptor_ui_\helpers folder of the project template and are ready-to-use, without any configuration needed.

Control Description
GameVersionLabel Child of Label which reads the version information from the game's version.json file and sets this as its text. This is the most easy way to display your version/build at the main screen
CopyrightLabel Also a child of Label, with text set to =legal/copyright, so it will display copyright information from the localization file
FPSTracker A simple control that measures the render time of frames and displays a text like 60 FPS
StoryTeller A child of Label which utilizes scribble's Typist, to slowly write text, as if some character would be talking

When you start the Example Project Demo, you see these objects in action.


Read on in UI Themes or UI Skins or head directly to Base Controls.

⚠️ **GitHub.com Fallback** ⚠️