- 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
18 KiB
← Home · Intuition · Frameworks · MUI
Architecture
This document describes MUI's internal architecture derived from the official MUI 3.8 SDK (MUIdev.guide, autodocs, and libraries/mui.h).
BOOPSI Foundation
MUI is built on BOOPSI, AmigaOS's Basic Object-Oriented Programming System for Intuition. BOOPSI provides:
- Classes — type definitions containing a dispatcher function and default attributes
- Objects — instances of classes created with
NewObject() - Methods — operations invoked on objects via
DoMethod() - Attributes — properties set with
SetAttrs()and read withGetAttr()
MUI extends BOOPSI with its own class hierarchy, layout engine, and notification system while remaining fully compatible with the underlying mechanism. Every MUI object is a valid BOOPSI object; every MUI class is a BOOPSI IClass.
How MUI Extends BOOPSI
| BOOPSI Primitive | MUI Extension |
|---|---|
NewObject(class, ...) |
MUI_NewObject(MUIC_name, ...) — auto-loads disk-resident classes |
DoMethod(obj, methodID, ...) |
Same — MUI adds 60+ new method IDs (MUIM_*) |
SetAttrs(obj, tag, val, ...) |
set(obj, attr, val) macro — same mechanism, convenience wrapper |
GetAttr(attr, obj, &store) |
get(obj, attr, &store) macro |
DisposeObject(obj) |
MUI_DisposeObject(obj) — cascading disposal of entire object tree |
Class Hierarchy
The canonical class tree from libraries/mui.h:
rootclass (BOOPSI's base class)
|
+-- Notify (implements notification mechanism)
| |
| +-- Family (handles multiple children)
| | |
| | +-- Menustrip (describes a complete menu strip)
| | +-- Menu (describes a single menu)
| | \-- Menuitem (describes a single menu item)
| |
| +-- Application (main class for all applications)
| |
| +-- Window (main class for all windows)
| | \-- Aboutmui (About window of MUI preferences)
| |
| +-- Area (base class for all GUI elements)
| |
| +-- Rectangle (spacing object)
| +-- Balance (balancing separator bar)
| +-- Image (image display)
| +-- Bitmap (draws bitmaps)
| | \-- Bodychunk (makes bitmap from ILBM body chunk)
| +-- Text (text display)
| +-- Gadget (base class for intuition gadgets)
| | |
| | +-- String (string gadget)
| | +-- Boopsi (interface to BOOPSI gadgets)
| | \-- Prop (proportional gadget)
| |
| +-- Gauge (fuel gauge)
| +-- Scale (percentage scale)
| +-- Colorfield (field with changeable color)
| |
| +-- List (line-oriented list)
| | |
| | +-- Floattext (special list with floating text)
| | +-- Volumelist (special list with volumes)
| | +-- Scrmodelist (special list with screen modes)
| | \-- Dirlist (special list with files)
| |
| +-- Numeric (base class for slider gadgets)
| | |
| | +-- Knob (turning knob)
| | +-- Levelmeter (level display)
| | +-- Numericbutton (space saving popup slider)
| | \-- Slider (traditional slider)
| |
| +-- Group (groups other GUI elements)
| |
| +-- Register (handles page groups with titles)
| +-- Virtgroup (handles virtual groups)
| +-- Scrollgroup (virtual groups with scrollbars)
| +-- Scrollbar (traditional scrollbar)
| +-- Listview (listview)
| +-- Radio (radio button)
| +-- Cycle (cycle gadget)
| +-- Coloradjust (several gadgets to adjust a color)
| +-- Palette (complete palette gadget)
| |
| +-- Popstring (base class for popup objects)
| |
| +-- Popobject (popup anything in a separate window)
| | |
| | +-- Poplist (popup a simple listview)
| | \-- Popscreen (popup a list of public screens)
| |
| \-- Popasl (popup an asl requester)
|
+-- Semaphore (semaphore equipped objects)
|
+-- Applist (private)
+-- Dataspace (handles general purpose data spaces)
\-- Configdata (private)
Class Role Summary
| Branch | Root | Responsibility |
|---|---|---|
| Notify | rootclass → Notify |
Attribute observation, inter-object messaging |
| Application | Notify → Application |
Program root, event loop, ARexx, Commodities |
| Window | Notify → Window |
Intuition window lifecycle, IDCMP multiplexing |
| Area | Notify → Area |
Visual base — sizing, rendering, input, frames |
| Group | Area → Group |
Layout container — child arrangement |
| Semaphore | Notify → Semaphore |
Thread-safe data containers |
Application Object Tree
A MUI application forms a tree at runtime. Only three class types may have children:
graph TB
APP["Application<br/><i>zero or more Window children</i>"]
W1["Window 1"]
W2["Window 2"]
ROOT1["Group<br/><i>root object</i>"]
ROOT2["Group<br/><i>root object</i>"]
C1["String"]
C2["Group"]
C3["Text"]
C4["List"]
C5["Cycle"]
APP --> W1 & W2
W1 --> ROOT1
W2 --> ROOT2
ROOT1 --> C1 & C2 & C3
C2 --> C4 & C5
style APP fill:#fff3e0,stroke:#ff9800,color:#333
style W1 fill:#e8f4fd,stroke:#2196f3,color:#333
style W2 fill:#e8f4fd,stroke:#2196f3,color:#333
style ROOT1 fill:#e8f5e9,stroke:#4caf50,color:#333
style ROOT2 fill:#e8f5e9,stroke:#4caf50,color:#333
| Parent | Allowed Children | Count |
|---|---|---|
| Application | Window objects only | 0..N |
| Window | Any Area subclass (usually Group) | Exactly 1 |
| Group | Any Area subclass (including nested Groups) | 1..N |
Error handling is cascading: if any child creation fails (returns NULL), the parent automatically disposes all previously created siblings and returns NULL itself. A successful ApplicationObject guarantees the entire tree was created. A single MUI_DisposeObject(app) destroys everything.
Object Lifecycle
Every MUI object passes through a strict sequence of constructor/destructor pairs. The SDK documents this as the fundamental contract:
OM_NEW — parse initial taglist, allocate instance data
{
MUIM_Setup — display environment known (screen, fonts, DrawInfo)
MUIM_AskMinMax — report min/max/default dimensions
[ window opens ]
{
MUIM_Show — object added to window (AddGadget for Intuition types)
{
MUIM_Draw — render content (may be called multiple times)
}
MUIM_Hide — object removed from window (RemGadget)
}
[ window closes ]
MUIM_Cleanup — free display-dependent resources
}
OM_DISPOSE — free all resources, destroy object
Lifecycle as State Machine
stateDiagram-v2
[*] --> Created: OM_NEW
Created --> SetUp: MUIM_Setup
SetUp --> Sized: MUIM_AskMinMax
Sized --> Visible: MUIM_Show
Visible --> Visible: MUIM_Draw (repeated)
Visible --> Sized: MUIM_Hide (resize)
Sized --> Visible: MUIM_Show (re-layout)
Sized --> Created: MUIM_Cleanup
Created --> [*]: OM_DISPOSE
Three Levels of Resource Binding
| Level | Constructor | Destructor | Available Information |
|---|---|---|---|
| Object | OM_NEW |
OM_DISPOSE |
Nothing — only taglist parsing |
| Display | MUIM_Setup |
MUIM_Cleanup |
Screen, fonts, DrawInfo, pens |
| Window | MUIM_Show |
MUIM_Hide |
Intuition Window, RastPort, position/size |
Each level may repeat independently. You may receive multiple Setup/Cleanup cycles (e.g., screen mode change) and multiple Show/Hide cycles within a single Setup (e.g., window resize).
Method Dispatch Model
Every MUI class has a dispatcher function — a single entry point that receives all method calls. This is the standard BOOPSI dispatch pattern:
ULONG MyDispatcher(struct IClass *cl, Object *obj, Msg msg)
{
switch (msg->MethodID)
{
case OM_NEW : return mNew(cl, obj, (APTR)msg);
case OM_DISPOSE : return mDispose(cl, obj, (APTR)msg);
case OM_SET : return mSet(cl, obj, (APTR)msg);
case OM_GET : return mGet(cl, obj, (APTR)msg);
case MUIM_Setup : return mSetup(cl, obj, (APTR)msg);
case MUIM_Cleanup : return mCleanup(cl, obj, (APTR)msg);
case MUIM_AskMinMax: return mAskMinMax(cl, obj, (APTR)msg);
case MUIM_Draw : return mDraw(cl, obj, (APTR)msg);
case MUIM_HandleInput: return mHandleInput(cl, obj, (APTR)msg);
}
/* Unknown methods → pass to superclass */
return DoSuperMethodA(cl, obj, msg);
}
Dispatch Chain
graph LR
CALLER["DoMethod(obj, MUIM_Draw)"] --> DISP_MY["MyClass<br/>Dispatcher"]
DISP_MY -->|"handled"| RESULT["Return value"]
DISP_MY -->|"DoSuperMethodA()"| DISP_AREA["Area<br/>Dispatcher"]
DISP_AREA -->|"DoSuperMethodA()"| DISP_NOTIFY["Notify<br/>Dispatcher"]
DISP_NOTIFY -->|"DoSuperMethodA()"| DISP_ROOT["rootclass<br/>Dispatcher"]
style DISP_MY fill:#e8f5e9,stroke:#4caf50,color:#333
style DISP_AREA fill:#e8f4fd,stroke:#2196f3,color:#333
style DISP_NOTIFY fill:#e8f4fd,stroke:#2196f3,color:#333
Key rules:
- Always call
DoSuperMethodA()for unrecognized methods — MUI sends undocumented internal methods - Never render in
OM_SET— callMUI_Redraw(obj, flag)instead; MUI will invokeMUIM_Draw - The dispatcher receives
struct IClass *cl(the class pointer),Object *obj, andMsg msgin registersa0,a2,a1respectively
Notification System
The central mechanism for controlling a MUI application. Notification creates declarative reactive bindings between objects.
How It Works
Every MUI object has attributes that reflect its current state. Attributes change either programmatically (SetAttrs()) or through user interaction. Notification watches for these changes and automatically invokes methods on target objects.
sequenceDiagram
participant User
participant Scrollbar as Scrollbar Object
participant MUI as MUI Notify Engine
participant List as List Object
User->>Scrollbar: Drags knob
Scrollbar->>Scrollbar: MUIA_Prop_First changes
Scrollbar->>MUI: Attribute change detected
MUI->>MUI: Match against registered notifications
MUI->>List: MUIM_Set(MUIA_List_TopPixel, new_value)
List->>List: Scrolls to new position
Registration
DoMethod(source, MUIM_Notify,
watched_attribute, /* which attribute to observe */
trigger_value, /* MUIV_EveryTime or specific value */
target_object, /* destination object */
param_count, /* number of following parameters */
target_method, ...); /* method + arguments to invoke */
Notification Suppression
When setting attributes programmatically, you may want to suppress notifications to avoid loops:
/* Normal set — triggers any registered notifications */
set(slider, MUIA_Slider_Level, 50);
/* Suppressed set — no notifications fire */
SetAttrs(slider, MUIA_NoNotify, TRUE,
MUIA_Slider_Level, 50, TAG_DONE);
MUIA_NoNotify is a one-shot attribute — it only applies to the current SetAttrs() call.
Deregistration
/* Remove all notifications on a specific attribute */
DoMethod(source, MUIM_KillNotify, MUIA_Slider_Level);
/* Remove notification targeting a specific object */
DoMethod(source, MUIM_KillNotifyObj, MUIA_Slider_Level, target);
Layout Engine Internals
MUI's layout engine uses a three-pass constraint system that runs before window open and on every resize.
Pass 1: AskMinMax (Bottom-Up)
Every object reports its minimum, maximum, and default dimensions. Leaf objects (Text, String, etc.) report fixed values based on content and font. Group objects aggregate their children:
Horizontal Group:
- Min width = sum of all children's min widths
- Max width = sum of all children's max widths
- Min height = largest child min height
- Max height = smallest child max height
Vertical Group:
- Min height = sum of all children's min heights
- Max height = sum of all children's max heights
- Min width = largest child min width
- Max width = smallest child max width
Pass 2: Size Distribution (Top-Down)
Starting from the window's current size, the root group distributes available space among children. Extra space (beyond minimum) is distributed proportionally according to weight attributes (MUIA_Weight, default 100).
Pass 3: Placement
Each object receives its final rectangle (left, top, width, height) and records it in instance data accessible via macros:
| Macro | Returns |
|---|---|
_mleft(obj) |
Left edge of content area (inside frame) |
_mtop(obj) |
Top edge of content area |
_mright(obj) |
Right edge of content area |
_mbottom(obj) |
Bottom edge of content area |
_mwidth(obj) |
Content area width |
_mheight(obj) |
Content area height |
_rp(obj) |
RastPort pointer for rendering |
Layout Flow
graph TB
subgraph "Pass 1: AskMinMax (bottom-up)"
LEAF1["Text: min=40, max=200"]
LEAF2["Button: min=60, max=120"]
GROUP1["HGroup: min=100, max=320"]
end
subgraph "Pass 2: Distribute (top-down)"
WINDOW["Window size: 250px wide"]
CALC["HGroup: 250px available<br/>Text weight=100, Button weight=100<br/>→ Text=125px, Button=125px"]
end
subgraph "Pass 3: Place"
PLACE1["Text: left=5, width=125"]
PLACE2["Button: left=130, width=125"]
end
LEAF1 & LEAF2 --> GROUP1
GROUP1 --> WINDOW --> CALC
CALC --> PLACE1 & PLACE2
Input Handling
MUI multiplexes Intuition IDCMP messages through the Application object's event loop.
Event Flow
graph TB
IDCMP["Intuition IDCMP Port<br/>(mouse, keyboard)"]
APP["MUIM_Application_NewInput"]
DISPATCH["MUI dispatches to<br/>active/relevant objects"]
HANDLE["MUIM_HandleInput<br/>(on each object)"]
NOTIFY["Notifications fire<br/>(attribute changes)"]
RETURN["Return ID to app loop"]
IDCMP --> APP --> DISPATCH --> HANDLE --> NOTIFY --> RETURN
style APP fill:#fff3e0,stroke:#ff9800,color:#333
style HANDLE fill:#e8f5e9,stroke:#4caf50,color:#333
IDCMP Subscription
Custom classes request specific IDCMP events dynamically:
/* During MUIM_HandleInput — request mouse move tracking */
MUI_RequestIDCMP(obj, IDCMP_MOUSEMOVE);
/* Stop tracking when done */
MUI_RejectIDCMP(obj, IDCMP_MOUSEMOVE);
MUI translates raw IDCMP events into higher-level MUIKEY_* constants for keyboard navigation:
| MUIKEY Constant | Meaning |
|---|---|
MUIKEY_PRESS |
Confirm / activate |
MUIKEY_TOGGLE |
Toggle selection |
MUIKEY_UP/DOWN/LEFT/RIGHT |
Directional navigation |
MUIKEY_PAGEUP/PAGEDOWN |
Page scrolling |
MUIKEY_TOP/BOTTOM |
Jump to extremes |
MUIKEY_WORDLEFT/WORDRIGHT |
Word-level navigation |
Dynamic Object Linking
MUI supports late binding — adding and removing children after the initial tree is created.
/* Add a window to a running application */
DoMethod(app, OM_ADDMEMBER, new_window);
set(new_window, MUIA_Window_Open, TRUE);
/* Remove and dispose when done */
set(new_window, MUIA_Window_Open, FALSE);
DoMethod(app, OM_REMMEMBER, new_window);
MUI_DisposeObject(new_window); /* manual disposal required */
For groups, the window must be closed before modifying children:
set(window, MUIA_Window_Open, FALSE);
DoMethod(group, MUIM_Group_InitChange); /* begin modification */
DoMethod(group, OM_ADDMEMBER, new_child);
DoMethod(group, MUIM_Group_ExitChange); /* end — triggers re-layout */
set(window, MUIA_Window_Open, TRUE);
Rendering Model
MUI uses retained-mode rendering — the framework maintains the object tree and handles all damage repair.
Draw Flags
| Flag | Meaning |
|---|---|
MADF_DRAWOBJECT |
Complete redraw of the object |
MADF_DRAWUPDATE |
Incremental update (optimization) |
Custom classes call MUI_Redraw(obj, flag) to request a redraw. MUI calls MUIM_Draw with appropriate flags. Inside MUIM_Draw, the class can check which type of redraw is needed:
case MUIM_Draw:
{
struct MUIP_Draw *msg = (struct MUIP_Draw *)msg;
DoSuperMethodA(cl, obj, msg); /* let Area draw background + frame */
if (msg->flags & MADF_DRAWOBJECT)
{
/* Full redraw — render everything */
}
else if (msg->flags & MADF_DRAWUPDATE)
{
/* Partial update — only changed elements */
}
return 0;
}
Rendering Rules
- Never draw outside
MUIM_Draw— not inOM_SET, not inMUIM_HandleInput - Always call
DoSuperMethodA()first — Area class draws background and frame - Only draw within
_mleft()/_mtop()/_mright()/_mbottom()— the content rectangle inside the frame - Use
_rp(obj)to get the RastPort — never cache it across Show/Hide cycles
Tag ID Namespace
MUI uses the AmigaOS TagItem system for all attributes and methods. The namespace is partitioned:
| Range | Owner |
|---|---|
0x80420000 – 0x8042FFFF |
MUI built-in classes (reserved) |
TAG_USER | (serial << 16) | offset |
Registered third-party classes |
Each registered MUI developer received a unique serial number. All their class attributes and methods are prefixed with TAG_USER | (serial << 16) to avoid tag collisions across the MCC ecosystem.
Key Architectural Files
| File | Purpose |
|---|---|
libraries/mui.h |
Main header — class names, method IDs, attribute tags, macros, class tree |
clib/muimaster_protos.h |
C prototypes for muimaster.library functions |
proto/muimaster.h |
Compiler-specific prototype wrappers |
pragma/muimaster_lib.h |
SAS/C library call pragmas |
muimaster.library |
Runtime — object creation, layout, event handling, preferences |
MUI_<Class>.doc |
Per-class autodoc reference (66 files) |
MUIdev.guide |
Official developer guide — architecture, custom classes, style guide |
Previous: Introduction Next: Getting Started