amiga-bootcamp/09_intuition/frameworks/mui/README.md
2026-04-26 14:46:18 -04:00

46 KiB
Raw Permalink Blame History

← Home · Intuition · GUI Frameworks

MUI — Magic User Interface

Overview

In 1985, AmigaOS shipped with Intuition — a windowing system that was years ahead of its time. Multiple screens, depth-arranged windows, real-time dragging — features that competing platforms wouldn't match for a decade. But Intuition had a fundamental limitation baked into its DNA: every pixel was the programmer's responsibility. Gadgets had fixed coordinates. Fonts were hardcoded. Layouts were brittle. If the user changed their system font from Topaz 8 to Helvetica 11, half the applications on their Workbench became unusable — text clipped, gadgets overlapping, windows too small to function.

By the early 1990s, this was no longer acceptable. The Amiga had evolved from a single-resolution PAL/NTSC machine into a platform supporting everything from 320×200 interlace to 1280×1024 RTG displays. Localization was becoming critical as the European market — Germany, Scandinavia, the UK — drove the majority of Amiga sales. A German label that reads "Datei" in English might be "Dateiverzeichnis" — and there was simply no way to handle that with ng_LeftEdge = 120.

MUI (Magic User Interface), created by Stefan Stuntz and first released in 1993, was the answer. It didn't just patch the problem — it fundamentally redefined how Amiga GUI programming worked. MUI introduced a declarative, object-oriented, layout-managed paradigm that replaced the pixel-coordinate imperative model entirely. The developer no longer tells the system where to place a gadget. Instead, the developer describes what the interface contains — its structure, its relationships, its behavior — and MUI decides where and how to render everything at runtime.

The Paradigm Shift

MUI's arrival on the Amiga platform represented something analogous to what CSS Flexbox would later do for web development, or what Auto Layout would do for iOS — but in 1993, on a 7 MHz 68000 with 512 KB of RAM. The core insight was deceptively simple: separate structure from presentation.

This wasn't just an API improvement. It was a philosophical break:

  • Imperative → Declarative. Instead of CreateGadget(STRING_KIND, gad, &ng, ...) with manual X/Y/W/H, you write Child, StringObject, StringFrame, End. No coordinates. No font measurements. No pixel arithmetic.

  • Programmer-controlled → User-controlled. MUI ships with a comprehensive Preferences application that lets end users control fonts, colors, frame styles, spacing, backgrounds, and scrollbar appearance — for every MUI application, globally. Two users running the same binary can have completely different visual experiences, and both are pixel-perfect.

  • Monolithic event loop → Reactive notification. Traditional Intuition programming requires a central while(GetMsg()) loop that manually dispatches every IDCMP message to every gadget. MUI replaces this with MUIM_Notify — a declarative binding system where objects notify each other directly. The event loop shrinks to three lines.

  • Fixed rendering → Retained-mode rendering. In raw Intuition, the application is responsible for every pixel of damage repair. MUI maintains a complete object tree and handles all refresh, resize, and clipping internally. The developer's custom rendering code only needs to paint the widget's content; MUI handles the rest.

Built on BOOPSI

MUI is not a standalone system. It is built on top of BOOPSI (Basic Object-Oriented Programming System for Intuition), the native object system introduced in AmigaOS 2.0. BOOPSI provides the foundational infrastructure — class registration, object instantiation via NewObject(), method dispatch via DoMethod(), and single inheritance. Every MUI object is a BOOPSI object. Every MUI class is registered in the BOOPSI class tree.

What MUI adds on top of BOOPSI is substantial:

BOOPSI Provides MUI Adds
Class registry (AddClass, FindClass) 40+ built-in widget classes
Object lifecycle (NewObject, DisposeObject) Declarative tree construction with macros
Method dispatch (DoMethod, DoSuperMethod) Notification system (MUIM_Notify)
Attribute system (OM_SET, OM_GET) Extended attribute naming (MUIA_*, I/S/G flags)
Single inheritance Layout engine (constraint-based sizing)
Nothing for layout Automatic font-sensitive positioning
Nothing for user prefs Full preference system with per-app overrides

Evolution and Adoption

MUI evolved through several major releases, all authored solely by Stefan Stuntz until the AmigaOS 4 continuation:

Version Year Author Milestone
1.0 1993 Stefan Stuntz Initial release. Core class tree, layout engine, notification system
2.0 1994 Stefan Stuntz Virtual groups, popup classes, improved preferences
3.0 1995 Stefan Stuntz Custom class API (MUI_CreateCustomClass), drag & drop
3.8 1997 Stefan Stuntz Final public developer release. Definitive API for classic AmigaOS
4.0 2005+ Thore Böckelmann AmigaOS 4 continuation, maintained under license from Stuntz

By the mid-1990s, MUI had become the de-facto standard for serious Amiga applications. Programs like YAM (Yet Another Mailer), IBrowse, AmiTradeCenter, SimpleMail, and Odyssey were all built with MUI. The Aminet archive accumulated hundreds of third-party MCC (MUI Custom Class) libraries — NList for multi-column lists, TextEditor for full text editing, HTMLview for web rendering — creating an ecosystem that no other Amiga GUI framework could match.

MUI's influence extended beyond AmigaOS. MorphOS adopted MUI as its native GUI toolkit. AROS created Zune, an API-compatible reimplementation. The core architectural patterns — declarative layout, reactive notification, user-controlled theming — anticipated design principles that wouldn't become mainstream on other platforms until years later.

Distribution and Licensing

MUI was distributed as shareware — a licensing model common in the Amiga ecosystem of the 1990s. The split worked as follows:

Component License Distribution
Runtime (muimaster.library + prefs app) Freeware Aminet util/libs/mui38usr.lha — freely redistributable
Developer SDK (headers, autodocs, examples) Shareware Aminet dev/mui/mui38dev.lha — registration requested
Source code Proprietary Never released; MUI remained closed-source

