amiga-bootcamp/09_intuition/frameworks/mui/09-custom-classes.md
Ilia Sharin 94a3ad1614 doc: MUI framework documentation — whitepaper overview, SDK-derived architecture, layout mockups
- 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
2026-04-23 16:46:58 -04:00

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 requirements
  • MUIM_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 message
  • DoSuperMethod() forwards with a variable argument list
  • DoSuperNew() is a helper for forwarding OM_NEW with tag lists
  • Always call the superclass first in AskMinMax and Draw unless you intend to completely replace its behavior

Previous: Menus Next: Events and Notifications