The User Interface (UI)

Defining UI for the user

The widgets available to the plugin writer are the same ones which are available in RAYZ. The following shows the types of widgets available: click on any widget to see the code for creating and using it.


Syntax for Defining Widgets:

Rules for widgets: Each widget has a label, which appears in the node panel, and a name, which is used to retrieve the value. The label can be any string, with or without spaces, numbers, etc. The name, on the other hand, must be: The name is an internal reference tag, so all names must be unique.


Integer
Syntax:  cpiAddInteger(const char *name, const char *label, CPI_Int32
default,
                       CPI_Int32 min_val, CPI_Int32 max_val,
                       CPI_Int32 flags, const char *help, const char *html);
Example: cpiAddInteger("integer", "Integer", 1, 0, 10, CPI_PARM_ANIMATABLE, "Number of loops", NULL); Get Value: cpiGetInteger( &intval, "integer", myTime );

Float
Syntax:  cpiAddFloat(const char *name, const char *label, CPI_Float32 default, 
                     CPI_Float32 min_val, CPI_Float32 max_val,
                     CPI_Int32 flags, const char *help, const char *html);
Example: cpiAddFloat("float", "Float", 1.0, 0.0, 10.0, CPI_PARM_ANIMATABLE, "Float value parameter", NULL); Get Value: cpiGetFloat( &fval, "float", myTime );

Integer Pair
Syntax:
cpiAddIntPair(const char *nameTop, const char *labelTop, 
              const char *name1, const char *label1, CPI_Int32 default1,
              const char *name2, const char *label2, CPI_Int32 default2,
              CPI_Int32 flags, const char *help, const char *html);
Example:
cpiAddIntPair("int_pair", "Int pair", 
              "w", "Width", 720, "h", "Height", 486,
              CPI_PARM_DEFAULT,
              "Pair of integer parameters", NULL );
Get Values:
cpiGetInteger( &width, "int_pair.w", myTime );
cpiGetInteger( &height, "int_pair.h", myTime );

Float Pair
Syntax:
cpiAddFloatPair(const char *nameTop, const char *labelTop,
                const char *name1, const char *label1, CPI_Float32 default1,
                const char *name2, const char *label2, CPI_Float32 default2,
                CPI_Int32 flags, const char *help, const char *html);
Example:
cpiAddFloatPair("float_pair", "Float pair", 
                "x", "X", 1.0, "y", "Y", 0.75,
                CPI_PARM_DEFAULT,
                "Pair of floating point values", NULL );
Get Values:
cpiGetFloat( &width, "float_pair.x", myTime );
cpiGetFloat( &height, "float_pair.y", myTime );

Menu
Syntax:  cpiAddMenu(const char *name, const char *label, 
                    CPI_Uint8 default, const char *menulist,
                    const char *help, const char *html);
Example: 
static const char *mymenu[]=
{
    "lion",
    "tiger",
    "bear",
    NULL
};

cpiAddMenu( "menu", "Menu", 0, mymenu,
            "Choose which type of animal from menu", NULL );
Get Values: 
CPI_Int32 menuval;
cpiGetInteger ( &menuval, "menu", myTime );
// In this example, 0 == lion, 1 == tiger, 2 == bear

File Input
Syntax:  cpiAddFile(const char *name, const char *label, 
                         const char *default,
                         const char *help, const char *html);
Example: cpiAddFile("file", "File", "/tmp/myfile",
                         "Please enter a file name", NULL );
Get Values: 
#define MAXSIZE 128
char fname[MAXSIZE];
cpiGetString( fname, MAXSIZE, "file.file_id", myTime );

String
Syntax:  cpiAddString(const char *name, const char *label, 
                         const char *val1, CPI_Bool multiline,
                         CPI_Int32 flags, const char *help, const char *html);
Example: cpiAddString("string", "String", "default string", CPI_FALSE,
                          CPI_PARM_DEFAULT,
                         "Enter a single line string", NULL );
Get Values: 
#define MAXSIZE 128
char strval[MAXSIZE];
cpiGetString( strval, MAXSIZE, "string", myTime );

