Clarisse 5.0 SP8 SDK  5.0.5.8.0
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
Adding a custom UI for a module

Table of Contents

This topic explains how to implement a custom UI that replaces in the Attribute Editor one or several attributes controls by a customized widget.
You need to be familiar with Clarisse modules and commands as well as widgets.

To see a custom UI in action, have a look to the SceneObjectScatterer. Its Geometries attribute is a custom UI that actually replaces 4 hidden attributes: geometry, collision_object, probability, id.

Create the custom UI widget

In our example, the widget computes uv values from mouse position to feed red and green channels of a rgba attribute.

In order to be used as a custom UI, the widget has to inherit GuiCustomUIWidget.

On mouse down and mouse drag, the widget will change the attribute's current value, on mouse up it will set the value for good by adding the corresponding command to the history stack.
Using ESC while dragging will revert current changes and cancel mouse operation.

#include <gui_app.h>
#include <app_builtin_commands.h>
class Gui2dGradient : public GuiCustomUIWidget
{
public:
Gui2dGradient(GuiWidget &parent, const int &x, const int &y, const int &w, const int &h)
: GuiCustomUIWidget(parent, x, y, w, h) {}
void set_attribute(OfAttr* at) { m_attribute = at; }
void draw(GuiDc &dc) override
{
GuiWidgetDc& wdc = dynamic_cast<GuiWidgetDc&>(dc);
// drawing background
unsigned char bg[3], c[3];
get_color(GuiColorScheme::GROUP_ENABLED, GuiColorScheme::ROLE_GL_BASE, bg[0], bg[1], bg[2]);
wdc.draw_rectf(get_x(), get_y(), get_width(), get_height(), bg[0], bg[1], bg[2]);
get_color(GuiColorScheme::GROUP_ENABLED, GuiColorScheme::ROLE_DARK_FRAME, c[0], c[1], c[2]);
wdc.draw_rect(get_x(), get_y(), get_width(), get_height(), c[0], c[1], c[2]);
}
int process_event(const CoreString &event_id) override
{
if (event_id == EVT_ID_MOUSE_ENTER) {
set_mouse_cursor(Gui::MOUSE_CURSOR_CROSS);
return 1;
} else if (event_id == EVT_ID_MOUSE_LEAVE) {
set_mouse_cursor(Gui::MOUSE_CURSOR_DEFAULT);
return 1;
} else if (event_id == EVT_ID_MOUSE_DOWN) {
m_is_cancelled = false;
if (m_attribute != nullptr) {
GMathVec2d uv = get_mouse_color();
m_attribute->change_vec4d(GMathVec4d(uv[0], uv[1], 0.0, 1.0));
}
return 1;
} else if (event_id == EVT_ID_MOUSE_DRAG) {
if (m_attribute != nullptr && !m_is_cancelled) {
GMathVec2d uv = get_mouse_color();
m_attribute->change_vec4d(GMathVec4d(uv[0], uv[1], 0.0, 1.0));
}
return 1;
} else if (event_id == EVT_ID_KEY_UP) {
if (Gui::get_last_key_pressed() == Gui::KEY_ID_ESCAPE) {
if (m_attribute != nullptr) {
GMathVec4d previous_color = m_attribute->get_vec4d(OfAttr::VALUE_PAGE_PREVIOUS);
m_attribute->set_vec4d(previous_color);
}
m_is_cancelled = true;
}
return 1;
} else if (event_id == EVT_ID_MOUSE_UP) {
if (m_attribute != nullptr && !m_is_cancelled) {
GMathVec2d uv = get_mouse_color();
rgba[0] << uv[0];
rgba[1] << uv[1];
rgba[2] = "0";
rgba[3] = "1";
get_application().get_builtin_commands().set_value(m_attribute->get_full_name(), rgba);
}
return 1;
}
}
void clear_data() override {} // mandatory for the GuiCustomUIWidget interface
private:
GMathVec2d get_mouse_color() const
{
int xr, yr;
double u = gmath_min(1.0, gmath_max(0.0, (double)xr/get_width()));
double v = 1. - gmath_min(1.0, gmath_max(0.0, (double)yr/get_height()));
return GMathVec2d(u, v);
}
bool m_is_cancelled;
OfAttr* m_attribute;
};

Declare the custom UI

In the CID file of your module, just add the following statement

custom_ui "my_controller" {}

This line declares a placeholder attribute named "my_controller".
As any attribute declaration, its line number defines where it will be displayed in Attribute Editor.

Any attribute it manages may be hidden with the following statement added to its definition.

rgba "my_slave_color" {
hidden yes
}

Bind the custom UI

In the main.cpp of your module, you have to bind manually the custom UI attribute to the actual attribute(s).
This is achieved by implementing a declare_custom_ui callback.

IX_BEGIN_DECLARE_MODULE_CALLBACKS(MyTexture, ModuleTextureCallbacks)
static void init_class(OfClass& cls);
static void module_constructor(OfObject& object, OfModule *module);
static void module_destructor(OfObject& object, OfModule *module);
static void *create_module_data(const OfObject& object);
static bool destroy_module_data(const OfObject& object, void *data);
//...
static GuiWidget *declare_custom_ui(const CoreVector<OfAttrHandle>& attrs, GuiWidget& wparent, void* info);
IX_END_DECLARE_MODULE_CALLBACKS(MyTexture)
IX_BEGIN_EXTERN_C
DSO_EXPORT void
on_register_module(OfApp& app, CoreVector<OfClass *>& new_classes)
{
OfClass *new_class = IX_DECLARE_MODULE_CLASS(MyTexture);
new_classes.add(new_class);
IX_MODULE_CLBK *module_callbacks;
IX_CREATE_MODULE_CLBK(new_class, module_callbacks)
IX_MODULE_CLBK::init_class(*new_class);
module_callbacks->cb_module_constructor = IX_MODULE_CLBK::module_constructor;
module_callbacks->cb_module_destructor = IX_MODULE_CLBK::module_destructor;
//...
module_callbacks->cb_declare_custom_ui = IX_MODULE_CLBK::declare_custom_ui;
}
IX_END_EXTERN_C
GuiWidget* IX_MODULE_CLBK::declare_custom_ui(const CoreVector<OfAttr *>& attrs, GuiWidget& wparent, void* info)
{
// several custom UIs may be used for the same module. Identifier is the attribute name
if (attrs.get_count() > 0 && attrs.back()->get_name() == "my_controller") {
OfObject* obj = attrs.back()->get_parent_object(); // the object to edit
OfAttr* color_at = obj->get_attribute("my_slave_color"); // the attribute to edit
Gui2dGradient* custom_widget = new Gui2dGradient(parent_widget, wparent.get_x(), wparent.get_y(), wparent.get_width(), wparent.get_height());
custom_widget->set_attribute(color_at);
return custom_widget;
}
return 0;
}

The attribute vector is filled by the caller (the Attribute Editor) depending on its current selection.
When a single MyTexture object is selected in Clarisse, it will contain a single attribute: its "my_controller" attribute. If several MyTexture objects are selected, the vector will contain all "my_controller" attributes, with respect to selection order (last selected last).

From each custom UI attribute, we retrieve the selected object, then the slave attribute(s) that the widget is intended to modify.