EZUI

From Sirikata Wiki
Jump to navigation Jump to search

EZUI is a user interface creation tool developed by Ben Christel. It is designed to allow an application developer to add code to an entity's script that specifies the UI's appearance and logic. The UI will then appear on a user's screen when the user interacts with one of the entity's presences.

EZUI was designed with the following goals in mind:

  • Facilitating rapid development of dialog-like UIs associated with a particular presence in the world
  • Enabling multiple users to interact with a presence simultaneously using the UI
  • Allowing application developers to easily collect and store data input by the user
  • Facilitating communication between the presence controlling the UI and the avatar interacting with it
  • Automating the synchronization of data between the UI and the controlling presence

Tutorial

Setting up EZUI

To use EZUI, you'll need the following files in your Sirikata directory:



If you've edited your own std/default.em and std/graphics/default.em (for example, if you've added scripts to the default presence or created your own UIs or keybindings) you'll want to patch those files manually. The section below describes how to do that.

Manually Patching std/default.em and std/graphics/default.em

Lines preceded with /*add*/ are to be added. Other lines are shown for context. Line numbers may differ if you've edited these files yourself.

In std/default.em
system.require('std/core/repeatingTimer.em');
/*add*/ system.require('std/graphics/ezui.em');
In std/graphics/default.em

(line 48)

system.require('std/graphics/axes.em');
/*add*/ system.require('std/graphics/ezuiViewer.em');

...

(line 79)

this._loadingUIs++; this._setMesh = new std.graphics.SetMesh(this._simulator, ui_finish_cb);
/*add*/ this._loadingUIs++; this._ezui = new std.ezui.EZUI(this, ui_finish_cb);

...

(line 91)

this._loadingUIs++; this._setMesh.onReset(ui_finish_cb);
/*add*/ this._loadingUIs++; this._ezui.onReset(ui_finish_cb);

...

(line 157)

this._binding.addAction('stopDrag', std.core.bind(this.stopDrag, this));
/*add*/ this._binding.addFloat2Action('showEZUI', std.core.bind(this.showEZUI, this));
/*add*/ this._binding.addAction('hideEZUI', std.core.bind(this.hideEZUI, this));

...

(line 230)

{ key: ['mouse-release', 1, '*'], action: 'stopDrag' },
/*add*/ { key: ['mouse-press', 3, 'none'], action: 'showEZUI' },

...

(end of the file)

            redo: function(action) {
                movable.setOrientation(new util.Quaternion());
            }
        });
    }
    
/*add*/    std.graphics.DefaultGraphics.prototype.showEZUI = function (x, y) {
/*add*/        var ignore_self = this._camera.mode() == 'first';
/*add*/       var clicked = this._simulator.pick(x, y, ignore_self);
/*add*/        this._ezui.show(clicked, x, y);
/*add*/    };
    
/*add*/    std.graphics.DefaultGraphics.prototype.hideEZUI = function () {
/*add*/        this._ezui.hide();
/*add*/    };
    
})();

Hello, World!

The Code

system.require('std/graphics/ezui.em');
ezui.script(@
	write('Hello, World!');
@);

Notes

  • The line system.require('std/graphics/ezui.em') ensures that the required files are included.
  • The Emerson function ezui.script() takes a string containing JavaScript to be executed when the GUI loads. The code passed to ezui.script() is executed every time a user opens the GUI. In between invocations of the GUI, its contents are cleared. The result is that the GUI always starts in the same state.
  • The write() function takes a string as a parameter. This string is appended to the GUI's HTML, similar to the way document.write() works in a plain HTML document.

Testing the UI

You can test the code above by opening a scripting window on any presence in the world and running the code. Then right-click on the presence you just scripted and the UI should appear. You may have to left-click first to move the focus to the main window.

Creating Buttons And Sections

The Code

system.require('std/graphics/ezui.em');
ezui.script(@
	write(sections('buttons', 'output'));
	var b1 = button('English', buttonCB);
	var b2 = button('Spanish', buttonCB);
	append('buttons', b1+'<br />'+b2);
	function buttonCB (buttonText) {
                if (buttonText == 'Spanish') {
		        setInnerHTML('output', 'Hola!');
                } else {
		        setInnerHTML('output', 'Hello!');
                }
	}
@);