Text
Syntax:  cpiAddString(const char *name, const char *label, 
                         const char *val1, CPI_Bool multiline,
                         CPI_Int32 flags, const char *help, const char *html);
Example: cpiAddString("text", "Text Entry", "default string", CPI_TRUE,
                          CPI_PARM_DEFAULT,
                         "Enter a block of input text", NULL );
Get Values: 
#define MAXSIZE 512
char strval[MAXSIZE];
cpiGetString( strval, MAXSIZE, "text", myTime );

Display Text (read-only)
Syntax:  cpiAddDisplayString(const char *name, const char *label, 
                         const char *string);
Example: cpiAddDisplayString("disp_text", "Display text", 
                         "rest of string");

Color Input
Syntax:  cpiAddColorGroup(const char *name, const char *label, 
                          CPI_Float32 *defaults, CPI_Int32 numargs, 
                          CPI_Int32 flags, 
                          const char *help, const char *html);
Example: 
CPI_Float32     def[4] = {0.5, 0.2, 0.9, 1.0};
cpiAddColorGroup("color", "Color", def, 4, CPI_PARM_DEFAULT,
                 "This is a color selector", NULL );
Get Values:
CPI_Float32 red, green, blue, alpha;
cpiGetFloat( &red, "color.red", myTime );
cpiGetFloat( &green, "color.green", myTime );
cpiGetFloat( &blue, "color.blue", myTime );
cpiGetFloat( &alpha, "color.alpha", myTime );

Hue Adjust
Syntax:
cpiAddHueCircle(const char *name, const char *label, CPI_Float32 default,
                CPI_Int32 flags, const char *help, const char *html);
Example:
cpiAddHueCircle("hue", "Hue Circle", 0.0,
                CPI_PARM_DEFAULT,
                "A hue adjustment circle", NULL );
Get Values:
cpiGetFloat( &hue, "hue", myTime );

Toggle
Syntax:  cpiAddToggle(const char *name, const char *label, CPI_Bool default,
                         CPI_Int32 flags, const char *help, const char *html);
Example: cpiAddToggle( "toggle", "Toggle", CPI_TRUE, CPI_PARM_DEFAULT,
                         "Toggle button which defaults to true", NULL );
Get Values:
cpiGetInteger( &toggle, "toggle", myTime );    // 0 = off, 1 == on

Button
Syntax:  cpiAddButton(const char *name, const char *label, void *callback,
                         CPI_Int32 flags, const char *help, const char *html);
Example: 
void
WidgetCallback( const char      *nodename,
                const char      *parmname,
                CPI_Float32      time )
{
    printf ("Got a button callback for node %s, parm %s at time %5.5f\n",
             nodename, parmname, time);
    cpiSetInteger( (CPI_Int32)time, "Integer", time);
}

...

cpiAddButton("push", "push me", WidgetCallback,
              CPI_PARM_DEFAULT,
             "This prints something when pressed", NULL );
Get Values: 
This does not return a value - when pressed, it causes the immediate 
execution of the named function.

Nested Group of Parameters
Syntax:
cpiStartGroup(const char *name, const char *label, CPI_Int32 flags, 
              const char *help, const char *html);
    // add parameters here
cpiEndGroup();
Example:
cpiStartGroup("group1", "Group 1", CPI_PARM_DEFAULT,
               "This is a nested group of parameters", NULL );
cpiAddFloat("float2", "Float 2", 0.0, -1.0, 1.0, CPI_PARM_ANIMATABLE,
             "Floating point parameter", NULL );
cpiEndGroup();

Get Values: cpiGetFloat( &floatval, "group1.float2", myTime );

Variable-Length List of Elements
This feature allows the user to build a list of parameters or parameter groups - an example in RAYZ is the Track node. These are built in two parts. The first is to create a function which defines the contents of each entry in the Var List. The second is to manage the list of entries with the routines which add, move, and remove Var List entries.

Normally (as in Track), a button is presented to the user to create a new entry. This button is optional. Entries can also be added programatically.

Syntax: 
To define a Var List:
cpiStartVarList();
cpiDefineVarListGroup( const char *name, const char *label,
                       (void *)addFunction, );
