FANDOM


Chap. 11 - The GUIEdit

GUI Complexity LevelsEdit

Wild Pockets provides a set of commands for building GUIs. There are really three possible levels of complexity:

  • A GUI that's just a readout or two (such as a score indicator) which doesn't accept input of any kind.
  • A GUI that consists of a few readouts plus a few onscreen hot-spots that respond to mouse clicks.
  • A full-fledged GUI involving menus, buttons, scrollbars, and the like.

You need to learn how to do each of these in order, starting with the simplest, and working your way up to the level you need.


Drawing Simple GUIsEdit

GUI BasicsEdit

Here is a simple program that puts a score in the corner of the window:

function onSceneStart() 
    currentScore = 235 
    GuiManager.call("paintScore", paintScore, 1) 
end

function paintScore() 
    local scorebox = Rect.new("",0,0,200,25) 
    scorebox:drawText(nil, {}, "Score: " .. currentScore .. " points") 
end


Every frame, the GUI is erased and rebuilt from scratch. The way this works is that you must write a function like 'paintScore' above whose job is to repaint the GUI from scratch. You then use GuiManager.call to tell the GUI subsystem to call your paint-function. Every frame, the this function will get called to rebuild the GUI.

The actual process of drawing the GUI consists of two steps: construct objects of class 'Rect' (rectangle) to lay out the positions of the gui elements. The coordinates are in pixels: (0,0) is the upper-left corner. After constructing the rectangles, you fill those rectangles with pictures and text. In the code above, we start by constructing a rectangle that will hold the score. Then, using the method 'Rect.drawText', we paint the desired string.

Because the GUI is rebuilt from scratch every frame, it tends to update itself naturally. For example, if some other part of the program were to change the contents of the variable 'currentScore', the GUI would immediately reflect that.


Drawing Pictures with Rect.drawPictEdit

The method 'Rect.drawPict' is used to fill a rectangle with a picture. The simplest cases are these:

rect:drawPict(nil, {background="user/zebra.jpg", stretch = false}) 
rect:drawPict(nil, {background="user/zebra.jpg", stretch = true})


In the first case, the picture of a zebra is tiled across the rectangle. In the second case, the picture of the zebra is stretched to fill the rectangle exactly once.

The first parameter to drawPict is a clipping rectangle. These aren't used that often, so the parameter is usually nil. If you supply a clipping rectangle, then this acts like a cropping tool outside of which nothing is drawn.

If you wish, you can also put something that looks like a picture frame around the zebra. To do so, create a PNG image that looks like a picture frame with an alpha-transparent center. Then, use the foreground keyword to supply the frame:

rect:drawPict(nil, {background="user/zebra.jpg", stretch=true, foreground="user/picframe.png"})


This will draw a picture of a zebra with a frame.

The 'foreground' and 'background' keywords are somewhat badly named - background is really "the image that fills the rectangle", and foreground is really "the picture frame that decorates the edges of the rectangle." This is an important distinction: many people make the mistake of trying to use 'foreground' to draw a picture. But foreground can't do that - foreground is to decorate the edges, not to fill the rectangle. To draw a simple picture, use 'background,' not 'foreground.'

The foreground (picture frame) image must have some alpha transparent areas. Before rendering, the alpha-transparent portions of the frame are analyzed. Some of the alpha-transparent areas may be judged to be on the "inside" of the frame, and others may be judged to be on the "outside" of the frame. (This analysis uses a flood-fill which starts at the outside edge and stops when it gets to an opaque pixel). Alpha transparent areas on the inside of the frame will show the zebra. Alpha transparent areas on the outside of the frame will show whatever was on the screen before the pict was rendered.

If the picture frame does not fit the rectangle exactly, it is expanded using a quadrant-based algorithm. The PNG is chopped into four quadrants: upper-left, upper-right, lower-left, and lower right. The four corners of the PNG are placed into the four corners of the screen Rect. This may leave gaps in the middle. The gaps are filled by stretching the middle pixels of the PNG. This quadrant-based algorithm is ideal for picture frames. It really is not very useful for anything else. The "foreground" keyword is solely intended to enable you to put a picture frame around an image. Note that the 'stretch' keyword only affects the background image. The foreground image is always processed using the quadrant-based algorithm.