Unregistered users saw periodic nag requesters reminding them to register — a friction point that contributed to some community resistance, though most serious developers registered.

Version 3.8 (1997) was the last release for classic AmigaOS (68K). Stefan Stuntz ceased active development of the classic branch after this version. The 3.8 API became the frozen reference point that all classic AmigaOS, MorphOS, and AROS (Zune) implementations target. No further features or bug fixes were issued for the 68K codebase.

The library was not included in AmigaOS ROM — it installed as a disk-resident library at LIBS:muimaster.library (~300 KB). This external dependency was MUI's primary competitive disadvantage against ROM-resident alternatives like GadTools, but the overwhelming superiority of the programming model made it the dominant choice regardless.

Historical Context — Parallel Evolution of GUI Paradigms

MUI did not emerge in isolation. The early 1990s saw an industry-wide reckoning with the limitations of manual, pixel-coordinate GUI programming. Several platforms arrived at remarkably similar solutions independently — a convergent evolution driven by the same underlying problem.

The Timeline of Innovation:

Year Platform Innovation
1988 NeXTSTEP Interface Builder + AppKit: visual declarative layout, outlet/action connections, .nib serialization
1990 Amiga OS 2.0 BOOPSI: object-oriented gadget system with NewObject(), DoMethod(), tag-based attributes
1991 Motif / Xt Widget constraint resources, XmForm constraint-based layout
1992 Qt (conception) Eirik Chambe-Eng invents the signals and slots paradigm at Trolltech
1993 MUI 1.0 MUIM_Notify reactive bindings, constraint-based layout engine, user preference system
1994 Qt 0.90 First public Qt release. MOC-generated signals/slots, widget hierarchy
1997 MUI 3.8 Definitive classic API — drag & drop, custom classes, virtual groups
1998 Qt 2.0 Layout managers (QHBoxLayout, QVBoxLayout), style engines

MUI's MUIM_Notify and Qt's Signals/Slots are conceptually parallel solutions to the same problem: how do objects communicate in a loosely-coupled, event-driven GUI?

Concept MUI (1993) Qt (1994)
Binding declaration DoMethod(src, MUIM_Notify, attr, val, dst, n, method, ...) connect(src, SIGNAL(sig()), dst, SLOT(slot()))
Trigger Attribute change matches a value Signal emission
Target Any object, any method Any QObject, any slot
Coupling Source knows nothing about target's implementation Source knows nothing about receiver
Value passing MUIV_TriggerValue forwards the changed value Signal parameters forwarded to slot
Implementation Runtime tag dispatch, no preprocessor MOC-generated metaobject tables

There is no documented evidence that Qt's signal/slot system was directly derived from MUI's notification model — they were invented independently within months of each other. However, the architectural convergence is striking. Both abandoned the callback-function-pointer pattern that dominated C and C++ GUI programming in favor of a declarative, loosely-coupled observer model.

NeXTSTEP's influence is easier to trace. Steve Jobs' NeXT platform introduced the outlet/action pattern in 1988 — a visual wiring system where Interface Builder connected UI objects to code without explicit callback registration. MUI's notification system solves the same problem from a different angle: NeXTSTEP used visual wiring in a builder tool; MUI used declarative code-level bindings. Both eliminated the monolithic event-dispatch loop.

The broader pattern:

graph TB
    subgraph "The Problem (19851990)"
        MANUAL["Manual pixel-coordinate GUIs<br/>break on font/resolution changes"]
        CALLBACK["Callback hell<br/>tight coupling, fragile dispatch"]
    end

    subgraph "Independent Solutions (19881994)"
        NEXT["NeXTSTEP (1988)<br/>Interface Builder<br/>outlet/action wiring"]
        BOOPSI["AmigaOS 2.0 (1990)<br/>BOOPSI object model<br/>tag-based attributes"]
        MUI["MUI (1993)<br/>MUIM_Notify reactive bindings<br/>constraint layout engine"]
        QT["Qt (199294)<br/>signals/slots<br/>layout managers"]
        MOTIF["Motif (1991)<br/>XmForm constraints<br/>widget resources"]
    end

    subgraph "Modern Descendants"
        COCOA["Cocoa / UIKit<br/>Auto Layout, KVO"]
        REACT["React / SwiftUI<br/>declarative, reactive"]
        QTMOD["Qt 5/6<br/>QML, property bindings"]
        CSS["CSS Flexbox / Grid<br/>constraint layout"]
    end

    MANUAL --> NEXT & BOOPSI & MOTIF
    CALLBACK --> NEXT & MUI & QT
    BOOPSI --> MUI
    NEXT --> COCOA --> REACT
    MUI --> |"conceptual parallel"| QT
    QT --> QTMOD
    MOTIF --> CSS
    MUI --> |"API adopted"| MORPHOS["MorphOS / AROS (Zune)"]

    style MUI fill:#e8f5e9,stroke:#4caf50,color:#333
    style QT fill:#e8f4fd,stroke:#2196f3,color:#333
    style NEXT fill:#fff3e0,stroke:#ff9800,color:#333

What made MUI remarkable was not just what it did — other platforms were solving similar problems — but when and where it did it. MUI delivered a fully declarative, reactive, user-themeable GUI framework in 1993, on a 7 MHz 68000 with 512 KB of RAM, in pure C without a preprocessor, a visual builder, or a garbage collector. The same concepts that would later require the MOC preprocessor in Qt, the Objective-C runtime on NeXT, or a JavaScript virtual DOM — MUI achieved with tag arrays and BOOPSI dispatchers.

What Developers Actually Valued

