GeUserArea Manual

About

GeUserArea is the base class for all gadgets that can be displayed in a GeDialog. A new gadget is created by implementing a subclass of GeUserArea. Such a gadget manages user interaction and draws the gadget interface in the GUI.

Note
GeUserArea based classes can only be used with a GeDialog or GeDialog based classes. To create a custom GUI element that can be used in the Attribute Manager one must implement a CustomGuiData / iCustomGui based plugin.

Allocation

An instance of a GeUserArea based class is typically stored as a member of the parent GeDialog. It is added to the dialog layout using:

See GeDialog Manual.

// This example adds the given user area "userarea" to the GeDialog layout.
private:
MyUserArea _geUserArea;
public:
Bool CreateLayout()
{
SetTitle("GeUserArea Dialog"_s);
C4DGadget* const userAreaGadget = this->AddUserArea(100, BFH_LEFT, 400, 100);
if (userAreaGadget)
this->AttachUserArea(_geUserArea, userAreaGadget);

The parent dialog is accessible with:

GeUserArea Based Classes

A custom user area is created by implementing a subclass of GeUserArea. This subclass can implement different virtual functions that define the behaviour of the gadget.

The user area is initiated with:

// This example initializes the GeUserArea and sets the timer.
Bool Init()
{
this->SetTimer(500); // value in ms
return true;
}

The size of the user area is handled with:

// This example defines the minimum size of this GeUserArea.
Bool GetMinSize(Int32& w, Int32& h)
{
w = 400;
h = 100;
return true;
}
// This example stores the width and height after the GeUserArea was resized.
void Sized(Int32 w, Int32 h)
{
_width = w;
_height = h;
}

Different messages are sent to a user area:

// This example catches a message to set the cursor when the mouse is over the user area.
Int32 Message(const BaseContainer& msg, BaseContainer& result)
{
// messages are identified by the BaseContainer ID
switch (msg.GetId())
{
{
result.SetString(RESULT_BUBBLEHELP, "This is an example GeUserArea"_s);
return true;
}
}
return GeUserArea::Message(msg, result);
}

The primary purpose of a user area is to draw the actual user interface:

// This example draws a white rectangle in the given region.
void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg)
{
OffScreenOn();
SetClippingRegion(x1, y1, x2, y2);
DrawSetPen(Vector(1, 1, 1));
DrawRectangle(x1, y1, x2, y2);
}
Note
To optimize the drawing call skip the redraw if only the focus has changed, see BFM_DRAW_REASON in Draw.

A user area can catch user interaction events:

// This example checks if the input device is the mouse.
// If so, the mouse coordinates are transferred into local coordinates.
Bool InputEvent(const BaseContainer& msg)
{
// check the input device
switch (msg.GetInt32(BFM_INPUT_DEVICE))
{
// some mouse interaction
{
// get the cursor position
Global2Local(&mx, &my);
ApplicationOutput("Mouse Event at " + String::IntToString(mx) + " - " + String::IntToString(my));
return true;
}
}
return false;
}

See also GeDialog Gadget Interaction.

Read-Only Properties

These properties can be read from a user area:

// This example sends the BFM_ACTION message from a GeUserArea to the parent GUI element.
// This message can be caught in GeDialog::Message() or GeDialog::Command().
action.SetInt32(BFM_ACTION_ID, GetId());
SendParentMessage(action);
// This example changes the color of the user area depending on the focus.
void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg)
{
OffScreenOn();
SetClippingRegion(x1, y1, x2, y2);
// check if the user area has the focus
if (this->HasFocus())
DrawSetPen(Vector(0.0, 0.0, 1.0));
else
DrawSetPen(Vector(0.0, 0.2, 0.8));
DrawRectangle(x1, y1, x2, y2);
}

Timer

A user area can use an internal timer that calls GeUserArea::Timer() periodically.

// This example initializes the GeUserArea and sets the timer.
Bool Init()
{
this->SetTimer(500); // value in ms
return true;
}
// This example implements GeUserArea::Timer().
// The function is called in the interval defined with GeUserArea::SetTimer().
void Timer(const BaseContainer& msg)
{
ApplicationOutput("Timer tick");
}

User Interaction

A user area can handle user interaction events by implementing GeUserArea::InputEvent(). The following functions are used to get more detailed information on the current event:

A special operation is a mouse drag event inside the user area. Such a mouse drag is initiated in reaction to certain user interaction using these functions:

// This example handles a mouse drag event of the left mouse button.
const Int32 device = msg.GetInt32(BFM_INPUT_DEVICE);
const Int32 channel = msg.GetInt32(BFM_INPUT_CHANNEL);
// check if this is a mouse event triggered by the left mouse button
if (device == BFM_INPUT_MOUSE && channel == BFM_INPUT_MOUSELEFT)
{
BaseContainer channels;
Int32 startX = msg.GetInt32(BFM_INPUT_X);
Int32 startY = msg.GetInt32(BFM_INPUT_Y);
Float deltaX = 0.0f;
Float deltaY = 0.0f;
Global2Local(&startX, &startY);
// start mouse drag
MouseDragStart(BFM_INPUT_MOUSELEFT, (Float)startX, (Float)startY, MOUSEDRAGFLAGS::DONTHIDEMOUSE);
// handle mouse drag
while (MouseDrag(&deltaX, &deltaY, &channels) == MOUSEDRAGRESULT::CONTINUE)
{
// get state of the left mouse button
break;
// check if the mouse button is not pressed
if (state.GetInt32(BFM_INPUT_VALUE) == 0)
break;
// print something if the mouse has moved
if (deltaX != 0.0 || deltaY != 0.0)
ApplicationOutput("Mouse Drag");
}
MouseDragEnd();
return true;

Drag and Drop

The user can drag and drop various elements onto a user area. The user area is informed about this event through messages sent to GeUserArea::Message(). These functions are used to react to those messages:

See also Drag and Drop.

// This example handles a drag and drop event in GeUserArea::Message().
{
// check drag area
if (!CheckDropArea(msg, true, true))
break;
Int32 type;
void* data = nullptr;
// get the drag data
if (!GetDragObject(msg, &type, &data))
return false;
// check if an C4DAtom was dragged
if (type != DRAGTYPE_ATOMARRAY)
return false;
// get content
const AtomArray* const elements = static_cast<AtomArray*>(data);
// check content
if (!elements->GetIndex(0))
return false;
// check if drag has finished
{
// loop through all elements of the AtomArray
for (Int32 i = 0; i < elements->GetCount(); ++i)
{
const C4DAtom* const atom = elements->GetIndex(i);
// check if the given C4DAtom is a BaseList2D element
if (atom && atom->IsInstanceOf(Tbaselist2d))
{
const BaseList2D* const baselistObject = static_cast<const BaseObject*>(atom);
ApplicationOutput("Dragged object: " + baselistObject->GetName());
}
}
}
else
{
// if in drag, show different mouse cursor
return SetDragDestination(MOUSE_POINT_HAND);
}
return true;
}

It is also possible to start a drag and drop operation from a user area. This is typically done in reaction to some user interaction.

// This example starts a drag and drop operation.
Bool InputEvent(const BaseContainer& msg)
{
const Int32 device = msg.GetInt32(BFM_INPUT_DEVICE);
const Int32 channel = msg.GetInt32(BFM_INPUT_CHANNEL);
if (device == BFM_INPUT_MOUSE)
{
if (channel == BFM_INPUT_MOUSELEFT)
{
// drag a color
Vector red(1.0, 0.0, 0.0);
// start a drag & drop event of dragging a color value
if (HandleMouseDrag(msg, DRAGTYPE_RGB, &red, 0))
return true;
}
}
return false;
}

Parent Message

A user area can send messages to its parent GeDialog. This is typically done to inform the dialog that some user interaction occurred and that the managed values changed.

See GUI and Interaction Messages Manual.

// This example sends the BFM_ACTION message from a GeUserArea to the parent GUI element.
// This message can be caught in GeDialog::Message() or GeDialog::Command().
action.SetInt32(BFM_ACTION_ID, GetId());
SendParentMessage(action);

Drawing

Drawing Basics

The central task of a user area is to draw something to Cinema 4D's user interface. The drawing operations have to happen inside the GeUserArea::DrawMsg() function.

The drawing operation can be triggered with:

Drawing Operations

The following functions can be used to draw inside the canvas provided by the user area. For more advanced drawing operations one can use a GeClipMap and GeUserArea::DrawBitmap().

The color settings are handled with:

// This example draws a white background and a black text.
// draw a white background
DrawSetPen(Vector(1.0f));
DrawRectangle(x1, y1, x2, y2);
// draw some text
DrawSetTextCol(Vector(0.0f), Vector(1.0f));
DrawText("Hello World!"_s, 0, 0, DRAWTEXT_HALIGN_LEFT);

These functions draw primitive shapes:

A BaseBitmap can both be drawn to the canvas and filled with the current pen:

See also BaseBitmap Manual.

// This example fills a GeClipMap and draws the resulting BaseBitmap in the user area.
if (clipMap)
{
// get monospaced font
clipMap->Init(200, 50, 32);
clipMap->BeginDraw();
// fill background
clipMap->SetColor(255, 255, 255, 255);
clipMap->FillRect(0, 0, 200, 50);
// draw text
clipMap->SetFont(&font, 20.0);
clipMap->SetColor(0, 0, 0, 255);
clipMap->TextAt(0, clipMap->GetTextHeight(), "This is some text.");
clipMap->EndDraw();
// get bitmap
BaseBitmap* const bitmap = clipMap->GetBitmap();
if (bitmap)
{
// draw bitmap
const Int32 width = bitmap->GetBw();
const Int32 height = bitmap->GetBh();
DrawBitmap(bitmap, 0, 0, width, height, 0, 0, width, height, BMP_NORMAL);
}
}

The border style of the user area is defined with:

// This example draws a round border for the GeUserArea.
DrawBorder(BORDER_ROUND, 0, 0, GetWidth(), GetHeight());

Text is drawn with these functions:

// This example draws some text and a line under that text.
const String text("Hello World!");
const Int32 left = 100;
// draw text
DrawSetTextCol(Vector(0.0f), Vector(1.0f));
DrawSetFont(FONT_MONOSPACED);
DrawText(text, left, 0, DRAWTEXT_HALIGN_LEFT);
// draw a line
const Int32 baseline = DrawGetFontBaseLine() + 1;
const Int32 length = DrawGetTextWidth(text);
DrawSetPen(Vector(0.5f));
DrawLine(left, baseline, left + length, baseline);

A user area can support a dynamic fading effect that is typically used to change the background color of the gadget after the cursor moved over it:

See also Fading.

// This example shows a GeUserArea that is "fading".
// When the cursor is over the user area the fading is activated.
// Cinema 4D will then send the message BFM_FADE to the user area so
// it can interpolate a color and use this color to redraw itself.
class FadingUserArea : public GeUserArea
{
public:
FadingUserArea() { };
~FadingUserArea() { };
{
// messages are identified by the BaseContainer ID
switch (msg.GetId())
{
{
// cursor is over the user area
// start fading
return true;
}
case BFM_FADE:
{
// receive fade message and update the COLOR_BG used in DrawMsg()
const Float percentage = msg.GetFloat(BFM_FADE);
// redraw using the updated COLOR_BG
Redraw();
return true;
}
}
return GeUserArea::Message(msg, result);
}
void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg)
{
SetClippingRegion(x1, y1, x2, y2);
// draw background with COLOR_BG
DrawRectangle(x1, y1, x2, y2);
// draw text
DrawText("User Area"_s, 0, 0, DRAWTEXT_HALIGN_LEFT);
}
};