To get more complex effects, you can use multiple 'drawPict' calls to layer images on top of each other.

The table which is passed to drawPict may contain a number of other options in addition to 'foreground', 'background', and 'stretch'. See the API reference for a full list.

Drawing Text with Rect.drawTextEdit

To display text, build a rectangle, then call drawText. Here is a common case:

rect:drawText(nil, {}, "Now is the time for all good men")


The first parameter to drawText is a clipping rectangle. These aren't used that often, so the parameter is usually nil. If you supply a clipping rectangle, then this acts like a cropping tool outside of which nothing is drawn.

The table can contain a large variety of options. Here is an example of passing options:

rect:drawText(nil, { font="josh/verdana.ttf", size = 12, wrap = false }, "Now is the time for all good men")

The font must name a truetype font-file that has been uploaded to the Wild Pockets server.

There are a large number of options to drawText. These can be found in the API reference in the builder.


Mouse Input from the GUIEdit

Naming your RectanglesEdit

Objects of class Rect contain an ID string field. This ID string must be properly initialized if you want your GUI to accept mouse clicks. The ID string is what makes it possible to identify which rectangle the user clicked on. It is standard Wild Pockets practice to name your rectangles using a directory-tree-like naming system. Consider, for example, a dialog box with a message, an OK button, and a cancel button. It would be in keeping with standard practice to name the rectangles as follows:

Rectangle Purpose

Rectangle ID String

Entire Screen

screen

The Dialog Box

screen/dialogbox

The OK Button

screen/dialogbox/ok

The Cancel Button

screen/dialogbox/cancel

The Message

screen/dialogbox/message


Note that these are logical, not spatial relationships. The ok button does not technically have to be physically inside the dialog box, it is just conceptually a part of the dialog box. There are a great many constructors that return rectangles. With the exception of Rect.new, all of these constructors require you to pass in a parent rectangle. These constructors concatenate the ID of the parent, a slash, and a sub-ID specified in the arguments to produce the resulting rectangle's ID. Because of this, it is very easy to construct rectangles with the standard naming conventions. Sometimes, a rectangle doesn't have any particular "parent." In that case, you should use the screen rectangle as the parent:

local screen = Rect.screen()


So here's an updated version of the paintScore function on the previous page. The rectangle is now properly named:

function paintScore() 
    local screen = Rect.screen() 
    local scorebox = screen:sub("scorebox",0,0,200,25) 
    scorebox:drawText(nil, {}, "Score: " .. currentScore .. " points") 
end


The function Rect.new has been replaced with Rect:sub. These functions differ only in how they initialize the ID string of the rectangle. In Rect.new, the rectangle gets the exact ID string that you pass in. In Rect:sub, the ID is created using the Wild Pockets standard: by concatenating the parent ID, a slash, and the specified sub-ID ("scorebox"). Having a valid ID string in this rectangle will make it possible to detect the presence of the mouse.

Detecting Mouse Position

To detect the mouse, the first step is to activate a rectangle for mouse input using the 'Rect:activateRegion' method:

function paintScore() 
    local screen = Rect.screen() 
    local scorebox = screen:sub("scorebox",0,0,200,25) 
    scorebox:drawText(nil, {}, "Score: " .. currentScore .. " points") 
    scorebox:activateRegion() 
end


This causes the rectangle to be recorded in a list of input-regions. This list, like the GUI as a whole, is rebuilt once per frame. Now that our rectangle is part of the list of input regions, we can check whether it contains the mouse:

local mx,my = Mouse.getXY() 
local region = GuiManager.find(mx,my)


The variable region will now contain the ID string of the region containing the mouse. This is subtly different from simply asking whether or not the rectangle contains the mouse using Rect.contains. If you have two active regions that overlap, GuiManager.find will choose the one which is on top.