Community feedback from forums (Amiga.org, EAB/English Amiga Board, Reddit r/amiga) and developer testimonials consistently highlights these qualities:

  • "Install MUI first" — setting up a working Amiga Workbench without MUI was considered incomplete. It was the single most essential third-party package.
  • Rapid development — developers report that complex UIs that would take hundreds of lines of GadTools code compress to 3050 lines of MUI macros.
  • "It just works on every screen" — the font/resolution independence meant software worked from 320×200 interlace to 1280×1024 RTG without any code changes.
  • The MCC ecosystem — third-party classes like NList, TextEditor, and TheBar turned MUI into a comprehensive widget toolkit rivalling contemporary commercial frameworks.
  • User empowerment — end users valued MUI because they controlled how their desktop looked. The MUI Preferences application was as important to the Amiga user experience as the applications themselves.

The primary criticisms — resource weight on 68000, shareware nag screens, and external dependency — were valid trade-offs that the community overwhelmingly accepted in exchange for the programming model.

Why MUI Matters

Before MUI With MUI
Manual X/Y coordinates for every gadget Automatic layout with groups
Fixed fonts break layout Font-sensitive, resolution-independent
No standard look across apps Consistent, user-customizable appearance
Complex IDCMP event loop Declarative notification system
Programmer handles all refresh Framework handles damage repair
Each app reinvents scrollbars, lists Rich built-in widget set

The Fundamental Problem MUI Solves

Intuition's Fixed-Layout Model

Traditional Intuition/GadTools programming requires the developer to specify exact pixel coordinates for every gadget. This creates a fragile layout that breaks when any environmental variable changes:

/* Traditional GadTools — hardcoded coordinates */
struct NewGadget ng = {
    .ng_LeftEdge   = 120,    /* absolute X position */
    .ng_TopEdge    = 24,     /* absolute Y position */
    .ng_Width      = 200,    /* fixed width in pixels */
    .ng_Height     = 14,     /* fixed height in pixels */
    .ng_GadgetText = "Name:",
    .ng_TextAttr   = &topaz8,  /* hardcoded font */
};
gad = CreateGadget(STRING_KIND, gad, &ng, ...);

/* Next gadget — manually calculated position */
ng.ng_TopEdge += 20;  /* hope 20 pixels is enough... */
ng.ng_GadgetText = "Address:";
gad = CreateGadget(STRING_KIND, gad, &ng, ...);

This breaks in the following common scenarios:

graph TB
    subgraph "What Goes Wrong with Fixed Layout"
        FONT["User changes system font<br/>Topaz 8 → Helvetica 11"]
        LOCALE["German locale<br/>'Name' → 'Benutzername'"]
        RES["Different screen resolution<br/>640×256 → 1280×512"]
        OVERSCAN["PAL overscan vs NTSC"]
    end

    subgraph "Result"
        CLIP["Text clipped / overlapping"]
        TRUNCATE["Labels truncated"]
        TINY["Window unusably small"]
        OVERLAP["Gadgets overlap each other"]
    end

    FONT --> CLIP & OVERLAP
    LOCALE --> TRUNCATE & OVERLAP
    RES --> TINY
    OVERSCAN --> CLIP

How MUI Fixes It

MUI replaces absolute coordinates with a constraint-based layout model. The developer declares the structure of the interface, and MUI calculates positions at runtime:

/* MUI — same dialog, no coordinates anywhere */
WindowContents, VGroup,
    Child, ColGroup(2),
        Child, Label2("Name:"),
        Child, StringObject, StringFrame, End,

        Child, Label2("Address:"),
        Child, StringObject, StringFrame, End,
    End,

    Child, HGroup,
        Child, SimpleButton("_OK"),
        Child, SimpleButton("_Cancel"),
    End,
End,

MUI automatically:

  • Measures label widths in the current font and aligns columns
  • Adapts when the user switches from Topaz 8 to any proportional font
  • Accommodates longer text in localized versions (German, French)
  • Respects the screen's available area and overscan settings
  • Makes the window resizable — gadgets stretch proportionally

Side-by-Side Comparison

graph LR
    subgraph "GadTools (Fixed)"
        G_CREATE["CreateGadget()<br/>x=120, y=24, w=200, h=14"]
        G_NEXT["ng.TopEdge += 20"]
        G_DRAW["Draw labels manually<br/>IntuiText at fixed pos"]
        G_RESIZE["Window not resizable<br/>WFLG_SIZEGADGET absent"]
        G_REFRESH["App handles REFRESHWINDOW<br/>BeginRefresh/EndRefresh"]
    end

    subgraph "MUI (Dynamic)"
        M_CREATE["VGroup / ColGroup<br/>Children declared, no coords"]
        M_NEXT["Child, next object<br/>auto-positioned"]
        M_DRAW["Labels are objects<br/>auto-measured, auto-aligned"]
        M_RESIZE["Window freely resizable<br/>layout recalculates"]
        M_REFRESH["MUI handles all refresh<br/>retained-mode rendering"]
    end

    style G_CREATE fill:#ffebee,stroke:#f44336,color:#333
    style G_NEXT fill:#ffebee,stroke:#f44336,color:#333
    style G_DRAW fill:#ffebee,stroke:#f44336,color:#333
    style G_RESIZE fill:#ffebee,stroke:#f44336,color:#333
    style G_REFRESH fill:#ffebee,stroke:#f44336,color:#333

    style M_CREATE fill:#e8f5e9,stroke:#4caf50,color:#333
    style M_NEXT fill:#e8f5e9,stroke:#4caf50,color:#333
    style M_DRAW fill:#e8f5e9,stroke:#4caf50,color:#333
    style M_RESIZE fill:#e8f5e9,stroke:#4caf50,color:#333
    style M_REFRESH fill:#e8f5e9,stroke:#4caf50,color:#333

Key Capabilities Deep Dive

1. Automatic Layout Management

MUI's layout engine works in three passes for every window open/resize:

Pass Name Action
1 AskMinMax Each object reports minimum, maximum, and default size
2 Calculate Groups sum child minimums, distribute remaining space by weight
3 Place Each object receives its final (x, y, w, h) rectangle

