Files
2026-08-16 18:24:52 +07:00

22 KiB

UI Description Scripting

Introduction

There is simple scripting support added to the uidescription editor when the cmake option VSTGUI_UISCRIPTING is turned on. This scripting language is based on JavaScript via the TinyJS library and supports variables, arrays, structures and objects with inheritance (not fully implemented).

Getting started

You need to add the scripting library to your target. With cmake this is done by adding target_link_libraries(${target} PRIVATE vstgui_uiscripting) to your CMakeLists.txt.

The library needs to be initialized once at runtime before any UIDescription object is created. This is done with a call to VSTGUI::UIScripting::init ();. You need to include "vstgui/uiscripting/uiscripting.h" for this symbol.

The init function accepts two optional parameters. The first parameter is a callback function that will be called whenever an exception occurs within a script, allowing you to handle any errors or exceptions that may arise. The second parameter is also a function that is invoked when a script is loaded, providing its name as input. This feature can be used, for instance, to load scripts directly from your source repository instead of the default location in the resource folder of your plug-in or application.

UIScripting::ReadScriptContentsFunc loadScriptFromRepositoryPath = {};
#if DEBUG
// in Debug mode, we want to load the scripts from the repository instead of from the app
// resource folder as the scripts in the app resource folder are only synchronized when we build
// the app and not in-between.
loadScriptFromRepositoryPath = [] (auto filename) -> std::string {
	std::filesystem::path path (__FILE__);
	if (!path.empty ())
	{
		path = path.parent_path ().parent_path ();
		path.append ("resource");
		path.append ("scripts");
		path.append (filename);
		if (std::filesystem::exists (path))
		{
			std::ifstream f (path, std::ios::in | std::ios::binary);
			const auto sz = std::filesystem::file_size (path);
			std::string result (sz, '\0');
			f.read (result.data (), sz);
			return result;
		}
	}
	return {};
};
#endif

UIScripting::init ({}, loadScriptFromRepositoryPath);

All scripts of one UIDescription object will be executed in the same JavaScript context. So you can access global variables from all your scripts if needed.

The view script

A script runs when a view is created and has a script assigned to its script attribute. The script attribute either contains the script text or a reference to a file inside the resource directory of the plugin/application containing the script text.

A reference to a script file is done via

//scriptfile $SCRIPT_FILENAME

The script is executed directly after the view was created and before its other attributes are set and before child views are added.

The view variable

The script contains a view variable which among other things can be used to add listeners on actions on the view. To install a function that is executed whenever the mouse enters the view do:

view.onMouseEnter = function (view, event) {
  // do something when the mouse enters this view...
};

See View Listeners which listeners can be installed on views. And see View methods and properties for the other methods a view supports.

If the view is a control it additionally supports the methods described in Control methods and properties and the listeners as described in Control listeners.

If the view is a view container then it supports the additional listeners described in View container listeners.

The JavaScriptDrawableView

You can add a JavaScriptDrawableView from the view types to your view hierarchy to create a view that you need to completely program via the script.

Event handling is done via the previously mentioned listeners and drawing is done by implementing the draw function of the view:

view.draw = function(drawContext, dirtyRect) {
}

The drawContext parameter is the object where you call its method to draw things into the view and the dirtyRect parameter contains the rectangle that needs to be drawn.

Additionally the following two functions can be implemented to provide custom focus drawing:

view.drawFocusOnTop = function () { return false; };

view.getFocusPath = function(path, focusWidth) {
	var bounds = view.getBounds();
	path.addRoundRect (bounds, 4);
	bounds.left -= focusWidth;
	bounds.right += focusWidth;
	bounds.top -= focusWidth;
	bounds.bottom += focusWidth;
	path.addRoundRect (bounds, 4);
	return true;
};

See the C++ IFocusDrawing interface for a description of these methods.

The JavaScriptDrawableControl

You can add a JavaScriptDrawableControl from the view types to your view hierarchy to create a control that you need to completely program via the script.

In addition to the JavaScriptDrawableView you can also use the Control methods and properties and Control listeners on this view.

The uiDesc variable

The script also contains the uiDesc variable you can use to query information on the uiDesc file. The following methods are implemented on that object:

name arguments return type comments
colorNames array<string> get all color names from the UIDesc file
fontNames array<string> get all font names from the UIDesc file
bitmapNames array<string> get all bitmap names from the UIDesc file
gradientNames array<string> get all gradient names from the UIDesc file
controlTagNames array<string> get all control tag names from the UIDesc file
getTagForName name:string integer get the tag for the control tag name
lookupTagName tag:integer string get the control tag name from the tag

Interacting with c++ code

To interact with the c++ code set a subcontroller on the view or its parents and extend the IController with IScriptControllerExtension and implement its methods. From the script you can call the view methods getControllerProperty or setControllerProperty.