Setting up HandlersEdit

In addition to simply checking whether the mouse is inside a region, you can set up click handlers. As with the rest of the GUI, the handler tables are rebuilt once per frame, so they need to be set up inside the same paintScore function:

function paintScore() 
    local screen = Rect.screen() 
    local scorebox = screen:sub("scorebox",0,0,200,25) 
    scorebox:drawText(nil, {}, "Score: " .. currentScore .. " points") 
    scorebox:leftMouseDown(scoreDown) 
end

function scoreDown() 
    print("You clicked on the score!") 
end


Now clicking on the score rectangle will cause the scoreDown function to get called. In addition to leftMouseDown, you will find leftMouseClick, which doesn't call the handler until you have clicked the mouse down and then released it. Of course, rightMouseDown and rightMouseClick both exist as well. Functions that set up handlers call Rect:activateRegion implicitly.

Eating Click EventsEdit

When you click on a GUI element that is activated using leftMouseDown or the like, the GUI eats the mouse-down event and the corresponding mouse-up event. When this occurs, the event never gets to the EventHandler/EventMap system.

Other OperationsEdit

There are a great many functions in classes GuiManager and Rect to help detect the activity of the mouse. These include such functions as Rect:leftClickInProgress, Rect:hasMouse, and the like. See the documentation for these two classes.


How Widgets are BuiltEdit

Widget Basics

In Wild Pockets, GUI widgets like buttons, scrollbars, menus, and the like are simply subroutines that use the simpler primitives to draw a complex object. Consider, for example, this subroutine:

function Rect:drawSimpleButton(clip, label) 
    if (self:leftClickInProgress()) then 
        self:drawPict(clip, { background="josh/buttonDownImage" } ) 
    elseif (self:hasMouse()) then 
        self:drawPict(clip, { background="josh/buttonHoverImage" } ) 
    else 
        self:drawPict(clip, { background="josh/buttonStandardImage" } ) 
    end 
    self:drawText(clip, { halign="center" }, label) 
    self:leftMouseClick(fn) 
end

So this is just a drawing routine like drawPict and drawText - the only difference is, it draws one of three different pictures depending on what the mouse is doing, and it also sets up a mouse handler.

This is a very important concept: our GUI system has no "widget" objects. All we have are subroutines like drawSimpleButton above that draw several drawPict/drawText elements all at once, and which may set up some mouse handlers as well.

Rect UserdataEdit

Some widgets have state. For example, a check-box widget has a single on/off bit. A scrollbar widget has state that indicates how high/low the thumb is in the bar. When designing a routine that draws a checkbox or a scrollbar, you will need someplace to store the checkbox state, or the thumb position. For this, you will need Rect:userdata.

The function Rect:userdata will return a Lua table where you can put anything you want. The data is specifically associated with a particular rectangle.

The interesting thing about userdata is that your GUI may be discarded and recreated every frame, but the userdata is not discarded. Instead, the system manages to carry over userdata from the previous frame.

The way this works is as follows: the userdata is cached according to the rectangle's ID string. When the GUI is rebuilt, if you construct a rectangle whose ID string is the same as the previous frame, the userdata will be retrieved from the previous frame. This is one reason why it's important to use the predictable Rect naming conventions that are standard for Wild Pockets GUIs.

You must fetch the userdata every frame, or it will be discarded. This is how garbage collection of userdata works: if you stop drawing a given GUI element, its userdata will dissappear.

Here is how you would use userdata to implement a checkbox widget:

function Rect:drawSimpleCheckBox(clip) local userdata = self:userdata() if (userdata.__CHECKED) then self:drawPict(clip, {background="josh/checkBoxChecked"}) else self:drawPict(clip, {background="josh/checkBoxUnchecked"}) end self:leftMouseClick(toggleCheckBox) end

function toggleCheckBox(rect) local userdata = rect:userdata() userdata.__CHECKED = not userdata.__CHECKED end

