Boo User Interface (BUI)¶
Warning
BUI is still supported in Empeld, and necessary as most of its core interfaces use it. However, it can be tricky to get started with, so we recommend ui2 as it's more reactive and bound to a context (A little vuejs/angular style, if you're familiar)
BUI’s mentality is an HTML-like interface connecting game logic with front-end appearance. It uses an XML markup to describe widgets, just like HTML, and a powerful scripting language (Boo, which is like Python), to modulate the appearance. You can modify, generate, remove, move, and animate widgets completely in Boo.
Code Docs¶
If you're looking for Widget documentation, see here: Widgets
Basic Layout¶
The basic layout of a BML file is:
Here's a very simple example, pulled from the chat UI:
<control>
<script src="chatroom.boo" />
<style>
<class name="messagebox" inherit="textbox">
<property name="BackgroundColor" value="#ddd1" />
<property name="BackgroundActivated" value="#ddde" />
</class>
<class name="messagequeue" inherit="terminal">
<property name="HasBackground" value="false" />
<property name="HasBorder" value="false" />
</class>
<class name="sendbutton" inherit="button">
<property name="BackgroundColor" value="#fff3" />
<property name="BorderColor" value="#6663" />
</class>
</style>
<view VAlign="Bottom">
<container width="475" Height="300" FitToParent="False">
<terminal X="0" Y="0" Width="475" Height="275" id="term" ShowExpiredMessages="False" MaxLines="500" class="messagequeue" />
<textbox X="0" Y="275" Width="400" Height="25" id="message" class="messagebox" Placeholder="Type '/'" />
<button X="400" Y="275" Width="75" Height="25" Text="Send" id="send" class="sendbutton" />
</container>
</view>
</control>
The important part of the BML is the control, script, and view. (Think HTML, Head, and body; respectively)
Text Formatting¶
There are currently a few built-in text formatting options. Each option
starts with a ^
character, followed by a command character, and ends
with a ;
. Here are the list of each and what they do:
Command | Example | Description |
---|---|---|
\ | \^asdf; | \ is the escape character, and will force-write anything after it without special interpretation |
# | ^#0F0; | Set the color of the following text using CSS 3 or 4(Alpha) digit hex colors. This case is green |
@ | ^@16; | Set the font size to, eg, 16 |
x | ^x0; | Set the x coord to 0, back to the left-hand side of the text block |
y | ^y23; | Set the y coord of the block to, eg, 23 |
> | ^>-12; | Offset the x coord by -12 |
| | ^|20; | Offset the y coord by, eg, 20 |
Development¶
On Jan 27 2015, we introduced a UI-development mode in the game, toggable via commandline. This allows you to load up your UI and scripts, and validate that your scripts don't throw any errors, and observe the appearnce. Run it by typing:
empeld.exe --dev-ui /path/to/my/buifile.bml
Whilst in there, it will watch for changes to the bml file and automatically reload.
The following hotkeys are available:
- F4: Draw guides on the GUI
- F5: Force-reload the UI file
- Escape: Exit
Widgets¶
BML is composed by a set of widgets, each has some common properties, but also have custom properties, fields, and events. You can find all of them described Widgets.
Common widget properties include:
Property | Type | Description |
---|---|---|
id | string | Set the ID of the widget to be referenced from the script |
class | string | Set the name of the style class to retrieve widget styling from |
data-* | any | You can use data-fields to store any extra data that you might want to reference from the Boo script |
Image Paths¶
A common use case specifies a picturebox image path. For technical reasons, these paths can't be relative, they must be absolute. To handle this case we've added a special variable that will return the current markup file's absolute path. eg, if you want to reference a file called "heat.png" in the same folder as the XML, you would do:
<picturebox Image="{path}/heat.png" Width="32" Height="32" />
BUI Glue¶
BUI Glue was a layer developed to help bind models to UI, without you having to keep track of updating UI state. Here's some example code.
There are 3 major parts:
- The glued C# model
- The BOO script that inherits GlueStick
- The BML file that has bound variables via the bind-* attribute
There are a few custom attributes that it adds to the markup:
Attribute | Description |
---|---|
bind-* | Bind the value of the attribute to the widget's variable. eg. if bind-Text="var" , the parameter Text will be bound to the model variable var |
format-* | This will do a string.Format on the bound variable before it is set to the bounded widget field. eg. format-Text="Before {0}" , will put "Before" before any change on Text field. |
transform-* | This will pass any bound variable through a method before it is passed to the Widget's field. eg if transform-Text="CustomTransform" , it will pass the bound model variable through the boo script's method CustomTransform before inserting into Text . |
reconstruct-* | This will pass any bound variable through a method before it is passed back up to the application model. eg if a textbox text is '123', and the reconstruct method makes it an int, it will pass it through that before returning to the program |
event-*eventname* | Will invoke a bound name to an event. eg, on a button you can have event-ButtonClickEvent="doSomething". Then, a method that has been bound code-side will invoke a method with void return and void args "doSomething" |
Plugin¶
public class MyModel{
[Glue]
public string MyVar;
[Glue(Writable = false)]
public int MyOtherVar{
get{return 1;}
}
}
var model = new MyModel();
var control = screen.AddControl("path/to/my/control.bml");
control.GlueModel(model);
model.MyVar = "Hithar"; //The UI will automatically update
BML¶
<?xml version="1.0" encoding="utf-8"?>
<control>
<script src="myscript.boo" />
<view>
<label x="0" y="0" bind-Text="MyVar" />
<label x="0" y="30" bind-Text="MyVar" format-Text="Something is before {0}" />
<progressbar x="0" y="60" Width="100" Height="25" bind-Value="MyOtherVar" transform-Value="customTransform" />
</view>
</control>
Boo¶
#include "gluestick.boo"
class Controller(GlueStick):
def constructor(ui):
super(ui)
def customTransform(v):
return v + 5
Special Boo Methods¶
Besides the constructor and the destructor defined by Boo, Empeld has a few additional bound methods it will call.
Name | Description |
---|---|
init() |
Called after the entire UI has finished loading. Control does not exist on screen yet |
map() |
Called when the UI is mapped to a parent widget. Doesn't necessarily mean it's visible |
unmap() |
Called when the UI is unmapped to a parent widget. |
focus() |
Called when a widget gains focus |
__losefocus() |
Called when a widget loses focus |
Default Boo Controller¶
If you find you don't need any customer properties, methods, or transforms, you can use the default glue controller by referencing:
<script src="ui/glue.boo" />
Widget XML Mapping¶
This switch statement defines the mapping of the XML name to the widget type as described in Widgets
switch(name.ToLowerInvariant())
{
case "label":
return new Label();
case "wrappedlabel":
return new Label(){AllowOverflow = false};
case "header":
return new Label(){Size = 26};
case "picturebox":
return new Picturebox();
case "bitmap":
return new Bitmapbox();
case "sprite":
return new Sprite();
case "button":
return new Button();
case "checkbox":
return new Checkbox();
case "progressbar":
return new ProgressBar();
case "textbox":
return new Textbox();
case "numberbox":
return new Numberbox();
case "verticalscrollbar":
return new VerticalScrollbar();
case "block":
return new Container(){FitToParent = false};
case "container":
return new Container();
case "dialog":
return new Dialog(){HasTitle=false};
case "window":
return new Dialog(){HasTitle=true};
case "frame":
return new Frame();
case "terminal":
return new Terminal();
case "keycapturer":
return new KeyCapturer();
case "dropbox":
return new Dropbox();
case "griddropbox":
return new GridDropbox();
case "scroller":
return new Scroller<Widget>();
case "listbox":
return new Listbox<Widget>();
case "option":
return new OptionPicker();
case "hidden":
return new Hidden();
}