cpiEndVarList(const char *name, const char *listname, 
              CPI_Int32 flags, CPI_Bool addButtons );

To manipulate a Var List:
cpiAddVarListGroup( const char *varlistname, const char *entryname );
cpiRemoveVarListGroup( const char *varlistname, CPI_Uint32 entrynum );
cpiMoveVarListGroup( const char *varlistname, CPI_Uint32 entryToMove,
                     CPI_Uint32 moveBefore );
cpiGetVarListSize( const char *varlistname, CPI_Uint32 *size );

All routines return CPI_Bool which is CPI_TRUE if the call succeeded.

Example: 
First, you define a routine which creates the contents of a specific entry in the list:
void
addNewElement( void )
{
    cpiAddInteger( "new_thing", "new thing", 0, 0, 10, CPI_PARM_DEFAULT,
                   "This was added to the var list", NULL );
}
And then in the Init() routine, you would add the following lines. The middle statement defines what happens when an entry is added to the list, and optionally adds a button to create a new entry.
...
cpiStartVarList();
cpiDefineVarListGroup( "add_new", "Add New Element", 
                       (void *)addNewElement, CPI_TRUE );
cpiEndVarList( "add_new", "Var List", CPI_PARM_DEFAULT);

Get Values:
Each element is addressed with '(n)' where n is the position in the list created by the user. n begins at 0.
cpiGetInteger( &intval, "var_list(0).add_new.new_thing", myTime);

Integer Group
Syntax:
cpiAddIntRGBAOGroup(const char *name, const char *label, CPI_Int32 defval,
                    CPI_Int32 minval, CPI_Int32 maxval, CPI_Int32 numchans,
                    CPI_Int32 flags, const char *help, const char *html);
Example:
cpiAddIntRGBAOGroup("int_group", "Int Group", 0, 0, 100, 3,
                    CPI_PARM_DEFAULT,
                    "A related group of 3 integer channels", NULL );
Get Values:
cpiGetInteger( &overall, "int_group.int_group", myTime );
cpiGetInteger( &red, "int_group.red", myTime );
cpiGetInteger( &green, "int_group.green", myTime );
cpiGetInteger( &blue, "int_group.blue", myTime );

Float Group
Syntax:
cpiAddFloatRGBAOGroup(const char *name, const char *label, CPI_Float32 defval,
                      CPI_Float32 minval, CPI_Float32 maxval, 
                      CPI_Int32 numchans, CPI_Int32 flags, 
                      const char *help, const char *html);
Example:
cpiAddFloatRGBAOGroup("float_group", "Float Group", 
                      0.0, -1.0, 1.0, 5,
                      CPI_PARM_DEFAULT,
                      "A related group of 5 float channels", NULL );
Get Values:
cpiGetFloat( &overall, "float_group.float_group", myTime );
cpiGetFloat( &red, "float_group.red", myTime );
cpiGetFloat( &green, "float_group.green", myTime );
cpiGetFloat( &blue, "float_group.blue", myTime );
cpiGetFloat( &alpha, "float_group.alpha", myTime );
cpiGetFloat( &other, "float_group.other", myTime );

Channel Selection
Syntax:  cpiAddChannelSelect(const char *help, const char *html);
Example: cpiAddChannelSelect("Select channels to operate on", NULL);
Get Values:
CPI_Int32 chan;
cpiGetChannelSelect( &chan );

Enabling and Disabling Widgets

Parameter widgets can be enabled and disabled depending on changes of state in the node - for example, the value of another parameter, or the number of inputs connected to the node.

The function for this is the very simply defined as

void cpiEnableParameter( const char *parmname, CPI_Bool enable )
Two examples of the use of this: in the first, we enable or disable parameters based on the presence or absence of a second input. This is done by defining the InputChanged function, and responding to that event by checking the number of connected inputs. That looks like
void
BlurXYInputChanged( CPI_Uint32 inputnum )
{
    CPI_Uint32          numInputs;
    CPI_Bool            enable;

    cpiGetNumInputs( &numInputs );

    // enable/disable the mask parameters depending on the presence
    // if the mask input
    enable = CPI_FALSE;
    if ( numInputs == 2 )
        enable = CPI_TRUE;

    cpiEnableParameter( "mask_action", enable );
    cpiEnableParameter( "mask_channel", enable );
}