This means:

  • A VGroup stacks children vertically, distributing vertical space
  • An HGroup arranges children horizontally, distributing horizontal space
  • A ColGroup(n) creates an n-column grid with automatic column alignment
  • Weights (MUIA_Weight) control how extra space is divided — a weight-200 object gets twice the space of a weight-100 sibling
  • HVSpace objects act as springs, pushing siblings apart

2. Full User Customization

MUI ships with a Preferences application (MUI:MUI) that lets end users control:

Category What Users Control Developer Impact
Fonts Every text element: lists, buttons, labels, titles App looks right in any font
Frames Border style: none, button, string, group, etc. No hardcoded DrawBevelBox()
Backgrounds Fill pattern, color, gradient, image per object type No hardcoded SetAPen/RectFill
Spacing Inner/outer margins, group spacing No magic pixel constants
Scrollbars Look, feel, arrow buttons No custom scrollbar code
Windows Snap positions, remember sizes, screen type No OpenWindowTags() tuning
Keyboard Tab cycling, default gadget, shortcut handling Auto keyboard navigation

This means two users running the same MUI application can have it look completely different — one with a minimalist look, another with textured backgrounds and 3D frames — and both layouts are pixel-perfect because MUI recalculates everything.

3. Object-to-Object Notification

Traditional Intuition requires a central event loop that explicitly checks every gadget:

/* Traditional: monolithic event loop */
while ((msg = GT_GetIMsg(win->UserPort))) {
    switch (msg->Class) {
        case IDCMP_GADGETUP:
            switch (((struct Gadget *)msg->IAddress)->GadgetID) {
                case GAD_SLIDER:
                    /* manually read slider value */
                    /* manually update text display */
                    /* manually update other dependent gadgets */
                    break;
                case GAD_CHECK:
                    /* manually check state */
                    /* manually enable/disable other gadgets */
                    break;
            }
            break;
    }
    GT_ReplyIMsg(msg);
}

MUI replaces this with declarative reactive bindings:

/* MUI: declare relationships once, framework handles updates */

/* Slider value → text display (automatic, no main loop code) */
DoMethod(slider, MUIM_Notify,
    MUIA_Slider_Level, MUIV_EveryTime,
    text, 4, MUIM_Set, MUIA_Text_Contents, MUIV_TriggerValue);

/* Checkbox → enable/disable button (automatic) */
DoMethod(checkbox, MUIM_Notify,
    MUIA_Selected, MUIV_EveryTime,
    button, 3, MUIM_Set, MUIA_Disabled, MUIV_NotTriggerValue);

/* The main loop is now trivially simple: */
while (DoMethod(app, MUIM_Application_NewInput, &sigs) 
       != MUIV_Application_ReturnID_Quit)
    if (sigs) sigs = Wait(sigs | SIGBREAKF_CTRL_C);

4. Retained-Mode Rendering

Approach How It Works Developer Burden
Intuition (immediate mode) App draws directly to RastPort. On damage, app must redraw everything. High — manage damage rectangles, BeginRefresh/EndRefresh, clip regions
MUI (retained mode) MUI knows the object tree and each object's visual state. Redraws automatically. Zero — framework handles REFRESHWINDOW, window resize, screen depth changes

The developer's MUIM_Draw method in a custom class only needs to draw the widget's content. MUI handles:

  • Background erasure (using user-selected pattern)
  • Frame drawing
  • Clip rectangles for damage repair
  • Partial redraws (MADF_DRAWUPDATE vs MADF_DRAWOBJECT)

5. Drag & Drop

MUI provides built-in drag & drop between objects:

/* Enable drag on source */
set(source_list, MUIA_Draggable, TRUE);

/* Enable drop on target */
set(target_list, MUIA_Dropable, TRUE);

/* MUI handles the visual feedback (ghost image) and
   calls MUIM_DragQuery / MUIM_DragDrop on the target */

No custom input handling, no manual image compositing, no Intuition layer manipulation needed.

6. Keyboard Navigation

MUI automatically implements:

  • Tab cycling between gadgets (with visual highlight)
  • Underscored shortcut keys (the _O in _OK becomes Alt+O)
  • Return key on default button
  • Escape key to cancel/close
  • Cursor keys in lists and cycle gadgets

None of this requires any developer code beyond the _ prefix in label strings.

7. Context Menus

/* Attach a context menu to any MUI object */
set(myObject, MUIA_ContextMenu, menustrip);

/* MUI handles right-click detection, popup positioning,
   and menu item dispatch — zero IDCMP code needed */

8. Application and Window State Persistence

/* Give the window a unique ID */
MUIA_Window_ID, MAKE_ID('M','A','I','N'),

/* MUI automatically saves and restores:
   - Window position and size
   - Column widths in lists
   - Splitter positions
   - Active page in RegisterGroups (tabs)
   
   Stored in ENV:MUI/<appname>.cfg */

Pros and Cons

Advantages

Advantage Detail
Font independence Layout adapts to any font — essential for localization
Resolution independence Same binary works on 640×200 PAL and 1280×1024 RTG
Resizable windows Every MUI window can be resized; layout recalculates automatically
User customization End users control appearance without modifying the application
Minimal boilerplate A complete app with window, gadgets, and event handling in ~50 lines
Notification model Eliminates most event loop complexity
Rich widget set Lists, trees, text editors, toolbars available as MCCs
Retained mode No manual damage repair — framework handles all refresh
Drag & drop Built-in, with visual feedback
State persistence Window positions and sizes remembered automatically
Cross-platform Code runs on MorphOS and AROS (Zune) with minimal changes
Active ecosystem Large MCC (custom class) library on Aminet

Disadvantages

Disadvantage Detail
External dependency muimaster.library must be installed (not in ROM) — ~300 KB
Memory overhead MUI objects consume more RAM than raw GadTools gadgets (~25× per widget)
Startup time Loading and initializing the MUI class tree adds noticeable delay on 68000
Learning curve OOP concepts in C (dispatchers, TagItems, method IDs) are unfamiliar to many
Complex debugging Object tree issues (wrong parent, missing End) cause cryptic crashes
Shareware stigma Early MUI was shareware with nag screens — some users avoided it
Overkill for simple tools A CLI utility with one requester doesn't need MUI's overhead
Black box rendering Custom drawing requires understanding MUIM_Draw, _rp(), _mleft() macros

