mirror of
https://github.com/alfishe/amiga-bootcamp.git
synced 2026-06-13 00:26:28 +00:00
- 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
292 lines
7.8 KiB
Markdown
292 lines
7.8 KiB
Markdown
[← 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)
|