amiga-bootcamp/09_intuition/frameworks/mui/09-custom-classes.md

293 lines
7.8 KiB
Markdown
Raw Permalink Normal View History

[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md)
# 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.
```c
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.
```c
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.
```c
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.
```c
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
```c
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
```c
MyObj = NewObject(mcc->mcc_Class, NULL,
TextFrame,
MUIA_Background, MUII_BACKGROUND,
TAG_DONE);
```
### Step 6: Cleanup
When your application exits, delete the custom class:
```c
MUI_DeleteCustomClass(mcc);
```
Do this after disposing all objects of that class.
## Complete Minimal Custom Class Example
```c
#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](08-menus.md)
Next: [Events and Notifications](10-events-and-notifications.md)