- README.md: comprehensive whitepaper-style overview with historical context, paradigm shift analysis, BOOPSI comparison, version history (Stefan Stuntz as sole author 1.0-3.8), licensing model breakdown, parallel evolution timeline (NeXTSTEP/Qt/MUI convergence), community-sourced developer values - 02-architecture.md: complete rewrite from MUI 3.8 SDK sources — object lifecycle state machine, three-level resource binding, method dispatch chain, notification system with sequence diagrams, layout engine internals (3-pass constraint system), input handling, dynamic object linking, rendering model, tag ID namespace - 05-layout-system.md: Mermaid visual mockups for VGroup, HGroup, nested groups, column grids, scrollgroups, file requester real-world example, layout algorithm and resize sequence diagrams - frameworks/README.md: framework index with comparison table - All content in American English
7.8 KiB
← Home · Intuition · Frameworks
Custom Classes
Why Create Custom Classes?
Custom classes let you encapsulate reusable behavior and rendering. Instead of writing the same notification setup and hook logic repeatedly, you create a class that handles everything internally. Examples include:
- A custom chart or graph widget
- A specialized list with drag-and-drop rules
- A rendering area with custom graphics
- A compound widget that bundles multiple controls
Custom classes are standard BOOPSI classes with MUI-specific methods.
Creating a Custom Class
Step 1: Define Instance Data
Instance data is a structure that holds per-object state. MUI allocates it for each object automatically.
struct MyData
{
LONG dummy;
/* add your own fields here */
};
Step 2: Implement Methods
At minimum, most visual custom classes implement two methods:
MUIM_AskMinMax- Report size requirementsMUIM_Draw- Render the object
AskMinMax
Called before layout. You must add your size requirements to what the superclass already calculated.
SAVEDS ULONG mAskMinMax(struct IClass *cl, Object *obj, struct MUIP_AskMinMax *msg)
{
/* Let superclass fill in base values (frame, spacing, etc.) */
DoSuperMethodA(cl, obj, msg);
/* Add our own requirements */
msg->MinMaxInfo->MinWidth += 100;
msg->MinMaxInfo->DefWidth += 120;
msg->MinMaxInfo->MaxWidth += 500;
msg->MinMaxInfo->MinHeight += 40;
msg->MinMaxInfo->DefHeight += 90;
msg->MinMaxInfo->MaxHeight += 300;
return 0;
}
Important: Use += to add to the superclass values, not = to replace them.
Draw
Called whenever MUI needs you to render.
SAVEDS ULONG mDraw(struct IClass *cl, Object *obj, struct MUIP_Draw *msg)
{
int i;
/* Let superclass draw frame, background, etc. */
DoSuperMethodA(cl, obj, msg);
/* If MADF_DRAWOBJECT isn't set, don't draw content */
if (!(msg->flags & MADF_DRAWOBJECT))
return 0;
/* Render within the object's rectangle */
SetAPen(_rp(obj), _dri(obj)->dri_Pens[TEXTPEN]);
for (i = _mleft(obj); i <= _mright(obj); i += 5)
{
Move(_rp(obj), _mleft(obj), _mbottom(obj));
Draw(_rp(obj), i, _mtop(obj));
Move(_rp(obj), _mright(obj), _mbottom(obj));
Draw(_rp(obj), i, _mtop(obj));
}
return 0;
}
Key macros for rendering:
| Macro | Meaning |
|---|---|
_rp(obj) |
RastPort for drawing |
_dri(obj) |
DrawInfo (pens, fonts) |
_mleft(obj) |
Left edge of render area |
_mtop(obj) |
Top edge of render area |
_mright(obj) |
Right edge of render area |
_mbottom(obj) |
Bottom edge of render area |
_mwidth(obj) |
Width of render area |
_mheight(obj) |
Height of render area |
Step 3: Create the Dispatcher
The dispatcher is a switch statement that routes methods to your handlers.
SAVEDS ASM ULONG MyDispatcher(REG(a0) struct IClass *cl,
REG(a2) Object *obj,
REG(a1) Msg msg)
{
switch (msg->MethodID)
{
case MUIM_AskMinMax: return mAskMinMax(cl, obj, (APTR)msg);
case MUIM_Draw: return mDraw(cl, obj, (APTR)msg);
}
return DoSuperMethodA(cl, obj, msg);
}
Unknown methods are passed to the superclass via DoSuperMethodA().
Step 4: Register the Class
struct MUI_CustomClass *mcc;
mcc = MUI_CreateCustomClass(NULL, /* library base (usually NULL) */
MUIC_Area, /* superclass */
NULL, /* private data (usually NULL) */
sizeof(struct MyData),
MyDispatcher);
if (!mcc)
fail(NULL, "Could not create custom class.");
MUI_CreateCustomClass returns a struct MUI_CustomClass *, not a raw struct IClass *. The mcc_Class member contains the actual class pointer for NewObject().
Step 5: Use the Class
MyObj = NewObject(mcc->mcc_Class, NULL,
TextFrame,
MUIA_Background, MUII_BACKGROUND,
TAG_DONE);
Step 6: Cleanup
When your application exits, delete the custom class:
MUI_DeleteCustomClass(mcc);
Do this after disposing all objects of that class.
Complete Minimal Custom Class Example
#include "demo.h"
struct MyData
{
LONG dummy;
};
SAVEDS ULONG mAskMinMax(struct IClass *cl, Object *obj, struct MUIP_AskMinMax *msg)
{
DoSuperMethodA(cl, obj, msg);
msg->MinMaxInfo->MinWidth += 100;
msg->MinMaxInfo->DefWidth += 120;
msg->MinMaxInfo->MaxWidth += 500;
msg->MinMaxInfo->MinHeight += 40;
msg->MinMaxInfo->DefHeight += 90;
msg->MinMaxInfo->MaxHeight += 300;
return 0;
}
SAVEDS ULONG mDraw(struct IClass *cl, Object *obj, struct MUIP_Draw *msg)
{
DoSuperMethodA(cl, obj, msg);
if (!(msg->flags & MADF_DRAWOBJECT))
return 0;
SetAPen(_rp(obj), _dri(obj)->dri_Pens[TEXTPEN]);
RectFill(_rp(obj), _mleft(obj), _mtop(obj), _mright(obj), _mbottom(obj));
return 0;
}
SAVEDS ASM ULONG MyDispatcher(REG(a0) struct IClass *cl,
REG(a2) Object *obj,
REG(a1) Msg msg)
{
switch (msg->MethodID)
{
case MUIM_AskMinMax: return mAskMinMax(cl, obj, (APTR)msg);
case MUIM_Draw: return mDraw(cl, obj, (APTR)msg);
}
return DoSuperMethodA(cl, obj, msg);
}
int main(int argc, char *argv[])
{
APTR app, window, MyObj;
struct MUI_CustomClass *mcc;
init();
mcc = MUI_CreateCustomClass(NULL, MUIC_Area, NULL,
sizeof(struct MyData), MyDispatcher);
if (!mcc)
fail(NULL, "Could not create custom class.");
app = ApplicationObject,
MUIA_Application_Title, "CustomClassDemo",
...
SubWindow, window = WindowObject,
...
WindowContents, VGroup,
Child, MyObj = NewObject(mcc->mcc_Class, NULL,
TextFrame,
MUIA_Background, MUII_BACKGROUND,
TAG_DONE),
End,
End,
End;
if (!app)
fail(app, "Failed to create Application.");
DoMethod(window, MUIM_Notify, MUIA_Window_CloseRequest, TRUE,
app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);
set(window, MUIA_Window_Open, TRUE);
{
ULONG sigs = 0;
while (DoMethod(app, MUIM_Application_NewInput, &sigs)
!= MUIV_Application_ReturnID_Quit)
{
if (sigs)
{
sigs = Wait(sigs | SIGBREAKF_CTRL_C);
if (sigs & SIGBREAKF_CTRL_C) break;
}
}
}
set(window, MUIA_Window_Open, FALSE);
MUI_DisposeObject(app);
MUI_DeleteCustomClass(mcc);
fail(NULL, NULL);
}
Advanced Custom Class Methods
Beyond AskMinMax and Draw, custom classes often implement:
| Method | Purpose |
|---|---|
OM_NEW |
Initialize instance data when object is created |
OM_DISPOSE |
Clean up instance data before deletion |
OM_SET |
Handle attribute changes |
OM_GET |
Handle attribute queries |
MUIM_Setup |
Prepare for rendering (allocate resources) |
MUIM_Cleanup |
Release rendering resources |
MUIM_HandleEvent |
Handle raw input events |
MUIM_DragQuery |
Accept or reject drag operations |
MUIM_DragDrop |
Handle dropped objects |
Inheritance Notes
DoSuperMethodA()forwards a method to the superclass with the original messageDoSuperMethod()forwards with a variable argument listDoSuperNew()is a helper for forwardingOM_NEWwith tag lists- Always call the superclass first in
AskMinMaxandDrawunless you intend to completely replace its behavior
Previous: Menus Next: Events and Notifications