HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
Native UI Programming

Topics

UI Overview

The user interface in Houdini can be categorized into several sub-categories:

  • Feel (UI_Feel) - describes the behavior of a single gadget or container.
  • Look (UI_Look) - describes the visual representation of a gadget or container.
  • Interface (AP_Interface) - a large collection of gadgets (often dialogs or panes)
  • Value (UI_Value) - a variable, which communicates values to other gadgets & Houdini.

A gadget is any recognizable UI control, like a button, slider or field. A basic gadget is made up of a feel paired with a look. Normally, you do not need to provide the look for a feel, as most gadgets do this for you.

A compound gadget made up of a bunch of basic gadgets (like a SLIDERFIELD, which contains a label, input field, and a slider). For the most part, you don't need to be concerned with what is a basic or compound gadget - a compound gadget should behave exactly as you'd expect any gadget to.

A UI_Feel is a class that handles all the value changes, mouse and keyboard events on the gadget. A feel may have child feels (a container). It also contains the size and location of the gadget.

A UI_Look is a very basic class which is only used for rendering. A look has no reference to its feel, and the feel-look relationship is very much master-slave. If the look has a variable rendering (like text, or on/off), it is the feel that should set it on the look.

Almost all active gadgets require a Value in order to be effective, otherwise manipulating the gadget will have no effect. By adding an interest in a value, you can be kept informed of any changes to that value, and act accordingly. The feel portion of the gadget contains the value.

Finally, an interface contains a layout of gadgets and a collection of values, which provide the user with the ability to perform a task. An interface is normally designed in the .ui scripting language, which makes it much easier to describe the interface than using raw C++ (though you can do that, as well). In the .ui script, you can name any of your values and gadgets, and reference them in C++ code.

To create a dialog, you will derive a class from AP_Interface and use AP_Interface::parse() to parse the .ui file which will create your interface for you. From this class, you can set or reference any of the gadgets or values. This class will often contain a list of callback methods to respond to value changes.

Because you will often create new interfaces, much less often create new feels and looks, and probably never create new value types, we'll start off describing how to create an interface.

Creating a User Interface

An interface can be any collection of gadgets, from a commonly used grouping of gadgets, to a full application screen. Regardless of scope, the way you create interfaces is always the same.

