Scripting
Engine
KUIML uses AngelScript for its scripting engine, with the following add-ons:
- Math functions (using double precision instead of float), with the addition of the exp() function
- Script Arrays
- Strings (string functions)
- File
- Dictionary
- Filesystem
- Datetime
- Custom addons (XML parsing, utf-8 iterator, graphics, StopWatch, etc).
Also, strings use double quotes only. Simple quotes are reserved for a single character, like in C or C++.
Related KUIML Elements
Separate script files can be loaded in KUIML using the SCRIPT element.
Separate build-time scripts/functions can be loaded via SKIN build_time_script_include attribute to provide additional functionality for VARIABLE built-time scripting.
Scripts can also be written directly (inline) as attributes of the SCRIPT, ACTION_TRIGGER or ACTION (Script type), GL_OBJECT_3D, CANVAS and VARIABLE elements. Some characters (like &, ", <) need to be escaped in Inline scripts. See our online script converter.
Data model (PARAMs, STRINGS, ACTIONS, etc) can be exposed to scripts using the EXPOSED_OBJECTS element, and also via requires and exposed attributes in particular elements.
How it works
Once the structure of the user interface and data model have been described using KUIML elements and connected together with links (see build-time/runtime), it is possible to write scripts that perform more complex actions on the data model on behalf of the end-user. Most of the elements and their attributes are available to the scripting engine (see the "E (exposed)" column in the attributes list in the elements reference).
- <!-- create a param and a string -->
- <PARAM id="demo_param" min="0" max="20" default="10" />
- <STRING id="demo_string" default="Hello, World!" />
- <!-- display string and param values -->
- <TEXT string_id="demo_string" width="100" />
- <PARAM_TEXT param_id="demo_param" />
- <!-- a widget to draw a line -->
- <WIDGET id="widget_line" width="100" height="3" background_color="#DD0000" />
- <!-- a simple button to run an action -->
- <SYSTEM_ACTION_BUTTON action_id="run_my_script" width="100" />
- <!-- demo action to change color of the line -->
- <ACTION id="change_color" type="Script" script="
- widget_line.background_color.r = rand(0,1);
- widget_line.background_color.g = rand(0,1);
- widget_line.background_color.b = rand(0,1);
- " requires="widget_line.background_color.*" />
- <!-- demo action to run when button is clicked -->
- <ACTION id="run_my_script" type="Script" script="
- /* update STRING - looks like a simple string, actually is a Kt::String */
- demo_string = "Param was: " + demo_param;
- /* update PARAM - looks like a simple variable, actually is a Kt::Param */
- demo_param++;
- /* run an ACTION - looks like a simple function, actually is a Kt::Action */
- change_color();
- " name="Run script" requires="demo_string;demo_param;change_color" />

You can see in the code, that accessing PARAM or STRING or ACTION from the script is easy and looks identical to accessing a regular variable (double, string) or a function calls. However, they are represented as a different types of objects. It can be important to understand if you want to pass them as an argument to a function, or run some specific operations, that are possible only with real strings or doubles (or with actual kuiml objects).
KUIML model elements like PARAM, STRING, ACTION, CURVE, SURFACE and events are made available to the scripting engine using the following classes (they are all reference types):
- Kt::Param : behaves like a double, supporting most common operators (++,–,+=,-=,*=,/= etc.)
There's also a parent Kt::ConstParam for read-only params. - Kt::String : behaves like a native string, implements basic operations, such as concatenation (+=)
There's also a parent Kt::ConstString for read-only strings. - Kt::Curve : acts as a unary function double f(double x). double v=curve(10.0); Can be copied with =
There's also a parent Kt::ConstCurve for read-only curves. - Kt::Surface : acts as a binary function double f(double x,double y); double v=surface(10.0, 20.0); Can be copied with =. There's also a parent Kt::ConstSurface for read-only surfaces.
- Kt::Action : simple function f(); Can be called via f()
- Kt::Event : supports += and -= operators to register callback functions.
- Kt::Object is just a base class for all the objects (listed above) in the scripting engine. Inherited by Kt::Action, Kt::ConstCurve, Kt::ConstParam, Kt::ConstString, Kt::ConstSurface, and Kt::Event.
Here's an example on how it can be useful to understand. We make two identical functions, call them seemingly identically, the only difference is the type of argument they receive.
- <!-- create a param -->
- <PARAM id="demo_param" min="0" max="20" default="10" />
- <!-- display param value -->
- <PARAM_TEXT param_id="demo_param" />
- <!-- two demo functions -->
- <SCRIPT script="
- void updateParam(double param) {
- param++;
- }
- void trulyUpdateParam(Kt::Param@ param) {
- param++;
- }
- " />
- <!-- demo action to run when button is clicked -->
- <ACTION id="run_my_script" type="Script" script="
- updateParam(demo_param); /* this doesn't really update param */
- trulyUpdateParam(demo_param); /* this works as expected */
- " name="Run script" requires="demo_param" />
- <!-- a simple button to run an action -->
- <SYSTEM_ACTION_BUTTON action_id="run_my_script" width="100" />
Another example how to create an empty "placeholder" reference in the script (Kt::Param@), and then attach it to real parameters.
- <!-- create a param -->
- <PARAM id="demo_param1" min="0" max="20" default="10" />
- <PARAM id="demo_param2" min="0" max="20" default="10" />
- <!-- display param value -->
- <PARAM_TEXT param_id="demo_param1" />
- <PARAM_TEXT param_id="demo_param2" />
- <!-- demo action to run when button is clicked -->
- <ACTION id="run_my_script" type="Script" script="
- Kt::Param@ param;
- @param = demo_param1; /* reference first param */
- param++;
- @param = demo_param2; /* reference second param */
- param++;
- " name="Run script" requires="demo_param1;demo_param2" />
- <!-- a simple button to run an action -->
- <SYSTEM_ACTION_BUTTON action_id="run_my_script" width="100" />
Access params as array
Though KUIML doesn't support arrays, we can use script arrays and use a nice trick to link script array to actual params.
- <!-- how much params in array we need -->
- <VARIABLE id="ARRAY_SIZE" value="10" />
- <!-- create a placeholder script array of references to params -->
- <SCRIPT script="
- array<Kt::Param@> my_params($ARRAY_SIZE$);
- " />
- <!-- repeat several times, updating $index$ variable -->
- <REPEAT count="$ARRAY_SIZE$">
- <!-- create a param -->
- <PARAM id="demo_param$index$" min="0" max="40" default="10" />
- <!-- link each param to an script array of Kt::Param@ -->
- <ACTION_TRIGGER event_id="window.loaded.value_changed" condition_formula="window.loaded==1" script="@my_params[$index$] = demo_param$index$;" requires="demo_param$index$" />
- </REPEAT>
- <!-- display param values -->
- <ROW spacing="1" height="60">
- <REPEAT count="$ARRAY_SIZE$" height="50">
- <COLUMN width="25" v_align="bottom">
- <!-- draw a vertical line -->
- <WIDGET id="w_column_$index$" width="20" height="30" background_color="#FF99CC" />
- <!-- display text value -->
- <PARAM_TEXT param_id="demo_param$index$" font_size="10" />
- </COLUMN>
- <!-- link param value to widget height and color -->
- <PARAM_LINK from="demo_param$index$" to="w_column_$index$.height" />
- <PARAM_LINK from="demo_param$index$" to="w_column_$index$.background_color.r" formula="x/40" />
- </REPEAT>
- </ROW>
- <!-- access params as items in an array -->
- <ACTION id="run_my_script" type="Script" script="
- for(uint i=0; i < my_params.length; i++) {
- my_params[i] = rand(0,40);
- }
- " name="Run script" />
- <!-- a simple button to run an action -->
- <SYSTEM_ACTION_BUTTON action_id="run_my_script" width="100" />

Search in a string
To do some string-related operations you usually need to convert Kt::String to a regular string.
- <!-- create a kuiml string -->
- <STRING id="my_string" default="Hello!" />
- <!-- trying to search in a string when it changes -->
- <ACTION_TRIGGER event_id="my_string.value_changed" script="
- /* first we convert Kt::String to a regular string */
- string s = my_string;
- /* now we can search in its contents */
- if (s.findFirst("World")>-1) {
- my_string = "Welcome!";
- }
- " name="Run script" requires="my_string" />
- <!-- display and edit a string -->
- <TEXT_FIELD string_id="my_string" width="100" />
Creating shorthands using auto
In this example we show how to create references to random KUIML object (using "auto" class), how to reference to a PARAM and a STRING, how to change a reference on the fly.
- <!-- widget for the demo -->
- <WIDGET id="my_widget" width="40" height="40" background_color="#000000" mouse_sensitive="true" />
- <!-- add global script variables -->
- <SCRIPT script="
- array<double> phase = {0, 0.4, 0.8, 1};
- " />
- <!-- a timer and an action -->
- <TIMER id="forever_timer" refresh_time_ms="20" />
- <ACTION_TRIGGER event_id="forever_timer.elapsed"
- script="
- /* create an alias/reference to background_color (unknown type object) */
- auto bg = my_widget.background_color;
- /* update widget's background_color */
- bg.r = cos(phase[0])/2 + 0.5;
- bg.g = cos(phase[1])/2 + 0.5;
- bg.b = cos(phase[2])/2 + 0.5;
- /* create a reference to widget itself */
- auto w = my_widget;
- w.width = w.height + w.height * (cos(phase[3])+1)*grow_param;
- /* create a reference to a PARAM (just because we can) */
- Kt::Param@ sp = speed_param;
- /* update phase for each color */
- phase[0] += 0.04*sp; /* acts as a double, though is Kt::Param@) */
- phase[1] += 0.08*sp;
- phase[3] += 0.1*speed_param;
- /* format hex value to a string */
- string hex = formatUInt(uint(bg.r*255), "0H", 2);
- hex += formatUInt(uint(bg.g*255), "0H", 2);
- hex += formatUInt(uint(bg.b*255), "0H", 2);
- /* let's create a reference to a STRING */
- Kt::String@ str = output_string;
- /* add some randomness just for fun */
- if (rand(0,100)>95) {
- /* change reference to another STRING */
- @str = output_string2;
- }
- /* update STRING contents */
- str = "color: #" + hex;
- " requires="my_widget.background_color.*;my_widget.height;my_widget.width;grow_param;speed_param;output_string;output_string2" />
- <!-- param and control -->
- <PARAM id="speed_param" min="0" max="4" default="1" />
- <PARAM id="grow_param" min="0" max="3" default="0.4" />
- <ROW spacing="10">
- <PARAM_TEXT_CONTROL param_id="speed_param" content="Speed: {value}" pixel_range="400" value_format="0.2" />
- <PARAM_TEXT_CONTROL param_id="grow_param" content="Grow: {value}" pixel_range="400" value_format="0.2" />
- </ROW>
- <!-- string and output -->
- <STRING id="output_string" default="" />
- <TEXT_FIELD string_id="output_string" width="300" font_size="12" />
- <STRING id="output_string2" default="" />
- <TEXT_FIELD string_id="output_string2" font_size="12" />

Binding functions to events
In scripts you can access Kt::Event objects and bind or unbind script functions to it (in KUIML you can do similar things with ACTION_TRIGGER). Here's a simple demo.
- <!-- create a param -->
- <PARAM id="demo_param" min="0" max="100000" default="10" />
- <!-- display param value -->
- <PARAM_TEXT param_id="demo_param" />
- <!-- draw a demo line -->
- <WIDGET id="demo_widget" background_color="#0000DD" width="20" height="5" />
- <!-- a timer and an action -->
- <TIMER id="forever_timer" refresh_time_ms="200" />
- <ACTION_TRIGGER event_id="forever_timer.elapsed"
- script=" demo_param+=rand(-20,20); " requires="demo_param" />
- <!-- several script functions -->
- <SCRIPT script="
- /* a function to change the width of the widget */
- void updateWidget(){
- demo_widget.width = demo_param;
- }
- /* bind a script function to an event */
- void bind(){
- demo_param.value_changed += updateWidget;
- }
- /* unbind a script function from an event */
- void unbind(){
- demo_param.value_changed -= updateWidget;
- }
- " requires="demo_param.value_changed;demo_widget.width" />
- <!-- demo actions to run when button is clicked -->
- <ACTION id="do_bind" type="Script" script="bind();" name="Bind" />
- <ACTION id="do_unbind" type="Script" script="unbind();" name="Unbind" />
- <!-- a simple buttons to run actions -->
- <SYSTEM_ACTION_BUTTON action_id="do_bind" width="100" />
- <SYSTEM_ACTION_BUTTON action_id="do_unbind" width="100" />

Accessing param default values
Sometimes you may need to access PARAM "default" attribute from script. In AngelScript "default" is a reserved word, so use "defaultValue" instead.
- <!-- create a param -->
- <PARAM id="demo_param" min="0" max="20" default="10" />
- <!-- display param defaultvalue -->
- <PARAM_TEXT_CONTROL param_id="demo_param.default" />
- <!-- demo action to run when button is clicked -->
- <ACTION id="run_my_script" type="Script" script="
- /* this will not work, cause 'default' is a reserved word in AngelScript */
- /* demo_param.default = rand(0,20); */
- /* instead of .default use .defaultValue */
- demo_param.defaultValue = rand(0,20);
- " name="Run script" requires="demo_param.default" />
- <!-- a simple button to run an action -->
- <SYSTEM_ACTION_BUTTON action_id="run_my_script" width="100" />
Objects Exposure
For performance reasons, the KUIML engine does not create script objects for all elements that could be exposed by the data model.
So it is necessary to explicitely describe the objects that will be used by scripts so that they are properly exposed. See the "requires" attribute of the SCRIPT and EXPOSED_OBJECTS element.
You can of course expose all objects using the '*' wildcard to start writing the scripts, but exposed elements should definitely be filtered before releasing, or customers may encounter performance issues, especially with complex user interfaces with multiple elements.
Comments
Please, authorize to view and post comments.