amiga-bootcamp/09_intuition/frameworks/mui/02-architecture.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

529 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) · [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 with `GetAttr()`
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:
```mermaid
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
```mermaid
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:
```c
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
```mermaid
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`** — call `MUI_Redraw(obj, flag)` instead; MUI will invoke `MUIM_Draw`
- The dispatcher receives `struct IClass *cl` (the class pointer), `Object *obj`, and `Msg msg` in registers `a0`, `a2`, `a1` respectively
---
## 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.
```mermaid
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
```c
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:
```c
/* 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
```c
/* 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
```mermaid
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
```mermaid
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:
```c
/* 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.
```c
/* 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:
```c
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:
```c
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
1. **Never draw outside `MUIM_Draw`** — not in `OM_SET`, not in `MUIM_HandleInput`
2. **Always call `DoSuperMethodA()` first** — Area class draws background and frame
3. **Only draw within `_mleft()` / `_mtop()` / `_mright()` / `_mbottom()`** — the content rectangle inside the frame
4. **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](01-introduction.md)
Next: [Getting Started](03-getting-started.md)