The easiest way to design an interface is to mock it up in the .ui language. You can use uiparse to view the interface; just place an 'OPEN windowname;' command at the end (where windowname is your window's gadget name – more on that in a minute).

When you are finished, you can use the builduicode application to generate a C++ class (.C & .h) for your .ui file. All named values will be included as member variables, and have their own callback, and all named gadgets will have member feel ptrs to them. The C++ member names are derived from the UI names (ie, ok.val = myOkVal, filename.browse.val = myFilenameBrowseVal; window.gad = myWindowGad).

Design any interface more complex than a handful of feels in the .ui syntax, not in C++ code. Otherwise you will end up with an almost incomprehensible C++ file, where it is next to impossible to determine what the gadgets are and how they're laid out.

Gluing the UI File to Houdini

If you use builduicode, you won't have to worry much about gluing; it'll be done for you (though likely you will want to thin the callbacks out a bit, to what you actually need). However, it is good to know how to do this, as builduicode can't really be used once a dialog already exists (it would just overwrite your file).

builduicode has created an initApplication() method in your class, which is normally called in the constructor, or right before the dialog is launched for the first time. The important line in the method is this:

void
OPUI_ClassName::initApplication(UI_Manager *, int, const char **)
{
if( !parseUI( "OPUI_ClassName.ui") )
UIgetManager()->errorMessage(UI_FATAL);
}

This is what actually loads the UI file and builds the interface. OPUI_ClassName.ui is the name of the UI file, which should be in either the DSO or UI path. If it fails, often this is a bad sign and Houdini should exit, as you've likely made a programming error (or, the user has messed up their environment).

Gluing Values

But how do I get at the interface? The class generated is derived from AP_Interface (in SI), which provides hooks into the interface which you can use to access, or assign, gadgets & variables.

Say I have a filename field in a dialog which I want to query the value from. In the UI file, the gadget looks something like this:

file.gad = FILENAME_FIELD "Source File" VALUE(filename.val) HSTRETCH;

The easiest way to access the value is to create a member value in your class: UI_Value myFilenameValue;. Then, before parse is called, assign it a name which matches the one in the UI file:

void
OPUI_ClassName::initApplication(UI_Manager *, int, const char **)
{
setValueSymbol("filename.val", &myFilenameValue);
if( !parseUI( USERNAME VERSION "OPUI_ClassName.ui") )
UIgetManager()->errorMessage(UI_FATAL);
}

Now, you've substituted your value for the one in the UI file - instead of generating a new value for filename.val, it'll use yours.

Responding to Value Changes

If you're interested in knowing when the filename changes, you'll have to add an interest in the value. This is usually done after you've parsed the ui file, but you can do it any time you like:

void
OPUI_ClassName::initApplication(UI_Manager *, int, const char **)
{
setValueSymbol("filename.val", &myFilenameValue);
if( !parseUI( USERNAME VERSION "OPUI_ClassName.ui") )
UIgetManager()->errorMessage(UI_FATAL);
myFilenameValue.addInterest(this,(UI_EventMethod)&OPUI_ClassName::filenameChanged);
}
void
OPUI_ClassName::filenameChanged(UI_Event *e)
{
{
UT_String filename;
myFilenameValue.getValue(filename);
// Handle the filename change.
}
}

Values can change for other reasons, and it's good practice to only respond to reasons that you care about. A complete list of reasons can be found in UI_Value.h (UI_Reason enum).

Setting a UI Value

Finally, you may want to set the value manually. To do this, you need to both set the value, and then tell it that it's been changed:

myFilenameValue.setValue("readme.txt");
myFilenameValue.changed(this);

If you forget the changed() call, the gadget will not update. The this pointer tells the changed() method that you're the one that changed it, so your callback (filenameChanged) will not be called. If you want your callback to be called, pass NULL instead.

Getting at a Gadget

In rarer cases, you may need to get access to an actual gadget. This can be done in two ways, both after the parseUI() call:

UI_Feel *filenamefeel = getFeelSymbol("file.gad");
// or
SI_FilenameField *filenamefield = SIgetSymbol<SI_FilenameField>(this, "file.gad");

The first line grabs a pointer to its generic base class, a UI_Feel. This is useful if whatever you need it for is available in UI_Feel.h. The second uses a template function to cast the gadget to its derived class. In both cases, if the gadget is not found, NULL is returned. In the second case, if the gadget is not of that type, it will return NULL.

Inserting a Gadget

Sometimes you might need to insert a gadget into a .ui file, either because it's type isn't known at parse time (you may use the same interface for various purposes), or because the gadget type does not exist in the UI grammar (perhaps it is too specific to be worth adding to the grammar). Either way, you can insert any UI class derived from UI_Feel into a UI interface:

void
OPUI_ClassName::initApplication(UI_Manager *, int, const char **)
{
setObjectSymbol("custom.gad", &myCustomGadget);
if( !parseUI( USERNAME VERSION "OPUI_ClassName.ui") )
UIgetManager()->errorMessage(UI_FATAL);
}

Just like setValueSymbol(), setObjectSymbol() inserts your own gadget into the ui file instead of creating it. All you need to do is specify where in the UI layout it goes:

win.gad = BEST_RGB_WINDOW "Load File..." DIALOG
{
LAYOUT(vertical) STRETCH
file.gad = FILENAME_FIELD "Source File" VALUE(filename.val) HSTRETCH;
custom.gad HSTRETCH VALUE(custom.val);
ACTION_BUTTON "Load" VALUE(load.val);
ACTION_BUTTON "Close" VALUE(close.val);
}

Note that you can specify attributes on it, just as you can any fully-specified gadget.

Example

An example of how to launch a simple UI dialog from a custom hscript command can be found in ui/cmd_ui.C . It brings up a dialog with some UI gadgets and some simple handlers in response to changes in the dialog.