A property can either be an integer, floating point, string or undefined.

View methods and properties

type name arguments return type comments
property type string contains the type of the view like CAnimKnob
method isTypeOf name:string integer returns true if the view is of type name
method getParent object returns the parent view or undefined if it has none
method getAttribute key:string string get the attribute with name key
method setAttribute key:string,value:string string set the attribute value with name key to value
method getControllerProperty name:string property set a controller property. returns true if succeeded
method setControllerProperty name:string,value:property property get a controller property. returns a property
method getBounds object returns the bounds of the view as a rectangle

For example to set the opacity attribute of a view write:

view.setAttribute("opacity", 0.5);

You can set any attribute as shown in the WYSIWYG editor.

View listeners

name arguments
onAttached view:object
onRemoved view:object
onSizeChanged view:object,newSize:rect
onLostFocus view:object
onTookFocus view:object
onMouseEnabled view:object,state:boolean
onMouseEnter view:object,event:object
onMouseExit view:object,event:object
onMouseDown view:object,event:object
onMouseUp view:object,event:object
onMouseMove view:object,event:object
onMouseWheel view:object,event:object
onKeyDown view:object,event:object
onKeyUp view:object,event:object

TODO: Describe Event object

Control methods and properties

type name arguments return type comments
method setValue value:double void set the control value to value
method getValue double get the control value
method setValueNormalized value:double void set the control value from the normalized value
method getValueNormalized double get the normalized control value
method beginEdit void begin editing the control
method endEdit void end editing the control
method getMinValue double get the minimum value of the control
method getMaxValue double get the maximum value of the control
method getTag integer get the control tag

Control listeners

name arguments
onValueChanged view:object,value:double
onBeginEdit view:object
onEndEdit view:object

View container listeners

name arguments
onViewAdded view:object,child:object
onViewRemoved view:object,child:object

The drawcontext object

name arguments return type comments
clearRect rect:Rect void
createRoundGraphicsPath rect:Rect,radius:double Path
createGraphicsPath Path
createGradient startColorPosition:double,startColor:Color,endColorPosition:double,endColor:Color Gradient
getStringWidth string:string double
drawArc rect:Rect,startAngle:double,endAngle:double,style:string void style can be stroked,filled or filledAndStroked
drawBitmap name:string,destRect:Rect,offsetPoint?:Point,alpha?:double void name is the bitmapname as declared in the uidesc file
drawEllipse rect:Rect,style:string void style can be stroked,filled or filledAndStroked
drawGraphicsPath path:Path,mode?:string,transform?:TransformMatrix void mode can be stroked, filled or filledEvenOdd
drawLine from:Point,to:Point void
drawPolygon points:PointArray,style:string void style can be stroked,filled or filledAndStroked
drawRect rect:Rect,style:string void style can be stroked,filled or filledAndStroked
drawString string:string,rect:Rect,align?:string void align can be left, center or right
fillLinearGradient path:Path,gradient:Gradient,startPoint:Point,endPoint:Point,evenOdd?:bool,transform?:TransformMatrix void
fillRadialGradient path:Path,gradient:Gradient,centerPoint:Point,radius:double,originOffsetPoint?:Point,evenOdd?:bool,transform?:TransformMatrix void
restoreGlobalState void
saveGlobalState void
setClipRect rect:Rect void
setFont name:string void name is the fontname as declared in the uidesc file
setFontColor color:Color void
setFillColor color:Color void
setFrameColor color:Color void
setGlobalAlpha alpha:double void
setLineWidth width:double void
setLineStyle styleOrLineCap:string,lineJoin?:string,dashLengths?:doubleArray,dashPhase?:double void
setDrawMode mode:string void

The setLineStyle method takes either one argument: the style which can be solidordotted. Or it can take 4 arguments: * lineCap [required] : butt, roundorsquare* lineJoin [opt] :miter, round or bevel * dashLength [opt] : doubleArray * dashPhase [opt] : double

The Rect object has 4 properties: left, top, right and bottom.

The Point object has 2 properties: x and y.

The Color object is either a CSS color name, a colorname as described in the uidesc file or an rgba value in hex form: #FF00FFAA.

The path object

The path object can be created via the drawcontext object methods createGraphicsPath and createRoundGraphicsPath and has these methods:

name arguments
addEllipse rect:Rect
addArc rect:Rect,startAngle:double,endAngle:double,clockwise:bool
addBezierCurve control1:Point,control2:Point,end:Point
addLine to:Point
addPath path:Path,transformMatrix?:TransformMatrix
addRect rect:Rect
addRoundRect rect:Rect,radius:double
closeSubpath
beginSubpath start:Point

The TransformMatrix object

A transform matrix object is created via the global makeTransformMatrix () function and has these methods:

name arguments return type
concat transformMatrix:TransformMatrix void
inverse TransformMatrix
rotate angle:double, center?:Point void
scale x:double, y:double void
skewX angle:double void
skewY angle:double void
translate x:double, y:double void
transform pointOrRect:Point or Rect Point or Rect

Global functions

  • createTimer(context, fireTime, callback) -> TimerObject
    • creates a new stopped timer object
    • fire time is in milliseconds
    • the context will be provided on every timer callback
    • the callback has the signature function(context)
    • a timer object has the methods:
      • start()
      • stop()
      • invalid()
  • iterateSubViews(view, context, callback) -> Void
    • calls the callback with context as parameter for every child view of view
  • log(obj) -> Void
    • logs the object to the debug console
  • makeTransformMatrix() -> TransformMatrix

Other built in functions (from the TinyJS library)

  • exec(jsCode)
  • eval(jsCode)
  • trace()
  • charToInt(ch)
  • Object.dump()
  • Object.clone()
  • String.indexOf(search)
  • String.substring(lo,hi)
  • String.charAt(pos)
  • String.charCodeAt(pos)
  • String.fromCharCode(cha
  • String.split(separator)
  • Integer.parseInt(str)
  • Integer.valueOf(str)
  • JSON.stringify(obj)
  • Array.contains(obj)
  • Array.remove(obj)
  • Array.join(separator)
  • Math.rand()
  • Math.randInt(min, max)
  • Math.abs(a)
  • Math.round(a)
  • Math.min(a,b)
  • Math.max(a,b)
  • Math.range(x,a,b)
  • Math.sign(a)
  • Math.PI()
  • Math.toDegrees(a)
  • Math.toRadians(a)
  • Math.sin(a)
  • Math.asin(a)
  • Math.cos(a)
  • Math.acos(a)
  • Math.tan(a)
  • Math.atan(a)
  • Math.sinh(a)
  • Math.asinh(a)
  • Math.cosh(a)
  • Math.acosh(a)
  • Math.tanh(a)
  • Math.atanh(a)
  • Math.E()
  • Math.log(a)
  • Math.log10(a)
  • Math.exp(a)
  • Math.pow(a,b)
  • Math.sqr(a)
  • Math.sqrt(a)

Example

// Hover Opacity Animation Script
// This example script changes the opacity of the view
// when the mouse enters or exits the view

/* the default opacity of the view is stored in view.default_opacity */
view.default_opacity = 0.6;

/* the current opacity of the view is stored in view.opacity */
view.opacity = view.default_opacity;

/* the timer to change the opacity is stored in view.opacity_timer */
view.opacity_timer = createTimer(view, 16, function(view) {
	view.opacity += view.opacity_change;
	if (view.opacity_change > 0)
	{
		if (view.opacity > 1)
		{
			view.opacity = 1;
			view.opacity_timer.stop();
		}
	}
	else
	{
		if (view.opacity <= view.default_opacity)
		{
			view.opacity = view.default_opacity;
			view.opacity_timer.stop();
		}
	}
	view.setAttribute("opacity", view.opacity);
});

/* the view will be shown with full opacity when focused so the state of the focus is stored in view.hasFocus */
view.has_focus = false;

/* to correctly restore the hover state after focus lost, the state if the mouse is inside the view or outside 
   is stored in view.mouseInside
*/
view.mouse_inside = false;

/* we install a mouse enter listener
   when the mouse enters the view we start the opacity change timer 
*/
view.onMouseEnter = function(view, event) {
	view.mouse_inside = true;
	if (view.has_focus)
		return;
	view.opacity_change = 0.075;
	view.opacity_timer.start();
	event.consume = true;
};

/* we also install a mouse exit listener
   when the mouse exits the view we start the opacity change timer again 
   now with a negative opacity_change variable so that in the timer callback 
   the opacity is going back to the default opacity 
*/
view.onMouseExit = function(view, event) {
	view.mouse_inside = false;
	if (view.has_focus)
		return;
	view.opacity_change = -0.05;
	view.opacity_timer.start();
	event.consumed = true;
};

/* we also install a view removed listener so that we can cleanup and stop the timer */
view.onRemoved = function(view) {
	// cleanup, when the view is removed, stop the timer
	view.opacity_timer.stop();
};

/* when the view takes focus we show the view with full opacity */
view.onTookFocus = function(view) {
	view.has_focus = true;
	view.opacity_timer.stop();
	view.setAttribute("opacity", 1);
	view.opacity = 1;
};

/* when the view lost focus we start the opacity animation when the mouse is not inside this view */
view.onLostFocus = function(view) {
	view.has_focus = false;
	if (!view.mouse_inside)
	{
		view.onMouseExit(view, undefind);
	}
};

/* enable the mouse, otherwise no mouse listener is called */
view.setAttribute("mouse-enabled", true);

/* set the initial view opacity*/
view.setAttribute("opacity", view.opacity);