When to Use What

Scenario Recommendation
Full-featured GUI application MUI — automatic layout, user prefs, rich widgets
OS preferences editor ReAction (OS 3.5+) — ships with OS, no external dep
Simple one-window tool GadTools — lightweight, always available in ROM
Game or demo UI Custom Intuition — direct control over rendering
OS 1.3 compatibility required Raw Intuition — no GadTools, no BOOPSI
Cross-platform (MorphOS/AROS) MUI — Zune on AROS is API-compatible

Architecture

graph TB
    subgraph "Application Code"
        APP_CODE["Your Program<br/>MUI_NewObject / DoMethod calls"]
    end

    subgraph "MUI Framework"
        MUIMASTER["muimaster.library<br/>Class registry, object lifecycle"]
        PREFS["MUI Preferences<br/>Global + per-app appearance"]
        LAYOUT["Layout Engine<br/>Min/max/def sizing, font scaling"]
        NOTIFY["Notification System<br/>MUIM_Notify inter-object events"]
    end

    subgraph "BOOPSI Layer"
        BOOPSI["BOOPSI Dispatcher<br/>OM_NEW, OM_SET, OM_GET<br/>rootclass → MUI classes"]
    end

    subgraph "Intuition / Graphics"
        INT["intuition.library<br/>Screens, Windows"]
        GFX["graphics.library<br/>RastPort, Layers"]
    end

    subgraph "Hardware"
        CUSTOM["Custom Chips<br/>Copper, Blitter, Denise"]
    end

    APP_CODE --> MUIMASTER
    MUIMASTER --> PREFS & LAYOUT & NOTIFY
    MUIMASTER --> BOOPSI
    BOOPSI --> INT
    INT --> GFX
    GFX --> CUSTOM

Key Insight: The Library as Abstraction

MUI applications link against muimaster.library. This single library:

  • Contains all built-in classes (Application, Window, Group, Button, String, List, etc.)
  • Manages the BOOPSI class tree
  • Implements the layout engine
  • Handles the preference system
  • Provides the event loop (MUIM_Application_NewInput)

The application never calls OpenWindow(), DrawImage(), or ModifyIDCMP() directly. MUI handles all Intuition interactions internally.


Class Hierarchy

graph TB
    ROOT["rootclass<br/>(BOOPSI root)"]

    NOTIFY["Notify<br/>Attribute change notifications<br/>MUIM_Notify, MUIM_Set"]

    FAMILY["Family<br/>Parent-child relationships"]

    AREA["Area<br/>Base visual class<br/>Sizing, drawing, input"]

    GROUP["Group<br/>Layout container<br/>Horiz / Vert / 2D"]
    RECT["Rectangle<br/>Spacer / separator"]
    IMAGE["Image<br/>Image display"]
    TEXT["Text<br/>Text display + formatting"]
    STRING["String<br/>Text input field"]
    LIST["List<br/>Scrollable item list"]
    PROP["Prop / Scrollbar<br/>Value slider"]
    CYCLE["Cycle<br/>Popup selection"]
    SLIDER["Slider<br/>Numeric range"]
    RADIO["Radio<br/>Exclusive choice"]
    GAUGE["Gauge<br/>Progress bar"]
    SCALE["Scale<br/>Percentage display"]
    NLIST["NList<br/>Multi-column list (3rd party)"]
    BALANCE["Balance<br/>Drag divider"]

    WINDOW["Window<br/>Intuition window wrapper"]
    APP["Application<br/>Program root object"]
    ABOUTMUI["Aboutmui<br/>Standard About MUI dialog"]

    MENU["Menustrip<br/>Menu hierarchy"]
    MENUOBJ["Menu<br/>Menu title"]
    MENUITEM["Menuitem<br/>Menu entry"]

    ROOT --> NOTIFY
    NOTIFY --> FAMILY & AREA & WINDOW & APP & ABOUTMUI & MENU
    FAMILY --> GROUP
    MENU --> MENUOBJ --> MENUITEM
    AREA --> GROUP & RECT & IMAGE & TEXT & STRING & LIST & PROP & CYCLE & SLIDER & RADIO & GAUGE & SCALE & BALANCE
    LIST --> NLIST

    style ROOT fill:#f0f0f0,stroke:#999,color:#333
    style NOTIFY fill:#e8f4fd,stroke:#2196f3,color:#333
    style AREA fill:#e8f5e9,stroke:#4caf50,color:#333
    style APP fill:#fff3e0,stroke:#ff9800,color:#333
    style WINDOW fill:#fff3e0,stroke:#ff9800,color:#333
    style GROUP fill:#e8f5e9,stroke:#4caf50,color:#333

Class Responsibilities

Class Role Key Attributes
Notify Base for all MUI objects. Handles attribute change notifications MUIM_Notify, MUIM_Set, MUIM_Get
Area Base for all visual objects. Handles size negotiation, rendering, input MUIA_Frame, MUIA_Background, MUIA_Weight
Group Container that arranges children horizontally, vertically, or in pages MUIA_Group_Horiz, MUIA_Group_Columns, MUIA_Group_PageMode
Window Wraps an Intuition window. Contains exactly one root Group MUIA_Window_Title, MUIA_Window_Open, MUIA_Window_CloseRequest
Application Top-level program object. Owns all windows, drives event loop MUIA_Application_Title, MUIA_Application_Author
List Scrollable list with construct/destruct/display hooks MUIA_List_Format, MUIA_List_ConstructHook
String Single-line text input MUIA_String_Contents, MUIA_String_Accept
Cycle Dropdown selection (popup menu) MUIA_Cycle_Entries, MUIA_Cycle_Active
Text Multi-line formatted text display MUIA_Text_Contents, MUIA_Text_PreParse

