Runes of Magic Wiki
Explore
Main Page
All Pages
Interactive Maps
navigation
Main Page
Recent changes
Community portal
Random page
Admin noticeboard
wiki navigation
Classes
Equipment
Regions and Cities
Quests
Bestiary
Gathering
Processing
Production
Sitemap
Transmutation Chart
Attributes
portals
Runes of Magic
Joint RoM Forum
Official Games Status
RoM Discord Servers
Unofficial Server Status 1
Unofficial Server Status 2
Unofficial Server Status 3
Twitch
RoM-Welten database
Wikia RoM Wiki
Wikidot RoM Wiki
ZAM RoM Wiki
Gamepedia
Gamepedia support
Report a bad ad
Help Wiki
Contact us
FANDOM
Fan Central
BETA
Games
Anime
Movies
TV
Video
Wikis
Explore Wikis
Community Central
Start a Wiki
Don't have an account?
Register
Sign In
Sign In
Register
Runes of Magic Wiki
28,469
pages
Explore
Main Page
All Pages
Interactive Maps
navigation
Main Page
Recent changes
Community portal
Random page
Admin noticeboard
wiki navigation
Classes
Equipment
Regions and Cities
Quests
Bestiary
Gathering
Processing
Production
Sitemap
Transmutation Chart
Attributes
portals
Runes of Magic
Joint RoM Forum
Official Games Status
RoM Discord Servers
Unofficial Server Status 1
Unofficial Server Status 2
Unofficial Server Status 3
Twitch
RoM-Welten database
Wikia RoM Wiki
Wikidot RoM Wiki
ZAM RoM Wiki
Gamepedia
Gamepedia support
Report a bad ad
Help Wiki
Contact us
Editing
Guide to XML frames part2
Back to page
Edit
VisualEditor
History
Talk (0)
Edit Page
Guide to XML frames part2
Warning:
You are not logged in. Your IP address will be publicly visible if you make any edits. If you
log in
or
create an account
, your edits will be attributed to your username, along with other benefits.
Anti-spam check. Do
not
fill this in!
== Reviewing Part 1 == In part 1 we created an add-on that displayed a string of text showing the amount of memory used by the Lua engine and updated every couple of seconds. Not exactly exciting stuff, but we spent a lot of time laying the ground work in understanding how frames are made with the XML file, and the interaction between the game, the frame, and the add-on's Lua code. Now we'll start decorating our frame by adding a texture and a close buttons to it, give it a background and border much like the game tooltips, and allow the user to move our frame around the screen. We'll also save and restore the frame's position as well as add a slash command to show the frame if the user closes it. Since there is much work to do, we'll start moving things along at a slightly faster pace, though it should not be a problem to see what is going on. == Adding a little decoration == Lets start by adding a texture behind the text. We'll make it use an additive blend mode to give it a nice glow. Open <code>MemViewer.xml</code> so we can do the changes we want. The texture we'll use is <code>"interface/transportbook/tb_highlight-01"</code> from the <code>interface.fdb</code>. Though we don't need to extract the file to use it, I still recommend extracting the files in that fdb since it contains much of the interface graphics and extracting them is pretty much the only way to browse the images. Though you should extract them to some other folder so as not to confuse RoM. As you might expect, we define a texture with a <code>Texture</code> tag. As we want this as part of our frame, we'll add it to the layers. We also want this texture to appear behind the text itself, so we shouldn't add it to the same layer definition as the FontString. Instead we'll add a new layer, but set it to the <code>BORDER</code> level to ensure it is behind the text. Here's our new <code>Layers</code> description: <code><pre> <Layers> <Layer level="ARTWORK"> <FontString name="$parentDesc" inherits="GameFontHighlight" text="Displayed Text"> <Anchors> <Anchor point="CENTER" relativePoint="CENTER"/> </Anchors> </FontString> </Layer> <Layer level="BORDER"> <Texture name="$parentGlow" file="interface/transportbook/tb_highlight-01" alphaMode="ADD"> <Size> <AbsDimension x="175" y="16"/> </Size> <Anchors> <Anchor point="CENTER" relativePoint="CENTER"/> </Anchors> <TexCoords left="0" right="1" top="0" bottom="1"/> </Texture> </Layer> </Layers> </pre></code> The <code>Texture</code> tag is defined much like we have for the FontString and the frame itself. The <code>file</code> attribute tells the game engine what actual image file we want to load for this texture. The <code>name</code> attribute here works just as it does for the FontString (namely, the <code>$parent</code> part will get replaced with the name of the parent object). We've also specified a size of 175 pixels by 16 pixels and this will become the size of the texture on-screen. The <code>alphaMode</code> attribute specifies what type of blending mode we want to use for this texture. Valid values for this attribute are: * "DISABLE" This turns off the texture itself. Used for displaying untextured color rectangles etc. * "BLEND" This is the normal, default blending mode. * "ALPHAKEY" This mode is used for texture masking based on alpha values. * "ADD" All pixels in the texture are added to the screen image (values capped at white) The really new part is the <code>TexCoords</code> tag with the four attributes <code>left</code>, <code>right</code>, <code>top</code> and <code>bottom</code>. These define what part of the image we are actually going to display as the texture. The way they work is much like a percentage or fraction across the width and height of the texture's image, and the values are always from 0 to 1. So if we only wanted to show the bottom right quarter of the image, we would use <code>left="0.5" right="1" top="0.5" bottom="1"</code>. If instead we wanted only the middle half (starting a quarter way in on all sides) we would use <code>left="0.25" right="0.75" top="0.25" bottom="0.75"</code>. For pixel perfect selection, just take the coordinates you want and devide by the width or height of the full image (as appropriate). Run this new version to view the changes. Try playing with some of the values to get a better understanding of how it works. === Framing the frame === One feature that frames support is the backdrop. Backdrops are special sets of textures that allow using an image as the background as well as having a secondary image to define the segments that will be used to draw a frame around... well, the frame. We do this with, you guessed it, a <code>Backdrop</code> tag. As this is a property of the frame itself, we don't place it inside a layer but within the <code>Frame</code> definition. Add the following just after closing the <code>Anchors</code> tag and before opening the <code>Layers</code> tag. We could place it elsewhere, but I usually put the layers and scripts at the end of the definition. <code><pre> <Backdrop edgeFile="Interface\Tooltips\Tooltip-border" bgFile="Interface\Tooltips\Tooltip-Background"> <BackgroundInsets> <AbsInset top="4" left="4" bottom="4" right="4"/> </BackgroundInsets> <EdgeSize> <AbsValue val="16"/> </EdgeSize> <TileSize> <AbsValue val="16"/> </TileSize> </Backdrop> </pre></code> The <code>edgeFile</code> attribute sets the image file to be used for drawing the border around the frame while the <code>bgFile</code> attribute defines the texture used to tile the background of the frame. The <code>EdgeSize</code> and <code>TileSize</code> tags give the size of the texture to tile. This value is dictated by the actual texture and how they are used. Try playing around with the values in the <code>BackgroundInsets</code> tag to get a feel for how these work. Basically they set how many pixels from each edge going towards the center of the frame the tiled background should be offset by. The values given for our example add-on make the edges of the tiled background appear behind the actual drawn border making it look like it is completely filled. We can also remove the <code>bgFile</code> attribute completely. In this case, there will be a border around the frame, but no background image at all. == Moving things around == Up until now, our frame has been pretty static, and is likely blocking your character's target frame to boot. So it would be nice if we allowed the user to move the frame around the screen. All that is really needed for this is to detect when the player is holding the mouse button over our frame and when the user releases the button. So for starters, we are going to need to make sure our frame can interact with the mouse. We do this by adding an attribute to our <code>Frame</code> tag called <code>enableMouse</code> and setting its value to <code>"true"</code> (by default it is considered <code>"false"</code>). Change the <code>Frame</code> tag as follows: <code><pre> <Frame name="MemViewerFrame" parent="UIParent" enableMouse="true"> </pre></code> Now to detect when a mouse button is pressed. We can use the <code>OnMouseDown</code> and <code>OnMouseUp</code> code snippets to do the dirty work for us, all we need is to tell the game when to start and stop moving the frame when the correct mouse button is pressed or released. Recall that the <code>OnMouseDown</code> and <code>OnMouseUp</code> code snippets can access a variable called <code>key</code> that will have the name of the button that is currently pressed/released. Using this fact we can now create our code snippets by modifying our <code>Scripts</code> section to add the following: <code><pre> <OnMouseDown> if(key == "LBUTTON") then MemViewerFrame:StartMoving("TOPLEFT"); end </OnMouseDown> <OnMouseUp> MemViewerFrame:StopMovingOrSizing(); </OnMouseUp> </pre></code> For the <code>OnMouseDown</code> snippet, we first check to see if the button being pressed is in fact the left mouse button. If so, we have the frame call one of its methods to begin moving the frame. The parameter to this method is what the relative position on our frame to consider while moving it around. Valid values here are the same as for the anchor points. The <code>OnMouseUp</code> is even simpler since all we want to do is tell the frame to stop moving. As we can see from this method's name, this method will stop both changes in movement and changes in size of a frame. Note how we are doing the moving completely in the code snippets instead of calling a function in our Lua file. We can do this because the code needed is quite small. In fact, we aren't really doing the moving at all. Everything is handled by the game engine, we just tell it when to start and stop. === Resizing the frame === Well now that we can move our frame around, how about resizing it. It really isn't any more difficult to do. Though there is a little caveat I'll mention in a bit. We'll start resizing our frame if the user is holding the right mouse button instead of the left one. Change the <code>OnMouseDown</code> code snippet to this: <code><pre> <OnMouseDown> if(key == "LBUTTON") then MemViewerFrame:StartMoving("TOPLEFT"); elseif(key == "RBUTTON") then MemViewerFrame:StartSizing("BOTTOMRIGHT"); end </OnMouseDown> </pre></code> If the user is holding the right mouse button instead of the left one, we call the frame's method to start resizing. Much like the moving, we also need to tell the game which part we are resizing from. In this case, the bottom right of our frame. This is all that needs to be done to get the frame to resize. Now for that caveat I mentioned. When resizing a frame like this, if the frame gets too small, the border will not be drawn correctly. Worse still, if resizing to a point where the frame would have to flip directions in order to draw, the game can get confused and draw a supersized frame. For this reason, we should also add code into our OnUpdate handler to check if we are resizing and if so, cap to a minimum size. I'll leave this last as a excersize for the reader. When testing this out, notice how the FontString and the texture do not resize, but do however stay in the middle of the frame. == Virtually buttoned up == In part 1, we briefly saw the use of virtual objects by using one of the pre-defined game fonts for creating our FontString. We can make our own virtual objects as well. This comes in handy if you are defining some type of object that will be used more than once. We'll add a couple of buttons to our frame, one to pause or un-pause the updating of the memory display, the other to lock or unlock the frame's position and sizing. However, we'll first create a virtual button definition for our use. Buttons are really just an extension of the frame object and so are in effect frames in their own right. They therefore can use all the methods that a frame can use, but also have some extra functionality to make button use easier. Virtual objects, better known as templates, are object definitions that may or may not be complete, but define all the common things we want all virtual objects of this type to have. So creating a virtual object for our buttons is much like creating another frame. We'll need to decide what we want in common however. In our XML file, add the following right after the opening <code>Ui</code> tag (the very first line in our file) and the opening of the <code>Frame</code> tag. <code><pre> <Button name="OurButtonTemplate" inherits="UIPanelButtonTemplate" enableMouse="true" virtual="true"> <Size> <AbsDimension x="90" y="26"/> </Size> </Button> </pre></code> We place this before our Frame definition since the game engine will need to know about this template before we ever use it. The <code>name</code> attribute here will become the name of this template and how we can refer to it. Since this is a button, it will obviously need to interact with the mouse so we enable the mouse for this template. Since this is a template, we have the <code>virtual</code> attribute set to <code>"true"</code> to tell the game engine that this is in fact a virtual object and therefore should not be treated as a full object definition. Note we are also inheriting settings from <code>UIPanelButtonTemplate</code>. This is because there are a lot of settings for buttons and we will be using a pretty standard looking button anyway, so may as well. To see all these settings, open the file <code>worldxml/uipaneltemplate.xml</code> from <code>interface.fdb</code> and look for the definition of <code>UIPanelButtonTemplate</code>. I strongly suggest looking through this file since it contains many standard templates to use. You'll also notice that there are also some default scripts defined for the button template in here, we'll need to take this into account when making our own buttons. The only other thing being done here is setting a size for the button much like we did for our frame. Given that this is a template, we'll be able to change it when we make our actual buttons. === Making the real buttons === Now that we have our button template defined, we can create the buttons for our frame. As mentioned, buttons are derived from frames and therefore we cannot add them to our frame in the same way we do for FontStrings and textures. Instead we need a <code>Frames</code> tag (again, note the spelling of this tag) which will contain any child frames to be created along with our frame. This will be true for almost all elements other than textures and FontStrings. After the <code>Layers</code> tag but before the <code>Scripts</code> tag of our frame definition, add the following: <code><pre> <Frames> <Button name="MemViewerPauseButton" inherits="OurButtonTemplate" text="Pause"> <Anchors> <Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"> <Offset> <AbsDimension x="6" y="-5"/> </Offset> </Anchor> </Anchors> <Scripts> <OnClick> MemViewer.Paused = not MemViewer.Paused; if(MemViewer.Paused) then this:SetText("Unpause"); else this:SetText("Pause"); end </OnClick> </Scripts> </Button> <Button name="MemViewerLockButton" inherits="OurButtonTemplate" text="Lock"> <Anchors> <Anchor point="BOTTOMRIGHT" relativePoint="BOTTOMRIGHT"> <Offset> <AbsDimension x="-5" y="-5"/> </Offset> </Anchor> </Anchors> <Scripts> <OnClick> MemViewer.Locked = not MemViewer.Locked; if(MemViewer.Locked) then this:SetText("Unlock"); else this:SetText("Lock"); end </OnClick> </Scripts> </Button> </Frames> </pre></code> Though this may look pretty complicated at first glance, there really isn't much that is new here. The <code>text</code> attribute for the buttons will be the text shown on the buttons themselves. Something not yet mentioned is we could also give the name of a string variable (enclosed in quotes) and the game will get the value of the variable to use as the text instead of the text itself. We define an <code>OnClick</code> code snippet for each button so we can have our buttons do something useful. The code snippet itself merely toggles the variable between a true and false value, then sets the button text to indicate the new state. When in a code snippet, <code>this</code> will always be the object itself. As these snippets are in a button, <code>this</code> will always be the button. So now we need to initialize those variables and also allow or disallow the sizing and moving depending on these variables. We can modify our frame's <code>OnLoad</code> code snippet to initialize the values, and change the <code>OnUpdate</code> and <code>OnMouseDown</code> code snippets for the rest. Change the three relevant code snippets for our frame as follows: <code><pre> <OnLoad> MemViewer.Paused = false; MemViewer.Locked = false; MemViewer.OnLoadHandler(); </OnLoad> <OnUpdate> if(not MemViewer.Paused) then MemViewer.OnUpdateHandler(elapsedTime); end </OnUpdate> <OnMouseDown> if(not MemViewer.Locked) then if(key == "LBUTTON") then MemViewerFrame:StartMoving("TOPLEFT"); elseif(key == "RBUTTON") then MemViewerFrame:StartSizing("BOTTOMRIGHT"); end end </OnMouseDown> </pre></code> Of course, we could also have placed the variable initialization in the OnLoad handler function, but this was easier. Finally, change the size of the frame so that there is room for the buttons to appear on this frame. <code><pre> <Size> <AbsDimension x="200" y="80"/> </Size> </pre></code> == Closing up shop == While we are on the subject of buttons, lets add a close button to our frame so that the user can completely remove the frame from the screen. Admitedly, there is an ulterior motive for doing this as you'll see in the next section, but for now lets pretend we want to allow the player to remove the frame from the screen. We've seen that RoM provides many templates for creating interface panels and one of these virtual objects is a close button. The advantage for the user is that by using this template, the close button will look like every other close button the player has seen in the game and will instantly know what it is and how it is supposed to work. For add-on developers the advantage is that it is pretty much complete and all we really need to do is to tell the game where to anchor the button on our frame. As this is a close button, we need to add it to the <code>Frames</code> section of our frame definition. Add the following: <code><pre> <Button name="$parentCloseButton" inherits="UIPanelCloseButtonTemplate"> <Anchors> <Anchor point="TOPRIGHT" relativePoint="TOPRIGHT"> <Offset> <AbsDimension x="-5" y="6"/> </Offset> </Anchor> </Anchors> </Button> </pre></code> That's all we need to do to get a working close button. All relevant scripts and code snippets are already defined by <code>UIPanelCloseButtonTemplate</code> so no other modifications to our code is needed to make it work. To get the window back after closing it, type the command <code>/run MemViewerFrame:Show()</code> in the chat edit box. === Opening shop again === So adding a close button was simple enough but to make it convenient to get it back we should really add a slash command. We won't discuss slash commands themselves as this is a tutorial on frames and they are also covered in the add-on tutorial on the RoM Wiki. We'll need to add this in our Lua file so open <code>MemViewer.lua</code> and add the following after we create the main variable: <code><pre> --[[ Slash command handler ]]-- SLASH_MemViewer1 = "/mview"; SlashCmdList["MemViewer"] = function(ebox, msg) if(MemViewerFrame:IsVisible()) then MemViewerFrame:Hide(); else MemViewerFrame:Show(); end end </pre></code> This will toggle our frame on and off each time we type <code>/mview</code> in the chat edit box. What we need to note here is how the code is verifying if the frame is visible, and how it shows or hides the frame. == Handling Events == Back in part 1 of this guide when we discussed code snippets, mention was made that one of the methods the game communicates with add-ons via frames is with events. It was also mentioned that events are really just an extension of the code snippet. We've come a long way, but now we finally get to introduce events. When certain things happen in-game, for example when the player begins casting a spell, the game engine looks for any frame that may be interested in knowing about this and if it finds one, sets a variable called <code>event</code> to a string value with the name of the event, and then calls that frame's <code>OnEvent</code> code snippet. This has a few ramifications that we need to know about. First, since the game engine always calls the <code>OnEvent</code> code snippet regardless of the event type itself, it is up to us to verify which event actually triggered and act accordingly. Second, we need to tell RoM which events we are actually interested in. The first modification we'll need to make is to add the <code>OnEvent</code> code snippet to our frame. We'll simply make it call a new function in our Lua file. Add the following to our frame's <code>Scripts</code> section: <code><pre> <OnEvent> MemViewer.OnEventHandler(event); </OnEvent> </pre></code> Note the use of the <code>event</code> variable here. This will pass along the type of event being received to our function. Now open <code>MemViewer.lua</code> so we can go add our OnEvent handler function. But first we should decide what event we want to know about. For our example add-on, we'll save and restore the position of our frame so that it always returns to where the user placed it. To achieve this, we'll want to know when the variable we'll save has been restored so that we can then restore the position. The RoM engine will tell us this fact via an event called <code>VARIABLES_LOADED</code>, so that is what we'll look for. We'll also want to make sure that our frame's position is saved when the game is exiting or returning to the character selection screen. For this we'll also need the event <code>SAVE_VARIABLES</code>. So our OnEvent handler will be <code><pre> --[[ The OnEvent handler ]]-- function MemViewer.OnEventHandler(event) if(event == "VARIABLES_LOADED") then if(not MemViewerPos.x) then MemViewerPos.x, MemViewerPos.y = MemViewerFrame:GetPos(); else MemViewerFrame:SetPos(MemViewerPos.x, MemViewerPos.y); end elseif(event == "SAVE_VARIABLES") then MemViewerPos.x, MemViewerPos.y = MemViewerFrame:GetPos(); end end </pre></code> The code here makes sure that our variable has a valid value before using it otherwise we set it to the frame's current position. For the <code>SAVE_VARIABLES</code> event, we merely set our saved variable to the frame's current position. Now that we have our event handler created, we need to tell the game what variable to save, and also what events we wish to be notified about. Change the OnLoad handler to this: <code><pre> --[[ The OnLoad handler ]]-- function MemViewer.OnLoadHandler() MemViewer.PrevMem = collectgarbage("count"); SetMemViewerText(MemViewer.PrevMem, MemViewer.PrevMem); MemViewer.UpdateTime = 0; SaveVariables("MemViewerPos"); MemViewerFrame:RegisterEvent("VARIABLES_LOADED"); MemViewerFrame:RegisterEvent("SAVE_VARIABLES"); end </pre></code> Notice that we haven't defined the <code>MemViewerPos</code> variable itself. This is because we'll want it defined in global space and that is where variables are defined by default. The two <code>RegisterEvent</code> calls tell RoM which events we want to be notified about. Try this new version out. The first time you run the game with this add-on, the frame will be in its usual place. However if we move it then log out and back in, the frame will be set to the new location. === Reviewing what is going on === Events are an important aspect of using frames and so a review of what is happening here may help in understanding them better. First, when the game creates our frame via the XML, it calls the <code>OnLoad</code> code snippet associated with our frame and that in turn calls <code>MemViewer.OnLoadHandler</code>. In that function we tell the game that our frame should be notified about the <code>VARIABLES_LOADED</code> and <code>SAVE_VARIABLES</code> events. Once the game actually loads the saved variables, it generates the <code>VARIABLES_LOADED</code> event, sees that our frame is interested in knowing about it, sets a variable called <code>event</code> to a string with the event name and calls the <code>OnEvent</code> code snippet for our frame. This code snippet then calls <code>MemViewer.OnEventHandler</code> with the name of the event triggered as the function parameter. In that function, we verify this value and execute the appropriate code we wish to execute for this event. When the game is shutting down, it first generates the <code>SAVE_VARIABLES</code> event and again executes our frame's <code>OnEvent</code> code snippet but this time the <code>event</code> variable is set to <code>SAVE_VARIABLES</code>. This in turn ends up calling our OnEvent handler function which then executes the appropriate code to set the variables before the game exits, ensuring the last known position of our frame is saved. The most important aspect to realize here is that it is the game itself that calls the frame's code when an event happens. We merely tell the game what it is we are interested in. == Learning more == This about wraps up this guide on XML frames. If you've made it this far and understood the concepts shown, you should be well on your way to creating complex user interfaces. But there is so much more to frames than can be put in a guide, even one a long as this. So we'll end this guide with a short discussion on where to learn more about frames and other elements. The first place is the Runes of Magic Wiki itself, for obvious reasons. The Wiki has a wealth of information available. Another good source is other people's add-ons. Though care should be taken that you don't copy other people's code without permission, learning how something can be done is fair use. This can often lead you to new ideas and ways to do things. One of the best sources is the game itself. RoM's entire user interface is created using Lua and so is filled with working examples. The catch is that it requires a little more investigative research and a few external tools (such as an FDB extractor program) to get at the code. It is suggested that all the files in <code>interface.fdb</code> be extracted to some folder where you can perform searches on the files. One file worth looking at is <code>worldxml/ui.xsd</code> from <code>interface.fdb</code>. This file is used by the game for validating the XML file (see the last attribute of the <code>Ui</code> tag at the start of any XML file). Though it can be a little strange to read through, this file contains all the tags, attributes, and values that an XML file is allowed to contain. Getting to know this file and how it relates to the XML files can help you discover much of how frames and other elements work. Another source of information actually comes from a different game. World of Warcraft was one of the inspirations for Runes of Magic, and the Lua API used in RoM is very similar to WoW's. The similarities go to such an extent that we can learn much about how RoM's functions and frames work by looking at WoW's API. Be aware that they are not identical, so not everything will work out of the box.
Summary:
Please note that all contributions to the Runes of Magic Wiki are considered to be released under the CC BY-NC-SA
Cancel
Editing help
(opens in new window)
Follow on IG
TikTok
Join Fan Lab