It is also possible to be informed when the cursor leaves the user area:

// This example user area registers a callback function with RemoveLastCursorInfo().
// This function is called when the cursor leaves the user area. It is used to send the
// message BFM_CURSORINFO_REMOVE back to the user area to inform it about the event.
// declarations
static void RemoveCursorInfo();
class RemoveCursorUserArea;
static RemoveCursorUserArea* g_userArea = nullptr; // static variable storing pointer to a user area
// user area will display a different color depending if the cursor is over the area or not
class RemoveCursorUserArea : public GeUserArea
{
public:
RemoveCursorUserArea()
{
if (g_userArea == this)
g_userArea = nullptr;
};
~RemoveCursorUserArea() { };
{
// messages are identified by the BaseContainer ID
switch (msg.GetId())
{
{
// register RemoveCursorInfo() callback
g_userArea = this;
RemoveLastCursorInfo(RemoveCursorInfo);
_cursorInside = true;
Redraw();
return true;
}
{
// message "BFM_CURSORINFO_REMOVE" sent by RemoveCursorInfo()
_cursorInside = false;
Redraw();
break;
}
}
return GeUserArea::Message(msg, result);
}
void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg)
{
SetClippingRegion(x1, y1, x2, y2);
// gadget color is controlled by the cursor position
if (_cursorInside)
DrawSetPen(Vector(1.0, 0.0, 0.0));
else
DrawSetPen(Vector(0.0, 1.0, 0.0));
DrawRectangle(x1, y1, x2, y2);
}
private:
Bool _cursorInside = false; // set to true if the cursor is over the user area
};
// function will be called by the user area when the cursor left its area
static void RemoveCursorInfo()
{
if (!g_userArea)
return;
// send message to the user area
g_userArea->Message(BaseContainer(BFM_CURSORINFO_REMOVE), bc);
}

Clipping

Clipping is used to limit drawing operations to certain areas:

// This example draws a white rectangle in the given region.
void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg)
{
OffScreenOn();
SetClippingRegion(x1, y1, x2, y2);
DrawSetPen(Vector(1, 1, 1));
DrawRectangle(x1, y1, x2, y2);
}

Miscellaneous

Miscellaneous functions are:

Coordinate Transformation

These functions are used to transform coordinates into different spaces:

// This example creates a pop up menu when the right mouse button was pressed.
Bool InputEvent(const BaseContainer& msg)
{
const Int32 device = msg.GetInt32(BFM_INPUT_DEVICE);
const Int32 channel = msg.GetInt32(BFM_INPUT_CHANNEL);
// check if this is a mouse event triggered by the left mouse button
if (device == BFM_INPUT_MOUSE && channel == BFM_INPUT_MOUSERIGHT)
{
// get the cursor position
Global2Local(&x, &y);
Local2Screen(&x, &y);
// define pop up menu
bc.InsData(5159, "CMD"); // cube
bc.InsData(0, String());
bc.InsData(5160, "CMD"); // sphere
// show pop up menu
ShowPopupMenu(GetDialog()->Get(), x, y, bc);
return true;
}
return false;
}

Further Reading