In the second example, the user can choose between entering values as float values or as pixel values, and he tells the plugin which he is using via a menu widget. To implement this, we define the ParmChanged function. This is called whenever a parameter value changes, and it is called with the internal name of the parameter. It is called with the name string set to NULL when the node is first initialized. Our example looks like this

static const char *unitmenu[]=
{
    "pixels",
    "fractions",
    NULL
};


// This shows the definitions of the parameters we are using

static CPI_Bool
EnableInit( void )
{
    cpiAddMenu( "units", "Units", 0, unitmenu,
                "Choose which units to enter values in", NULL );

    cpiAddIntPair( "pixels", "Pixels",
                   "x", "Width", 10, "y", "Height", 10,
                   CPI_PARM_DEFAULT, NULL, NULL );

    cpiAddFloatPair( "fractions", "Fractions",
            "x", "Width", 0.1, "y", "Height", 0.1,
            CPI_PARM_DEFAULT, NULL, NULL );

    return CPI_TRUE;
}


// This is where the parameter changes are handled

static void
EnableParmChanged( const char *name )
{
    CPI_Int32   units;

    // this is the situation when the node is initialized
    if ( name == NULL )
    {
        cpiEnableParameter( "pixels", CPI_TRUE );
        cpiEnableParameter( "fractions", CPI_FALSE );
        return;
    }

    // if the relevant param is changed, pick up its new value
    // and enable/disable based on that
    if ( strcmp( name, "units" ) == 0 )
    {
        cpiGetInteger( &units, "units", 1.0F );
        if ( units == 0 )
        {
            cpiEnableParameter( "pixels", CPI_TRUE );
            cpiEnableParameter( "fractions", CPI_FALSE );
        }
        else
        {
            cpiEnableParameter( "pixels", CPI_FALSE );
            cpiEnableParameter( "fractions", CPI_TRUE );
        }
    }
}

Setting Widget Values from Software

A common thing to want to do in the previous situation is to keep the two parameters, pixels and fractions, in sync with each other, so that if the user updates one, the other updates to show him/her the value in that form.

In our example, we have the (totally arbitrary) values of 10 pixels and 0.1 fractional values as initial parameter values. So if the user changes the X value of the pixels parameter to 15, we might expect the fractional X value to change to 0.15 to stay in sync.

To do this, we use the one of the following routines

cpiSetFloat( fltval, parmname, evaltime, isUserSet )
cpiSetInteger( intval, parmname, evaltime, isUserSet )
cpiSetString( stringval, parmname, evaltime, isUserSet )
This makes our example a little more complicated, as we now have to calculate the value for the other, unchanged parameter, and then set it. For this example, we have invented a completely arbitrary relationship between the pixel and fraction parameters; this relationship says that 1.0 == 100 pixels, so that in general
fractions = pixels / 100
and
pixels = fractions * 100
The new function looks like this:
static void
EnableParmChanged( const char *name )
{
    CPI_Int32   units, pval;
    CPI_Float32 fval;

    // this is the situation when the node is initialized
    if ( name == NULL )
    {
        cpiEnableParameter( "pixels", CPI_TRUE );
        cpiEnableParameter( "fractions", CPI_FALSE );
        return;
    }

    // if the relevant param is changed, pick up its new value
    // and enable/disable based on that
    if ( strcmp( name, "units" ) == 0 )
    {
        cpiGetInteger( &units, "units", 1.0F );
        if ( units == 0 )
        {
            cpiEnableParameter( "pixels", CPI_TRUE );
            cpiEnableParameter( "fractions", CPI_FALSE );
        }
        else
        {
            cpiEnableParameter( "pixels", CPI_FALSE );
            cpiEnableParameter( "fractions", CPI_TRUE );
        }
    }
    // if a value is changed, update the other set to stay
    // in sync
    // These are based on the arbitrary relationship that
    // fractions = pixels / 100
    //       and so
    // pixels = fractions * 100
    else if ( strcmp( name, "pixels.x" ) == 0 )
    {
        cpiGetInteger( &pval, "pixels.x", 0.0F );
        fval = pval / 100.0F;
        cpiSetFloat( fval, "fractions.x", 0.0F, CPI_FALSE );
    }
    else if ( strcmp( name, "pixels.y" ) == 0 )
    {
        cpiGetInteger( &pval, "pixels.y", 0.0F );
        fval = pval / 100.0F;
        cpiSetFloat( fval, "fractions.y", 0.0F, CPI_FALSE );
    }
    else if ( strcmp( name, "fractions.x" ) == 0 )
    {
        cpiGetFloat( &fval, "fractions.x", 0.0F );
        pval = fval * 100.0F;
        cpiSetInteger( pval, "pixels.x", 0.0F, CPI_FALSE );
    }
    else if ( strcmp( name, "fractions.y" ) == 0 )
    {
        cpiGetFloat( &fval, "fractions.y", 0.0F );
        pval = fval * 100.0F;
        cpiSetInteger( pval, "pixels.y", 0.0F, CPI_FALSE );
    }
}
Things to Note: First, the Get and Set functions all take a time argument, which would normally be related to the current frame. But when a user changes a parameter, there is no time - the animation is not running. That is why the current time is not passed into the ParmChanged function. So the time parameter is set to 0.0 in both the Set and Get case.

