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);
Represents a gadget in a dialog.
Definition: c4d_gui.h:108
maxon::Bool Bool
Definition: ge_sys_math.h:51
@ BFH_LEFT
Aligned to the left. 1<<3.
Definition: gui.h:312

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;
}
maxon::Int32 Int32
Definition: ge_sys_math.h:56

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.
{
// messages are identified by the BaseContainer ID
switch (msg.GetId())
{
{
result.SetString(RESULT_BUBBLEHELP, "This is an example GeUserArea"_s);
return true;
}
}
}
Definition: c4d_basecontainer.h:48
virtual Int32 Message(const BaseContainer &msg, BaseContainer &result)
PyObject PyObject * result
Definition: abstract.h:43
@ BFM_GETCURSORINFO
Definition: gui.h:560
@ RESULT_BUBBLEHELP
String Bubble help text.
Definition: gui.h:564
@ RESULT_CURSOR
Int32 Mouse cursor: MOUSE
Definition: gui.h:563
static const Int32 MOUSE_POINT_HAND
Point hand cursor.
Definition: ge_prepass.h:2708
const char const char * msg
Definition: object.h:438

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);
}
maxon::Vec3< maxon::Float64, 1 > Vector
Definition: ge_math.h:141
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
Int32 mx = msg.GetInt32(BFM_INPUT_X);
Int32 my = msg.GetInt32(BFM_INPUT_Y);
Global2Local(&mx, &my);
ApplicationOutput("Mouse Event at " + String::IntToString(mx) + " - " + String::IntToString(my));
return true;
}
}
return false;
}
static String IntToString(Int32 v)
Definition: c4d_string.h:497
@ BFM_INPUT_MOUSE
Mouse.
Definition: gui.h:710
@ BFM_INPUT_Y
Float Y value.
Definition: gui.h:727
@ BFM_INPUT_DEVICE
Int32 Device:
Definition: gui.h:709
@ BFM_INPUT_X
Float X value.
Definition: gui.h:726
#define ApplicationOutput(formatString,...)
Definition: debugdiagnostics.h:204

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);
@ BFM_ACTION_ID
Int32 ID of the dialog element that triggered the action.
Definition: gui.h:745
@ BFM_ACTION
One of the child elements made any action:
Definition: gui.h:744
// 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;
}
Bool GetInputState(Int32 askdevice, Int32 askchannel, BaseContainer &res)
PyArena _PyASTOptimizeState * state
Definition: compile.h:99
maxon::Float Float
Definition: ge_sys_math.h:62
@ BFM_INPUT_MOUSELEFT
Left mouse button.
Definition: gui.h:715
@ BFM_INPUT_VALUE
Int32 Value of the input channel (true/false or a Int32 value, e.g. for scroll wheel data).
Definition: gui.h:724
@ BFM_INPUT_CHANNEL
Int32 Contains the key or mouse button. See also KEY.
Definition: gui.h:714
@ DONTHIDEMOUSE
Show mouse pointer during drag.
@ CONTINUE
Drag still in progress.
Definition: grammar.h:37

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;
void* data = nullptr;
// get the drag data
if (!GetDragObject(msg, &type, &data))
return false;
// check if an C4DAtom was dragged
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
if (msg.GetInt32(BFM_DRAG_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;
}
Py_ssize_t i
Definition: abstract.h:645
Definition: c4d_baselist.h:1795
Int32 GetCount() const
Definition: c4d_baselist.h:1826
C4DAtom * GetIndex(Int32 idx) const
Definition: c4d_baselist.h:1841
Definition: c4d_baselist.h:2376
String GetName() const
Definition: c4d_baselist.h:2543
Definition: c4d_baseobject.h:248
Definition: c4d_baselist.h:1520
#define atom
Definition: graminit.h:72
@ BFM_DRAG_FINISHED
Bool Drag finished.
Definition: gui.h:795
@ DRAGTYPE_ATOMARRAY
AtomArray.
Definition: gui.h:784
@ BFM_DRAGRECEIVE
Drag receive. (See DragAndDrop.)
Definition: gui.h:772
#define Tbaselist2d
2D list.
Definition: ge_prepass.h:996
PyObject ** type
Definition: pycore_pyerrors.h:34

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;
}
@ DRAGTYPE_RGB
RGB color.
Definition: gui.h:786

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 and opacity 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);
#define DRAWTEXT_HALIGN_LEFT
Align to the left.
Definition: c4d_gui.h:189

Different styles used with various drawing functions are (LINESTYLE):

These functions draw primitive shapes:

// This example draws shapes in a GeUserArea.
// draw base
{
const maxon::Vector2d center(300, 60);
const maxon::Vector2d radius(40, 40);
DrawSetPen(Vector(1, 1, 0));
DrawEllipseFill(center, radius);
DrawSetPen(Vector(0, 0, 0));
DrawEllipseLine(center, radius);
}
// draw eyes
DrawSetPen(Vector(0, 0, 0));
{
const maxon::Vector2d center(285, 50);
const maxon::Vector2d radius(3, 3);
DrawEllipseFill(center, radius);
}
{
const maxon::Vector2d center(315, 50);
const maxon::Vector2d radius(3, 3);
DrawEllipseFill(center, radius);
}
// draw mouth
{
p.c1 = Vector2d(280, 90);
p.c2 = Vector2d(320, 90);
p.p = Vector2d(320, 70);
DrawBezierLine(Vector2d(280, 70), maxon::ToSingletonBlock(p), false, 1.0, LINESTYLE::NORMAL);
}
unsigned char * p
Definition: floatobject.h:87
maxon::Vec2< maxon::Float64, 1 > Vector2d
Definition: ge_math.h:143
#define MAXON_SCOPE
Definition: apibase.h:2886
Block< T > ToSingletonBlock(T &value)
Definition: block.h:984
Definition: ge_prepass.h:41

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);
}
}
Definition: ge_autoptr.h:37
Definition: c4d_basebitmap.h:435
Int32 GetBw() const
Definition: c4d_basebitmap.h:574
Int32 GetBh() const
Definition: c4d_basebitmap.h:580
static Bool GetDefaultFont(GeFontDefaultType type, BaseContainer *font_description)
@ BMP_NORMAL
Standard scaling by the operating system. Fast but low quality when using uneven scaling factors.
Definition: gui.h:171
@ GE_FONT_DEFAULT_MONOSPACED
The Cinema&#160;4D monospaced font.
Definition: lib_clipmap.h:122
unsigned long Py_ssize_t width
Definition: pycore_traceback.h:88

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());
@ BORDER_ROUND
Border with round corners.
Definition: gui.h:271

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);
Definition: c4d_string.h:41
@ FONT_MONOSPACED
Monospaced font.
Definition: gui.h:26
PyWideStringList Py_ssize_t length
Definition: initconfig.h:448
PyObject * text
Definition: pycore_traceback.h:70

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;
}
}
}
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);
}
};
@ COLOR_BG_HIGHLIGHT
Definition: c4d_colors.h:329
@ COLOR_TRANS
Definition: c4d_colors.h:14
@ COLOR_CONSOLE_TEXT
Definition: c4d_colors.h:94
@ COLOR_BG
Definition: c4d_colors.h:16
Definition: c4d_gui.h:172
void DrawSetFont(Int32 fontid)
void AdjustColor(Int32 colorid, Int32 highlightid, Float percent)
virtual void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer &msg)
void ActivateFading(Int32 milliseconds)
void Redraw(Bool threaded=false)
void DrawSetPen(const Vector &color)
void DrawText(const maxon::String &txt, Int32 x, Int32 y, Int32 flags=(0|(0<< 4)))
void DrawSetTextCol(Int32 fg, Int32 bg)
void DrawRectangle(Int32 x1, Int32 y1, Int32 x2, Int32 y2)
void SetClippingRegion(Int32 x, Int32 y, Int32 w, Int32 h)
Bool OffScreenOn()
@ BFM_FADE
Definition: gui.h:1007

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;
}
}
}
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 == nullptr)
return;
// send message to the user area
g_userArea->Message(BaseContainer(BFM_CURSORINFO_REMOVE), bc);
}
Bool RemoveLastCursorInfo(LASTCURSORINFOFUNC func)
@ BFM_CURSORINFO_REMOVE
Sent when mouse cursor has left a user area.
Definition: gui.h:576

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
Int32 x = msg.GetInt32(BFM_INPUT_X);
Int32 y = msg.GetInt32(BFM_INPUT_Y);
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;
}
Int32 ShowPopupMenu(CDialog *cd, Int32 screenx, Int32 screeny, const BaseContainer &bc, Int32 flags=POPUP_RIGHT|POPUP_EXECUTECOMMANDS|POPUP_ALLOW_FILTERING, Int32 *res_mainid=nullptr)
GeData * InsData(Int32 id, const GeData &n)
Definition: c4d_basecontainer.h:258
PyObject * x
Definition: bytesobject.h:38
@ BFM_INPUT_MOUSERIGHT
Right mouse button.
Definition: gui.h:716
const Class< R > & Get(const Id &cls)
Definition: objectbase.h:2073

Further Reading