Notes

  • Buttons are represented as JavaScript objects, and can be stored in vars. When you want them to appear on the screen, just pass the button object to a function like write(). Buttons can also be concatenated with strings using the + operator.
  • The button() function is used to create a button. This function takes the following parameters:
    • the text on the button
    • a callback function that is called when the user clicks the button. This callback can take the button text as a parameter, so if two buttons share the same callback, you can figure out which one was clicked.
  • The sections() function is used to divide the UI into sections. sections() takes any number of strings as parameters; these strings are used as section IDs. Note that the strings passed to sections() must be valid HTML ID attribute values (i.e. they must begin with a letter and contain only letters, numbers, and underscores). sections() returns a string that can be passed to write() or append().
  • The append() function is used to append text or HTML to a specific section of the UI. The function takes the following parameters:
    • The ID of the section
    • The text to append to the section
  • The setInnerHTML() function sets the contents of the section with the specified ID to the specified text or HTML. It has the same syntax as append.

Sending Messages to the Controlling Presence

  • The controlling presence (or simply controller) is the presence that you script with the ezui.script() call.
  • You can notify the controller that a button on your UI was pressed by setting notifyController as the button's callback function.
  • The ezui.onButtonPressed() function lets you specify a function to execute when your controller gets notified of a button event. Note that ezui.onButtonPressed() is called in the controller's main script, not the UI script inside ezui.script. The callback function must also be defined in the controller's main script. The callback can take the button's text as a parameter.

The Code

system.require('std/graphics/ezui.em');
ezui.script(@
	var upButton = button('up', notifyController);
	var downButton = button('down', notifyController);
	var stopButton = button('stop', notifyController);
	write(upButton+'<br />');
	write(stopButton+'<br />');
	write(downButton+'<br />');
@);

ezui.onButtonPressed(move);
function move (buttonText) {
	if (buttonText == "up") {
		system.self.setVelocity(<0, 1, 0>);
	}
	else if (buttonText == "down") {
		system.self.setVelocity(<0, -1, 0>);
	}
	else if (buttonText == "stop") {
		system.self.setVelocity(<0, 0, 0>);
	}
}

Notes

  • notifyController is only used when you don't need the UI to store any state about the user's actions. It can be handy for making context menus that allow the user to select from a list of options that cause the controller to take some action. Other methods of communicating with the controller will be discussed in later tutorials.

Managing User Data

  • The _() function lets you get and set user-specific data that can persist across multiple invocations of the UI.
  • For example, the following code gets a user variable called "score" and increments it.
var s = _('score') + 1;
_('score', s);
  • The expression _('score') retrieves the value of the variable named "score".
  • The line _('score', s); stores the value of s in the variable named "score".
  • User data can be automatically saved when the UI closes. To autosave user data, call OnUiWillClose(save);.
  • OnUiWillClose() registers a callback function that will be executed when the UI is about to close.
  • save() is a function that you can call to manually save user data.

The Code

system.require('std/graphics/ezui.em');
ezui.script(@

if (!_('score')) _('score', 0);
write(sections("welcome", "button", "score"));
append("welcome", "<b>Welcome to ButtonClick</b>,<br/> the game where you click a button.");
append("button", button("score++", incrementScore));
displayScore();

function displayScore () {
    setInnerHTML("score", _('score'));
}

function incrementScore () {
    var s = _('score') + 1;
    _('score', s);
    displayScore();
}

onUiWillClose(save);

@);

Testing the Code

  • Open the UI, click the button a few times, and close it. Reopen it, and notice that your score is the same as when you left off.
  • Your score is unique to you; if someone else opens the UI, the game will keep track of their score separately.

A Simple Chat Client

The Code

system.require('std/graphics/ezui.em');
ezui.script(@

write(sections('chat_log', 'chat_form'));

displayChatLog();

append('chat_form', '<input type="text" size="40" id="my_chat_input_box"></input><br />');
append('chat_form', button('submit', chatSubmitButtonCB));

function chatSubmitButtonCB () {
    var msg = {};
    msg.chatLine = $("#my_chat_input_box").val();
    $("#my_chat_input_box").val("");
    msg.avatar = _('viewer').id.substring(0, 6);
    sendToController(msg);
}

function displayChatLog () {
    clear('chat_log');
    var lines = __("chat_lines");
    if (lines) {
        for (var i = 0; i < lines.length; i++) {
            append('chat_log', lines[i]+'<br />');
        }
    }
}

onGlobalDataDidChange(displayChatLog);

@);

ezui.setGlobalData('chat_lines', []);

function handleNewChat (msg) {
    var chatLines = ezui.globalData("chat_lines");
    chatLines.push("<span style='color:#"+msg.avatar+"'>"+msg.avatar+": "+msg.chatLine+"</span>");
    if (chatLines.length > 10) chatLines.splice(0, 1);
    ezui.setGlobalData('chat_lines', chatLines);
}
handleNewChat << [{'chatLine'::}];