DreamGUI
The .dui Language

Overview

What a .dui file is, where it lives, the statements it is made of, and how it becomes a widget Blueprint's hierarchy.

A .dui file is the source of a DreamGUI widget Blueprint: a tree of widgets, their properties, their bindings and their animations, as text. The Blueprint names the file as its Source File, compiling the Blueprint reads the file and builds the hierarchy from it, and the designer writes its edits back into the file.

Text replaces the authoring step, not the runtime. A class compiled from a .dui and a class built by dragging widgets around are the same kind of class: the same generated class, one member variable per widget, the same property bindings, the same designer. The file is read at compile time, because that is the only moment that can declare a member variable per widget, resolve a binding against the functions the class declares, and check the whole tree. So one class has one file: SourceFile is a class default (EditDefaultsOnly), not something each instance can point elsewhere.

since 1.0.0

Where a file lives

Under a DUI/ directory: the project's own <Project>/DUI/, or an enabled plugin's <Plugin>/DUI/. Not under Content/: a .dui is source, not an asset. Under Content/ the cooker would walk it, the Content Browser would show a file it cannot open, and a plugin shipping widget classes would have to ship its sources inside its cooked content to have them found at all.

UI/Library.dui
UI/Settings.dui
UI/Components/Row.dui

A path to another file — in a Blueprint's Source File, or after use — is written one of three ways:

SpellingMeans
Panels/Settings.duiRelative to a DUI/ root: the project's first, then each plugin's in plugin-manager order; the first that has the file wins.
Plugin.MyPlugin:Panels/Settings.duiThe DUI/ directory of plugin MyPlugin.
D:/Work/Proj/DUI/Panels/Settings.duiAbsolute, used as written.

The first is convenient, and ambiguous the moment two roots hold the same relative path — which is what the second is for. When you pick a file with Set Source File..., the portable spelling is what gets stored: a relative path under the project's root, Plugin.X:… under a plugin's, and an absolute path only for a file outside every root (a path that then works on one machine). The Source File is editor-only data and is stripped at cook: the hierarchy was baked into the generated class long before.

File structure

A file is a list of statements. A statement ends at the end of its line or at a ;. Comments are // … to the end of the line and /* … */.

At the top level a file holds:

StatementPurposeSee
class /Game/UI/WBP_SettingsThe Blueprint this file is the source of. At most one.below
use …Another file or class made available to this one.use
resources { … }Named constants.Styles and resources
props { … }Properties this file's class declares.Writing a component
events { … }Events this file's class raises.Writing a component
style Name { … }A named set of lines.Styles and resources
timeline Name { … }An animation.Timelines
one nodeThe root of the tree.Nodes and values