The MUI Object Model

Object Creation — Declarative Tree

MUI applications build the entire GUI as a nested object tree using macros that expand to MUI_NewObject() calls:

#include <libraries/mui.h>

Object *app = ApplicationObject,
    MUIA_Application_Title,    "MyApp",
    MUIA_Application_Version,  "$VER: MyApp 1.0 (23.04.2026)",
    MUIA_Application_Author,   "Developer Name",

    SubWindow, WindowObject,
        MUIA_Window_Title, "Main Window",
        MUIA_Window_ID,    MAKE_ID('M','A','I','N'),

        WindowContents, VGroup,
            Child, TextObject,
                MUIA_Text_Contents, "\033cHello, MUI!",
                MUIA_Text_PreParse, "\033b",   /* bold */
            End,

            Child, HGroup,
                Child, bt_ok = SimpleButton("_OK"),
                Child, bt_cancel = SimpleButton("_Cancel"),
            End,
        End,
    End,
End;

Object Tree Visualized

graph TB
    APPLICATION["Application<br/>'MyApp'"]
    WINDOW["Window<br/>'Main Window'"]
    VGROUP["VGroup<br/>(vertical layout)"]
    TEXT["Text<br/>'Hello, MUI!'"]
    HGROUP["HGroup<br/>(horizontal layout)"]
    BT_OK["Button<br/>'OK'"]
    BT_CANCEL["Button<br/>'Cancel'"]

    APPLICATION --> WINDOW
    WINDOW --> VGROUP
    VGROUP --> TEXT
    VGROUP --> HGROUP
    HGROUP --> BT_OK
    HGROUP --> BT_CANCEL

Layout Engine

MUI's layout engine is one of its most powerful features. It uses a three-pass constraint system:

Sizing Protocol

sequenceDiagram
    participant MUI as MUI Layout Engine
    participant GRP as Group
    participant C1 as Child 1
    participant C2 as Child 2

    Note over MUI: Pass 1: Query
    MUI->>C1: AskMinMax() → {min, max, def}
    MUI->>C2: AskMinMax() → {min, max, def}
    C1-->>GRP: min=50, max=200, def=100
    C2-->>GRP: min=30, max=500, def=80

    Note over MUI: Pass 2: Calculate
    GRP->>GRP: Sum minimums, distribute<br/>remaining space by weight

    Note over MUI: Pass 3: Place
    GRP->>C1: Layout(x=0, y=0, w=120, h=20)
    GRP->>C2: Layout(x=0, y=22, w=120, h=18)

Weight System

Objects within a Group can have different weights that control how extra space is distributed:

Child, HGroup,
    /* Left panel: takes 30% of space */
    Child, ListviewObject,
        MUIA_Weight, 30,
    End,

    /* Right panel: takes 70% of space */
    Child, TextObject,
        MUIA_Weight, 70,
    End,
End,

Group Types

Type Macro Behavior
Vertical VGroup Stack children top-to-bottom
Horizontal HGroup Arrange children left-to-right
Page RegisterGroup(titles) Tabbed pages (only one visible)
Columns ColGroup(n) n-column grid layout
Virtual VGroupV / ScrollgroupObject Scrollable content area

Notification System — Event-Driven Programming

MUI's notification system replaces the traditional Intuition event loop. Instead of polling for IDCMP messages, you declare relationships between objects:

graph LR
    subgraph "Declarative"
        CLOSE["Window.CloseRequest<br/>changes to TRUE"]
        QUIT["Application.ReturnID<br/>receives Quit"]
    end

    subgraph "Traditional (without MUI)"
        IDCMP["GetMsg(UserPort)"]
        SWITCH["switch(msg->Class)"]
        HANDLER["case CLOSEWINDOW:<br/>running = FALSE"]
    end

    CLOSE -->|"MUIM_Notify"| QUIT

    style CLOSE fill:#e8f5e9,stroke:#4caf50,color:#333
    style QUIT fill:#e8f5e9,stroke:#4caf50,color:#333
    style IDCMP fill:#ffebee,stroke:#f44336,color:#333
    style SWITCH fill:#ffebee,stroke:#f44336,color:#333
    style HANDLER fill:#ffebee,stroke:#f44336,color:#333

MUIM_Notify Syntax

DoMethod(source, MUIM_Notify,
    attribute,          /* which attribute to watch */
    trigger_value,      /* value that triggers action (or MUIV_EveryTime) */
    target,             /* object to receive the action */
    param_count,        /* number of following parameters */
    method, ...args     /* method + arguments to invoke on target */
);

Common Notification Patterns

/* Close window → quit application */
DoMethod(window, MUIM_Notify,
    MUIA_Window_CloseRequest, TRUE,
    app, 2,
    MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit);

/* Slider changes → update numeric display */
DoMethod(slider, MUIM_Notify,
    MUIA_Slider_Level, MUIV_EveryTime,
    text, 4,
    MUIM_Set, MUIA_Text_Contents, MUIV_TriggerValue);

/* Checkbox toggles → enable/disable button */
DoMethod(checkbox, MUIM_Notify,
    MUIA_Selected, MUIV_EveryTime,
    button, 3,
    MUIM_Set, MUIA_Disabled, MUIV_NotTriggerValue);

/* Button pressed → call custom hook function */
DoMethod(button, MUIM_Notify,
    MUIA_Pressed, FALSE,          /* FALSE = on release */
    app, 2,
    MUIM_CallHook, &myHook);

/* List double-click → call application method */
DoMethod(list, MUIM_Notify,
    MUIA_Listview_DoubleClick, TRUE,
    app, 2,
    MUIM_Application_ReturnID, ID_EDIT);

Trigger Value Constants

Constant Meaning
MUIV_EveryTime Trigger on any change
MUIV_TriggerValue Pass the new attribute value as argument
MUIV_NotTriggerValue Pass the logical NOT of the new value
TRUE / FALSE Trigger only on specific boolean value

