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>
{
public:
Gui2dGradient(
GuiWidget &parent,
const int &x,
const int &y,
const int &w,
const int &h)
void set_attribute(
OfAttr* at) { m_attribute = at; }
{
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]);
}
{
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) {
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;
}
}
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;
};
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.
static
void init_class(
OfClass& cls);
static
void *create_module_data(const
OfObject&
object);
static
bool destroy_module_data(const
OfObject&
object,
void *data);
IX_END_DECLARE_MODULE_CALLBACKS(MyTexture)
IX_BEGIN_EXTERN_C
DSO_EXPORT void
{
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
{
if (attrs.get_count() > 0 && attrs.back()->get_name() == "my_controller") {
OfObject* obj = attrs.back()->get_parent_object();
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.