JS Painter Guide

Overview

The JS Painter implementation permits overriding any UI object's painting method with one written in Javascript. It employs the same mgraphics API which may be used from the jsui object with a few additions. This provides the advantage over using the jsui object for custom user interface in that it permits the standard threading behavior of Max objects (i.e. processing scheduler events in the high priority scheduler thread, and painting in the low priority main application thread), rather than forcing all object logic to be executed in the low priority main application thread as is the case for the jsui object. So you can have a custom dial or slider interface for example, and if numbers are passing through the object at high priority, their timing will not be adversely affected as they would with the jsui object.

Basic Usage

To override the object's paint method, simply create a javascript file in the max search path, define a javascript function (method) named "paint", and set the object's jspainterfile attribute to the file's filename. In your paint method, you may query the underlying native C object's value from the box.getvalueof() method, and any of the underlying native C object's attributes via the box.getattr("attributename") method to inform drawing. Otherwise, your paint method should look similar to how you might program a custom UI object for jsui. Below is an example of how you could program in javascript an interface that looks virtually identical to Max's native toggle object:


function paint()
{
	var val = box.getvalueof()[0]; // this is an array of size 1
	var viewsize = mgraphics.size;
	var valrange = box.getattr("size");
	var width = viewsize[0];
	var height = viewsize[1];
	var start;

	mgraphics.set_source_rgba(box.getattr("bgcolor"));
	mgraphics.rectangle(0, 0, width, height);
	mgraphics.fill();

	if (val) {
		mgraphics.set_source_rgba(box.getattr("checkedcolor"));	
	} else {
		mgraphics.set_source_rgba(box.getattr("uncheckedcolor"));
	}

	mgraphics.set_line_width((2./12.) *  box.getattr("thickness") * 0.01 * width);  // top left to bottom right, thin
	mgraphics.set_line_cap("square");

	start = (7./24.) * width;
	
	mgraphics.move_to(start, start);
	mgraphics.line_to(width - start, height - start);
	mgraphics.stroke();

	mgraphics.move_to(width - start, start);
	mgraphics.line_to(start, height - start);
	mgraphics.stroke();

}

Additional functionality

In addition to the standard mgraphics functionality, you may also call the underlying native C object's paint method to render an object's standard UI and add interface elements on top of it using the mgraphics.parentpaint() method. For example, here's a way to render a border on top of the standard native object UI using the underlying object's "elementcolor" style color:


function paint()
{
	var viewsize = mgraphics.size;
	var width = viewsize[0];
	var height = viewsize[1];
	
	// call original object paint method
	mgraphics.parentpaint(); 
	
	// the actual underlying attribute name may be different
	// so we use the attrname_forstylemap() method to map 
	// the style color to object attribute name
	var colorname = box.attrname_forstylemap("elementcolor");
	var bordercolor = box.getattr(colorname);
	
	// draw border rectangle over it 
	mgraphics.set_source_rgba(bordercolor);
	mgraphics.rectangle(0.5, 0.5, width-1, height-1);
	mgraphics.stroke();
		
}

Limitations

At the time of writing, there are some limitations to what you may do with the JS Painter implementation. Firstly, there is no access to mousing events and logic. All such event processing will remain handled by the native C object. Also, any use of the Task object to support timing information in the UI is not supported. Also, objects with text fields will have some issues. Text may be rendered with standard mgraphics API calls, but as is the case with JSUI, there is no automatic linewrapping, and if the underlying object has a textfield (as do number boxes, message objects, comment objects and the standard text object), there will be conflicts or missing text. For this reason the jspainterfile attribute is hidden for such objects. And finally, there is no support for autowatch or double clicking to open the jspainterfile in an in app editor.