Second, the last parameter of the Set functions is set to CPI_FALSE in each case above. This is the UserSet parameter - this tells RAYZ whether the parameter was changed by the user directly, or by the plugin indirectly. This matters when the node is saved to a .rayz file. Parameters which are not user set are not saved in the file, since they can be recalculated when the file is brought back into RAYZ. But if a parameter was directly changed by a user, then that change does need to be saved to the .rayz file, so that it can override the value it might otherwise have.

In general, you will want to set this flag to CPI_FALSE if you are setting values from within your plugin - the flag is automatically set to CPI_TRUE by RAYZ when a user adjusts a value directly. It is possible to discover the state of the UserSet flag for any given parameter by the use of the call

CPI_Bool        cpiIsParmUserSet( const char *parmname )

Common Parameters

The routines have common parameters. The 'name' parameter is the internal identifier for that parameter. This must be unique to the node. This string does not appear anywhere to the user.
The 'label' parameter is used to describe the parameter to the user. This does not have to be unique, although a set of parameters with the same name would certainly be an interesting user interface!
For example, say you define the following:
    cpiAddInteger( "cent", "Center", 1, 0, 150, CPI_PARM_DEFAULT, NULL, NULL );
Then when you want to get the value of the "Center" field, you will do this
    cpiGetInteger( "cent", result->time, &center )
This tells the software to get the value associated with the field you called "cent" at the current time. We will cover this in depth in a moment.

The default parameter is just that, a value to initialize the gadget with when it first appears.
min_val and max_val are values to put at the ends of the slider associated with the gadget. If the appropriate flag is set (see below), then these values are enforced - if not, they just appear as a guide to the user of a reasonable range of values.
flags may be any of the following:
CPI_PARM_DEFAULT Use default settings for the parameter
CPI_PARM_ANIMATABLE The parameter can be animated. This causes the little animate icon to appear at the right edge of the parameter. If this is not set, the value of the parameter will be used across all frames.
CPI_PARM_CLAMPMIN Enforce the minimum value as a hard limit
CPI_PARM_CLAMPMAX Enforce the maximum value as a hard limit
CPI_PARM_CLAMPMINMAX Enforce both min and max values as hard limits
CPI_PARM_FULL_SIZE_X Display the value as a pixel version of a float value, scaled by the X size of the input image's full size. Float parameters only. The purpose is to maintain float values internally, for resolution independence, while displaying more friendly pixel values to the user.
CPI_PARM_FULL_SIZE_Y Display the value as a pixel version of a float value, scaled by the Y size of the input image's full size. Float parameters only. The purpose is to maintain float values internally, for resolution independence, while displaying more friendly pixel values to the user.
CPI_PARM_FULL_SIZE_XY Display the value as a pixel version of a float value, scaled by the X and Y sizes of the input image's full size. This is used for FloatPair widgets only. The purpose is to maintain float values internally, for resolution independence, while displaying more friendly pixel values to the user.
CPI_PARM_INVISIBLE Don't show the parameter to the user, but save and load it in .rayz files. This is used to create state variables that you want to keep around from session to session.
CPI_PARM_NOMODIFIES Makes it so that the parameter cannot be modified by the user
CPI_PARM_PIXEL_VALUE Displays a float parameter as an integer value based on the pixel depth of the input image, for example 0 to 255 for 8-bit images. The purpose is to maintain float values internally, for image independence, while displaying more friendly color values to the user.
CPI_PARM_READONLY This is similar to CPI_PARM_NOMODIFIES, except that it applies only to strings.
CPI_PARM_EXPANDBYDEFAULT this only applies to groups, var lists, etc, and tells RAYZ to draw them as expanded when the node is first created.
Flags may be combined with a logical 'OR' (|) to set more than one flag. For example,

    cpiAddInteger( "rad", "Radius", 1, 0, 150, 
                CPI_PARM_ANIMATABLE | CPI_PARM_CLAMPMIN, NULL, NULL );