The order of these is free: a style may be declared below the node that wears it. A file compiled into a class has exactly one root node; none or more than one is DUI2006 (with no root, the build also reports DUI5009, the parser's message saying why). A file with no root is a library: it can be used, and it cannot be compiled.

The mistakes a whole file can make: a character that cannot begin any token is DUI1001; a /* that never reaches */ is DUI1003; a { with no matching } is DUI2002, a ( with no matching ) DUI2003; a token where the grammar wants something else is DUI2001, and the message names both. Blocks or parentheses nested deeper than 256 are DUI2013: no hand-written or generated screen comes near that, so reaching it means a malformed file, and a line number beats recursing into a stack overflow that takes the editor — and the unsaved work in it — down.

The class line

The class line does two jobs:

  • It says which Blueprint the file is the source of. Compiling it into a different one warns (DUI6003).
  • Its path is the namespace of every localized string in the file, which keeps the keys stable when the file is renamed. Without one, the file's own name is the namespace.

class twice, with an empty path, or inside a node is DUI2007.

It is also where another file reads the class from when it names this one as a component with use "…" as Row; without the line, only the editor can answer, by finding the Blueprint whose Source File this is (see use). So give every component file a class line.

Attaching it to a Blueprint

Make a text-backed Blueprint

Create a DreamUI widget Blueprint in the Content Browser and pick DreamUI Text User Widget (UDreamTextUserWidget) as its parent class. Only that class's descendants carry SourceFile; with a plain UDreamUserWidget parent the designer toolbar shows no Source File button. Put it where the file's class line says, or edit the line to match.

Point it at the file

Open the Blueprint. The designer toolbar has a Source File combo button (it reads No Source File until one is set). Its menu holds:

  • Create Source File...: only while there is no file. Opens a save dialog (in the project's DUI/ directory, made first if there is none), writes a starter .dui there (a root and one centred label), points the class at it, and compiles — so the first compile answers "did this work" by appearing.
  • Set Source File...: choose an existing .dui, then recompile.
  • Open in Default Editor, Show in Explorer, Reveal in VS Code: open the file, select it in the file browser, or put VS Code's cursor on the line of the selected node.

Compile

The compile reads the file, builds the tree and installs it as the Blueprint's hierarchy. Mistakes show in the compiler results as DUInnnn diagnostics. A file that does not exist or cannot be read is DUI6001; one that parsed and produced no tree is DUI6002.

Show it

UDreamUIBPLibrary::AddWidgetOfClassToViewport adds the class to the viewport. The screen root, the raycaster and the event system are created on demand; nothing else needs configuring. The full path is in Your first screen.

From then on, saving the .dui recompiles the loaded Blueprints built from it, and the classes of files that use it. A class that is not loaded is not updated, and loading a Blueprint does not recompile it; Rebuild DUI in the Tools menu recompiles every text-backed widget Blueprint in the project, loading the ones that are not (it asks first). Deleting or renaming a file a loaded class reads is reported when it happens, rather than as DUI6001 at the next compile.

A first file

The plugin ships Content/Samples/HelloDreamGUI.dui, the smallest .dui that is still a real screen. Copy it into your own project's DUI/ before editing it: a plugin update overwrites the copy in the plugin folder. Here is a shorter file of the same shape:

// DUI/UI/Hello.dui
class /Game/UI/WBP_Hello

style Title {
    Font     = /DreamGUI/DefaultFont_DistanceField
    FontSize = 28
    Color    = #E6E9F0
    HAlign   = Center
}

Widget Root {
    // Anchored to all four corners with no inset: fills whatever it is added to, at any resolution.
    AnchorData.AnchorMin = (0, 0)
    AnchorData.AnchorMax = (1, 1)
    AnchorData.SizeDelta = (0, 0)
    + Overlay { }

    Overlay Card {
        AnchorData.SizeDelta = (420, 120)
        @slot { HorizontalAlignment = Center  VerticalAlignment = Center }

        // Drawn first, so it is behind: an Overlay stacks its children in declaration order.
        Image Backdrop {
            Brush.TintColor = #1B1E26
            @slot { HorizontalAlignment = Fill  VerticalAlignment = Fill }
        }

        VerticalBox Column {
            Spacing = 10
            Padding = (24, 28, 24, 28)
            @slot { HorizontalAlignment = Fill  VerticalAlignment = Fill }

            Text Heading : Title {
                AnchorData.SizeDelta = (0, 34)
                Text = "Hello, DreamGUI"
                WrapTextAt = 372
                @slot { HorizontalAlignment = Fill  SizeRule = Auto }
            }
        }
    }
}

Part by part:

  • class says this is the source of /Game/UI/WBP_Hello.
  • style Title is a named set of properties; Text Heading : Title makes the node wear it, and the node's own lines override the style's.
  • Widget is a rect with no visual, there to hold others; + Overlay { } attaches a layout container that stacks its children. Overlay Card and VerticalBox Column are the short way to say the same thing: the type is the container, and Spacing and Padding set the container's properties.
  • Image and Text are nodes whose visual draws something.
  • @slot is what this node asks of the panel above it, not a setting of its own. AnchorData.SizeDelta is the node's own rect; @slot SizeRule is the parent's arrangement of it. The distinction is worth reading twice.
  • A column asks how tall a child wants to be before it gives the child a width, so a text in a Fill slot names a width to break at with WrapTextAt: here the card's 420 less the column's 24 on each side. Without it the heading is measured at its own width, 0, as one character per line.

Diagnostics

Every message has a code, DUInnnn, printed as File.dui(line,col): error DUI3001: …. The front end recovers and reads on: a file with five mistakes reports five, not the first. The first digit says which stage refused:

CodesStage
DUI1xxxlexer: characters that are not a token
DUI2xxxparser: tokens that do not form the grammar
DUI3xxxmeaning: grammatical, but the tree does not hold together (a duplicate id, an unknown type)
DUI4xxxvalues: a value that does not fit the property it is written on
DUI5xxxbuilding the tree: reflection refused the write, or the target does not exist
DUI6xxxcompiling: the .dui could not be attached to its Blueprint
DUI7xxxwriting back: the designer's edit could not be put into the text

The VS Code extension reports what one file settles as you type, and while the editor runs it also delivers every compile's verdicts to the Problems panel; see the VS Code extension.

The other pages

On this page