The Event Loop

MUI's event loop is minimal compared to traditional Intuition programming:

/* Traditional MUI main loop */
ULONG signals = 0;
BOOL running = TRUE;

while (running) {
    ULONG id = DoMethod(app, MUIM_Application_NewInput, &signals);

    switch (id) {
        case MUIV_Application_ReturnID_Quit:
            running = FALSE;
            break;

        case ID_OPEN:
            /* handle custom action */
            break;
    }

    if (running && signals)
        signals = Wait(signals | SIGBREAKF_CTRL_C);

    if (signals & SIGBREAKF_CTRL_C)
        running = FALSE;
}

Event Flow

sequenceDiagram
    participant APP as Application
    participant MUI as MUI Framework
    participant INT as Intuition
    participant USER as User

    loop Main Loop
        APP->>MUI: MUIM_Application_NewInput(&signals)
        MUI->>INT: Check IDCMP ports
        INT->>MUI: IntuiMessages (if any)
        MUI->>MUI: Dispatch to objects<br/>Fire notifications
        MUI-->>APP: Return ID (or 0)
        APP->>APP: Handle Return ID
        APP->>APP: Wait(signals)
        USER->>INT: Mouse click / keystroke
    end

Custom Classes

MUI's extensibility comes from its custom class system. You create new widget types by subclassing existing MUI classes.

Anatomy of a Custom Class

/* Private instance data */
struct MySliderData {
    LONG value;
    LONG min, max;
    Object *label;
};

/* Dispatcher function */
IPTR MySlider_Dispatcher(struct IClass *cl, Object *obj, Msg msg)
{
    switch (msg->MethodID) {
        case OM_NEW:
        {
            /* Initialize object */
            obj = (Object *)DoSuperMethodA(cl, obj, msg);
            if (obj) {
                struct MySliderData *data = INST_DATA(cl, obj);
                data->value = GetTagData(MUIA_MySlider_Value, 0,
                    ((struct opSet *)msg)->ops_AttrList);
                data->min = 0;
                data->max = 100;
            }
            return (IPTR)obj;
        }

        case OM_SET:
        {
            /* Handle attribute changes */
            struct TagItem *tags = ((struct opSet *)msg)->ops_AttrList;
            struct TagItem *tag;
            struct MySliderData *data = INST_DATA(cl, obj);

            while ((tag = NextTagItem(&tags))) {
                switch (tag->ti_Tag) {
                    case MUIA_MySlider_Value:
                        data->value = tag->ti_Data;
                        MUI_Redraw(obj, MADF_DRAWUPDATE);
                        break;
                }
            }
            return DoSuperMethodA(cl, obj, msg);
        }

        case MUIM_Draw:
        {
            /* Render the widget */
            struct MySliderData *data = INST_DATA(cl, obj);
            DoSuperMethodA(cl, obj, msg);  /* draw background first */

            /* Custom rendering using _rp(obj), _mleft(obj), _mtop(obj), etc. */
            struct RastPort *rp = _rp(obj);
            LONG x = _mleft(obj);
            LONG y = _mtop(obj);
            LONG w = _mwidth(obj);
            LONG h = _mheight(obj);

            /* Draw slider track and knob... */
            SetAPen(rp, _pens(obj)[MPEN_SHINE]);
            RectFill(rp, x, y, x + w - 1, y + h - 1);

            return 0;
        }

        case MUIM_AskMinMax:
        {
            /* Report size requirements */
            struct MUI_MinMax *mi = (struct MUI_MinMax *)msg;
            DoSuperMethodA(cl, obj, msg);
            mi->MinWidth  += 50;
            mi->MinHeight += 12;
            mi->DefWidth  += 100;
            mi->DefHeight += 16;
            mi->MaxWidth  += MUI_MAXMAX;
            mi->MaxHeight += 20;
            return 0;
        }
    }

    /* Unknown method → pass to parent class */
    return DoSuperMethodA(cl, obj, msg);
}

/* Class creation */
struct MUI_CustomClass *mcc_MySlider;

mcc_MySlider = MUI_CreateCustomClass(NULL,
    MUIC_Area,                    /* parent class */
    NULL,                         /* not from a library */
    sizeof(struct MySliderData),  /* instance data size */
    MySlider_Dispatcher);         /* dispatcher function */

/* Usage */
Object *slider = NewObject(mcc_MySlider->mcc_Class, NULL,
    MUIA_MySlider_Value, 50,
    TAG_DONE);

/* Cleanup */
MUI_DeleteCustomClass(mcc_MySlider);

Custom Class Dispatch Flow

graph TB
    METHOD["Incoming Method<br/>(OM_NEW, MUIM_Draw, etc.)"]
    DISPATCH["MySlider_Dispatcher()"]
    KNOWN{"Method<br/>recognized?"}
    HANDLE["Handle locally<br/>(custom logic)"]
    SUPER["DoSuperMethodA()<br/>(pass to parent)"]
    AREA["Area class dispatcher"]
    NOTIFY["Notify class dispatcher"]
    ROOT["rootclass dispatcher"]

    METHOD --> DISPATCH
    DISPATCH --> KNOWN
    KNOWN -->|"Yes"| HANDLE
    KNOWN -->|"No"| SUPER
    SUPER --> AREA --> NOTIFY --> ROOT

MUI Preferences System

One of MUI's defining features: the end user controls the application's appearance.

Preference Levels

Level Scope Location
Global All MUI applications ENV:MUI/
Per-class All instances of a class ENV:MUI/<classname>.mcp
Per-application Single application ENV:MUI/<appname>.cfg
Object-level Code-specified defaults MUIA_* tags in source

What Users Can Customise

  • Fonts (all text elements)
  • Frame types and thickness
  • Background patterns / images
  • Colors and pens
  • Window/gadget spacing
  • Scrollbar appearance
  • Button behavior (press/release vs. toggle)
  • Keyboard shortcuts