The checkbox state is stored in the userdata table. The routine that draws the checkbox draws either the checked or unchecked version, depending on the state stored in the userdata table. A mouse click in the region will call the toggle routine, which reverses the value stored in the table.

Keyboard FocusEdit

To implement GUI elements that accept keyboard input, you will need to use the GUI system's keyboard focus handling.

At any time, the focus points at a particular rectangle (or it can be nil). To enable a given rectangle to receive the keyboard focus, use Rect:enableFocus. This takes a pointer to a keyboard-event handling function. If you type when a rectangle has focus, the events will go to that rectangle's event-handling function.

The GUI system will use a series of heuristics to guide the focus to the right rectangle. The heuristics are:

  • If you click on a rectangle with enableFocus, the GUI will direct focus to that rectangle.
  • If you click on a rectangle without enableFocus, it will search for a child rectangle that has enableFocus. It will pick the child rectangle which had focus most recently.
  • If a new rectangle appears with enableFocus, then focus will go to the new rectangle. If multiple new rectangles appear with enableFocus, the focus will go to the earliest-painted of these.

It usually isn't necessary to understand these perfectly, suffice to say that these heuristics generally direct focus to the right place. You can force focus to a particular rectangle using GuiManager.setFocus.

Our Provided WidgetsEdit

Class Rect includes many 'draw' functions that draw all kinds of prefab widgets, including buttons, checkboxes, radio buttons, scroll bars, drop-down menus, and so forth. These are documented in the API reference for class Rect.


SkinsEdit

===
Style Objects
===

The functions drawPict and drawText both take tables full of options. If you recall:

rect:drawPict(nil, { option1=value1, option2=value2, ... }) 
rect:drawText(nil, { option1=value1, option2=value2, ... }, message)


These tables full of options are called "Gui Styles."

Parsing these tables can be a little expensive, so if your paint routine is taking up too much CPU time, you can precompile these into actual GuiStyle objects:

local style = GuiStyle.new({ background="josh/tiger" }) 
rect:drawPict(nil, style)


Using GuiStyle objects may be a little faster than using raw tables. Both the original table form and the compiled GuiStyle form are considered "Gui Styles."

SkinsEdit

A skin is a table containing a collection of styles and skins. This is a skin:

local style1 = { background="josh/tiger" } 
'local style2 = { background="josh/lion" } 
local skin = { tigerStyle = style1, lionStyle = style2 }


The purpose of skin objects is that you may have a routine, like drawButton, that may draw several different images depending on circumstances. The skin allows you to configure the routine to cause it to draw any images you choose.

Many of our higher-level functions that draw widgets (ie, our button-drawing routines, our menu-drawing routines, and so forth) expect skins as parameters. Not only do they require skins, but they require that those skins contain particular styles. For example, our button-drawing routine is documented as follows:

Rect:drawButton(clip, skin, label, callback)

Draws a button, and arranges a callback to occur if the button is clicked.

  • Uses skin[.buttonDefault].down
  • Uses skin[.buttonDefault].over
  • Uses skin[.buttonDefault].normal
  • Uses skin[.buttonDefault].text (optional)
  • Uses skin[.buttonDefault].overlay (optional)

As you can see from the documentation, the skin must contain the fields "down", "over", and "normal". Here is a valid call:

local downStyle = { background="josh/downImage" } 
'local overStyle = { background="josh/overImage" } 
local normalStyle = { background="josh/normalImage" } 
local skin = { down=downStyle, over=overStyle, normal=normalStyle } rect:drawButton(nil, skin, "OK", myCallback)


The GUI Manager provides a "standard skin" which contains all the required fields for any of our supplied widgets, but you can create your own skins to completely alter the appearance of these widgets.

Ad blocker interference detected!


Wikia is a free-to-use site that makes money from advertising. We have a modified experience for viewers using ad blockers

Wikia is not accessible if you’ve made further modifications. Remove the custom ad blocker rule(s) and the page will load as expected.