Using SVG files with JSUI

maybites's icon

Hi

One badly documented topic in Max is the usage of SVG images for userinterfaces. It took me a moment to figure this out:

mgraphics.init();
mgraphics.relative_coords = 0;
mgraphics.autofill = 0;

var g_icon;
var defaultColor = [0., 0., 0., 1.];
var myval = 0;

var pressColor = [1., 0., 0., 1.]
var releaseColor = [0., 1., 0., 1.]

// process arguments
if (jsarguments.length>1)
    g_icon = new MGraphicsSVG(jsarguments[1]);    
    
function bang()
{
    myval = 1 - myval; // toggle 0/1
    mgraphics.redraw();
}

function paint()
{
    gc(),

    g_icon.mapcolor(defaultColor, (myval == 0)?releaseColor:pressColor);

    mgraphics.translate(1,1);
    mgraphics.scale(1.,1.);
    mgraphics.svg_render(g_icon);
}

function onclick()
{
    bang();
    outlet(0, myval);
}
onclick.local = 1; //private

this is a simple code for a jsui toggle using a SVG-file (with black lines) to use as an image for toggling between two colors.

all you need to provide in the 'jsarguments' attribute is the filepath to the SVG.

Max Patch
Copy patch and select New From Clipboard in Max.

I hope this helps some of you out there that don't like the [pictctrl] object as I do.

maybites's icon

here another variation, instead providing two svg to toggle between two states:

mgraphics.init();
mgraphics.relative_coords = 0;
mgraphics.autofill = 0;

var g_icon_off;
var g_icon_on;

var myToggle = 0;
var myScale = 1.

// process arguments
if (jsarguments.length>1)
    g_icon_off = new MGraphicsSVG(jsarguments[1]);    
if (jsarguments.length>2)
    g_icon_on = new MGraphicsSVG(jsarguments[2]);    

onresize();

function bang()
{
    myToggle = 1 - myToggle;
    mgraphics.redraw();
    notifyclients();
}

function paint()
{
     gc();
    
    mgraphics.translate(3,1);
    mgraphics.scale(myScale, myScale);
    mgraphics.svg_render((myToggle == 0)?g_icon_off:g_icon_on);
}

function onclick()
{
    bang();
    outlet(0, myToggle);
}
onclick.local = 1; //private

function onresize()
{
    myScale = Math.min(this.box.rect[2] - this.box.rect[0], this.box.rect[3] - this.box.rect[1]) / Math.max(g_icon_on.size[0],g_icon_on.size[1]); 
}
onresize.local = 1;

function getvalueof () { 
    return myToggle 
}
    
function setvalueof (val) {
    myToggle = val;
}
Max Patch
Copy patch and select New From Clipboard in Max.

Rob Ramirez's icon

thanks for sharing!

maybites's icon

No worries. But I just realized this approach is not scaling when using presentation mode. Any hints?

tyler mazaika's icon

IIRC for presentation rect stuff with jsui you can use this.box.getattr(“presentation_rect”) to determine the correct bounds/image size. I think you can now also configure a MaxObjlistener to observe presentation_rect attribute (to simulate the effect onresize() gets you in patching mode).

maybites's icon

Is there a way to detect presentation mode of the parent patcher from within js?

maybites's icon

thanks to the getattr() function there is a way to query the presentation mode of the parent patcher. Unfortunately the onresize() function is not triggering in presentation mode and the presentation attribute of the parent patcher is not working with listeners, but I think I found a way to keep the housekeeping functions to a minimum:

// Created my maybites.ch (2021) - License: Free to use and hack. happy patching.

mgraphics.init();
mgraphics.relative_coords = 0;
mgraphics.autofill = 0;

var g_icon_off;
var g_icon_on;

var myToggle = 0;
var myScale = 1.

var myOffset = [0., 0.];

var hasResized = false;
var presentation = 0;

declareattribute("offset","getmyOffset","setmyOffset");

// listeners on the rect values to keep track on changes 
//     reason: onresize() is not working in presentation mode

var l1 = new MaxobjListener(this, "patching_rect", rect_changed);
var l2 = new MaxobjListener(this, "presentation_rect", rect_changed);

// process arguments
if (jsarguments.length>1)
    g_icon_off = new MGraphicsSVG(jsarguments[1]);    
if (jsarguments.length>2)
    g_icon_on = new MGraphicsSVG(jsarguments[2]);    

rescale();

// updating the gui and output.
function bang()
{
        mgraphics.redraw();
    outlet(0, myToggle);
}

// drawing function
function paint()
{
    gc();
    
        check_mode();    
        rescale();
    
        mgraphics.translate(myOffset);
        mgraphics.scale(myScale, myScale);
        mgraphics.svg_render((myToggle == 0)?g_icon_off:g_icon_on);
}

// wating for a mouseclick
function onclick()
{
    myToggle = 1 - myToggle;
    notifyclients();
    bang();
}
onclick.local = 1; //private

// called by rect listeners
function rect_changed(data){
    hasResized = true;
}

// check presentation mode
function check_mode(){
    if(presentation != this.patcher.getattr("presentation")){
        presentation = this.patcher.getattr("presentation");
        hasResized = true;
    }
}

// calculate new scale depending on mode 
function rescale()
{
    if(hasResized){    
        var boxRect = this.box.getattr("patching_rect");
        if(this.patcher.getattr("presentation") == 1){
            boxRect = this.box.getattr("presentation_rect");
        }
    
        myScale = Math.min(boxRect[2], boxRect[3]) / Math.max(g_icon_on.size[0],g_icon_on.size[1]); 
    }
    hasResized = false;
}

// get function for pattr system
function getvalueof () { 
    return myToggle 
}

// set function for pattr sysemt
function setvalueof (val) {
        myToggle = val;
        bang();
}

// attribute setter
function setmyOffset(posX, posY){
    myOffset = [posX, posY];
}

//attribute getter
function getmyOffset(){
    return myOffset;
}