This means a well-written MUI application looks native on every user's system, regardless of their visual preferences.


Complete Application Example

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/muimaster.h>
#include <libraries/mui.h>

struct Library *MUIMasterBase;

int main(void)
{
    Object *app, *window, *bt_ok, *bt_cancel, *str_name;

    if (!(MUIMasterBase = OpenLibrary("muimaster.library", 19)))
        return 20;

    app = ApplicationObject,
        MUIA_Application_Title,       "MUI Demo",
        MUIA_Application_Version,     "$VER: MUI Demo 1.0",
        MUIA_Application_Description, "Demonstrates MUI basics",
        MUIA_Application_Base,        "MUIDEMO",

        SubWindow, window = WindowObject,
            MUIA_Window_Title, "Enter Your Name",
            MUIA_Window_ID,    MAKE_ID('M','A','I','N'),

            WindowContents, VGroup,
                Child, HGroup,
                    Child, Label2("Name:"),
                    Child, str_name = StringObject,
                        StringFrame,
                        MUIA_String_MaxLen, 80,
                    End,
                End,

                Child, HGroup,
                    Child, bt_ok     = SimpleButton("_OK"),
                    Child, bt_cancel = SimpleButton("_Cancel"),
                End,
            End,
        End,
    End;

    if (!app) {
        CloseLibrary(MUIMasterBase);
        return 20;
    }

    /* Notifications */
    DoMethod(window, MUIM_Notify,
        MUIA_Window_CloseRequest, TRUE,
        app, 2, MUIM_Application_ReturnID,
        MUIV_Application_ReturnID_Quit);

    DoMethod(bt_cancel, MUIM_Notify,
        MUIA_Pressed, FALSE,
        app, 2, MUIM_Application_ReturnID,
        MUIV_Application_ReturnID_Quit);

    DoMethod(bt_ok, MUIM_Notify,
        MUIA_Pressed, FALSE,
        app, 2, MUIM_Application_ReturnID, 1);

    /* Open window */
    set(window, MUIA_Window_Open, TRUE);

    /* Event loop */
    ULONG signals = 0;
    BOOL running = TRUE;

    while (running) {
        ULONG id = DoMethod(app, MUIM_Application_NewInput, &signals);

        switch (id) {
            case MUIV_Application_ReturnID_Quit:
                running = FALSE;
                break;
            case 1:
            {
                char *name = NULL;
                get(str_name, MUIA_String_Contents, &name);
                MUI_Request(app, window, 0, "Hello",
                    "*_OK", "Hello, %s!", name);
                break;
            }
        }

        if (running && signals)
            signals = Wait(signals | SIGBREAKF_CTRL_C);
        if (signals & SIGBREAKF_CTRL_C)
            running = FALSE;
    }

    MUI_DisposeObject(app);
    CloseLibrary(MUIMasterBase);
    return 0;
}

MUI vs Other Frameworks

Feature MUI GadTools BOOPSI ReAction (OS 3.5+)
Layout engine Automatic Manual X/Y Manual Automatic
User prefs Full customization None None ⚠ Limited
OOP model Deep class hierarchy Procedural Basic OOP Class-based
Custom classes MUI_CreateCustomClass N/A MakeClass MakeClass
Notification MUIM_Notify IDCMP only ⚠ OM_NOTIFY Notification
3rd party widgets Huge ecosystem None ⚠ Few ⚠ Some
Availability Aminet (free runtime) ROM (built-in) ROM (built-in) OS 3.5+ only
Platforms AmigaOS 3.x, MorphOS, AROS AmigaOS 2.0+ AmigaOS 2.0+ AmigaOS 3.5+

MCC Aminet Purpose
NList.mcc dev/mui/MCC_NList.lha Multi-column sortable list
TextEditor.mcc dev/mui/MCC_TextEditor.lha Full text editor widget
HTMLview.mcc dev/mui/MCC_HTMLview.lha HTML rendering widget
TheBar.mcc dev/mui/MCC_TheBar.lha Toolbar with icons
BetterString.mcc dev/mui/MCC_BetterString.lha Enhanced string gadget
Lamp.mcc dev/mui/MCC_Lamp.lha Status indicator light
Busy.mcc dev/mui/MCC_Busy.lha Busy/progress animation
Calendar.mcc dev/mui/MCC_Calendar.lha Date picker

References

  • MUI SDK: GitHub amiga-mui/muidev
  • Stefan Stuntz: MUI Autodocs (included in SDK)
  • RKRM: Libraries Manual — BOOPSI chapter (foundation for MUI)
  • Aminet: dev/mui/ — MUI SDK, MCCs, examples
  • MUI runtime: Aminet util/libs/mui38usr.lha

Developer Guide — Sections

Each section below builds on the previous one with working code examples derived from the official MUI 3.8 SDK.

# Section Topic
1 Introduction BOOPSI foundation, Intuition relationship, version history
2 Architecture Full class tree from libraries/mui.h, object model
3 Getting Started Compiler setup, includes, Hello MUI, demo.h pattern
4 Core Concepts Naming conventions (MUIC/MUIM/MUIA), I/S/G attributes, lifecycle
5 Layout System Groups, spacing, frames, custom layout hooks, virtual groups
6 Widgets Overview Text, String, Button, List, Slider, Cycle, Radio, Gauge, Popup
7 Windows & Applications Application object, SubWindow, AppWindows, iconification
8 Menus GadTools NewMenu bridge, dynamic menus, radio/checkmark items
9 Custom Classes Instance data, dispatchers, AskMinMax, Draw, rendering macros
10 Events & Notifications Input loops, Return IDs, hooks, register conventions
11 Advanced Patterns Drag & drop, subtasks, settings persistence, BOOPSI integration
12 Reference Snippets Copy-paste templates for every common pattern

Source material: Official MUI 3.8 developer release — 66 autodocs, 22 C examples, libraries/mui.h