sets up a radius parameter which can be animated, and which cannot go below zero.

Error Handling

RAYZ allows the programmer to define a routine which is called to check for errors. This routine can generate errors and/or warnings based on the type of input, the values of parameters, or whatever. When an error is detected, there is a standard call for presenting that error to the user, which is
void cpiError(const char *format, ...)
For example:
    cpiError("Must have alpha channel to proceed");
    cpiError("Can't open file %s", filename);
There is also a call to generate a warning, which is
void cpiError(const char *format, ...)
The difference between the two is the cpiError() will stop execution, while cpiWarning() will color the node yellow, but continue execution. The warning message appears in the status line for the user.

These calls are generally placed in the GetErrors() function, which is called before processing begins. For example

static CPI_Bool
RegionsGetErrors( CPI_Float32 time )
{
    CPI_ImageContext    inputA;
    CPI_ImageContext    inputB;
    CPI_Bool            retval = CPI_TRUE;

    if ( cpiGetInputContext( &inputA, 0, time ) &&
         cpiGetInputContext( &inputB, 1, time ) )
    {
        if ( inputA.channels != inputB.channels )
        {
            cpiError( "Inputs A and B must have the same number of channels"
);            retval = CPI_FALSE;
        }
        else if ( inputA.bitsPerPel != inputB.bitsPerPel )
        {
            cpiError( "Inputs A and B must have the same bit depth" );
            retval = CPI_FALSE;
        }
    }
    else
    {
        cpiError( "Couldn't get input image contexts" );
        retval = CPI_FALSE;
    }

    return retval;
}
It is also possible to generate an error in other places, notably the Exec function, but that error will not be seen by the user unless Exec returns NULL. For example
static CPI_ImageOp
RegionsExec( CPI_Float32    myTime,
             CPI_Uint8      quality,
             CPI_Uint32     nodeOutput,
             CPI_Uint32     viewerOutput,
             CPI_Float32    scaleX,
             CPI_Float32    scaleY )

{
    CPI_Bool         fillWithColor;
    CPI_ImageOp      myInputs[2];
    CPI_Metadata     myParms    = cpiCreateMetadata();
    CPI_ImageOp      retval     = NULL;

    myInputs[0] = cpiGetInputOp( 0, myTime, quality, scaleX, scaleY );
    myInputs[1] = cpiGetInputOp( 1, myTime, quality, scaleX, scaleY );

    if ( cpiGetInteger( &fillWithColor, "fill", myTime ) )
    {
        cpiSetMetaBool( myParms, "color", 0 != fillWithColor );

        if ( NULL != myInputs[0] && NULL != myInputs[1] )
            retval = cpiAddImageOp( "regions", myParms, myInputs, 2 );

        cpiDeleteMetadata( myParms );
    }
    else
        cpiError( "Couldn't get toggle value" );

    return retval;
}

[Previous Page] [Next Page]
[Table of Contents] [Index]

Copyright © 2002 Silicon Grail Inc.
736 Seward Street, Hollywood, CA 90038