Skip to content

Drawing UI XML vs LUA

meldavy edited this page Nov 29, 2021 · 10 revisions

Based on first party IMC code, it seems to be the norm to draw and modify UI frames and controls through XML to define the look and feel of a frame. However, drawing UI purely with LUA is also totally viable, and I even may recommend it for third party addons.

The problem really is the available dev tools. We addon developers currently have no good way to test xml changes without having to repackage an ipf, restart game, reload the addon, and repeat. On the other hand, Developer Console addon allows us to reload LUA files, meaning that we can easily try making changes to the UI elements and test the changes in a very short amount of time. While developing an addon with its own frame FarmTracker, I ran into a really tough debugging experience just to get width and height correct. And at the end when I just needed to add an extra button and increase the height of the frame a bit, I gave up modifying the XML and just decided to make the change through code.

This page aims to document XML vs LUA differences.

Frame (xml vs lua)

XML

<uiframe name="sequentialpickitem" x="0" y="0" width="380" height="135" create="open">
	<frame layout_gravity="right bottom" margin="0 0 400 250"/>
	<draw drawtitlebar="false" drawframe="false"/>
	<option hideable="false" closebutton="false"/>
	<input hittest="false"/>
	<script OpenScp="SEQUENTIALPICKITEM_OPEN" CloseScp="SEQUENTIALPICKITEM_CLOSE"/>
	<animation frameOpenAnim="sequentialpickitem_open" frameCloseAnim="sequentialpickitem_close"/>
	<userconfig POPUP_DURATION="1.5" PUSHUP_ANIM_NAME="sequentialpickitem_pushup"/>
	<layer layerlevel="120"/>
	<controls>
	</controls>
</uiframe>

LUA (and base XML)

<uiframe name="sequentialpickitem" x="0" y="0" width="0" height="0" create="open">
</uiframe>
local frame = ui.GetFrame('sequentialpickitem');
frame:MoveFrame(0, 0);
frame:Resize(380, 135);
-- create="open" and hud="true" XML attribute equivalent in LUA is unknown, probably doesn't exist. This needs to be set through XML.
frame:SetGravity(ui.RIGHT, ui.BOTTOM);
frame:SetMargin(0, 0, 400, 240);
frame:ShowTitleBar(0);
frame:ShowFrame(0); -- not sure
frame:EnableHide(0);
frame:EnableCloseButton(0);
frame:EnableHitTest(0);
frame:SetOpenScript(SEQUENTIALPICKITEM_OPEN);
frame:SetCloseScript(SEQUENTIALPICKITEM_CLOSE);
-- frame:SetAnimation();  cannot find usage, either the following might work
-- fadein:SetAnimation("openAnim", "sequentialpickitem_open"); OR fadein:SetAnimation("frameOpenAnim", "sequentialpickitem_open");
frame:SetUserConfig("POPUP_DURATION", 1.5);
frame:SetUserConfig("PUSHUP_ANIM_NAME", "sequentialpickitem_pushup");
frame:SetLayerLevel(120);

Adding controls to frame

XML

<uiframe ...>
    ...
    <controls>
        <groupbox name="pickitem" rect="0 0 380 135" layout_gravity="left top" container="true" draw="true" scrollbar="false"  skin="normal_item_tootip_skin"/>  
        <picture name="pickitemslot" layout_gravity="left top" parent="pickitem" rect="0 0 380 135"  margin="15 10 0 0" image="jour_new_item1"/>
    </controls>
</uiframe>

LUA

local frame = ui.GetFrame('myframe');
-- you can also use frame:CreateControl() with same arguments, but CreateOrGetControl ensures control is created only once with the given unique name
local addontimer = frame:CreateOrGetControl("timer", "addontimer", 10, 10); -- args: type, name, width, height
local tipText = frame:CreateOrGetControl("richtext", "n_tip", 0, 0, 0, 0);  -- args: type, name, ?
local btnReset = frame:CreateOrGetControl("button", "reset", 60, 30, ui.RIGHT, ui.BOTTOM, 0, 0, 15, 8);  -- args: type, name, width, height, horiz align, verti align, margins

As good standard and convention, I recommend you always use the fully overloaded CreateOrGetControl() variant as it helps you properly constrain the size of the UI elements as necessary.

Properties

Below is the documentation of all common attributes that are used.

Any attributes that are not documented below are either:

  • Usage unknown
  • Probably not important
Attribute Description
name Unique identifier of the frame. Set to lowercase name of the addon.
x Horizontal position of the frame
y Vertical position of the frame
width Width of the frame
height Height of the frame
layout_gravity Combination of Horizontal alignment + Vertical alignment. However, a frame's gravity is recommended to be ui.LEFT and ui.TOP
moveable Can drag to move the frame
hittestframe HitTest is similar to "Enabled/Disabled" in various other UI frameworks. ie: if you have done iOS/Android UI or WPF/Winform development, a disabled control cannot be interacted with, clicked, modified, resized, etc. HitTest is a very similar concept. Click, drag, mouse over (hover), etc are all disabled, even drag to move becomes disabled.
layerlevel Z position of the frame. Higher value means it will be displayed over other frames with lower value.
drawframe Frame is not shown if false
frameskin Skin used for the frame. There will probably be an associated .tga file
visible Frame is displayed. Use frame:ShowWindow(0 or 1) to programmatically set visibility. This seems to force frame visibility to 1 even when unwanted sometimes. I recommend always setting to false in XML, and programmatically set to true ON_INIT if you want frame to always be visible.
alwaysVisible When set to true, addon stays visible during cutscenes. Should usually be set to false.
autoopen frame that is closed during cutscene is re-opened after cutscene
hideable Not sure. Supposed to prevent frame:ShowWindow(0) but it seems to be hiding frames fine.
closebutton When set to true a close button is displayed on title bar. But this should not be used, explained right below:
titlebar related stuff Titlebar is a control?ish thing usually only used by NPC dialog. Most, if not all, frames, including frames that seem to have title, like Inventory, also does not use a frame's titlebar attributes. Should always be set to false. drawtitlebar and drawtitlebarframe are some of those examples.

Create a base frame

When you look at many first party and third party addon.xml files, you may be quite overwhelmed at all of the different attributes and properties that just seem to make no sense. And my advice is just set everything to false, or just don't declare anything at all.

<uiframe name="myaddon" x="0" y="0" width="0" height="0">
    <draw drawtitlebar="false"/>
    <option visible="false" alwaysVisible="false" closebutton="false" hideable="false" moveable="true" autoopen="true"/>
    <input hittestframe="true"/>
    <layer layerlevel="10"/>
    <controls>
    </controls>
</uiframe>

The above is in my opinion, the standard XML of all addons that should not introduce any unwanted behavior. And I recommend only making modifications to the frame's properties through code in ON_INIT rather than in the XML to emphasize that you want to deviate from the recommended baseline options.