From 94a3ad1614d93d83f4e9931ea75d63711f42542d Mon Sep 17 00:00:00 2001 From: Ilia Sharin Date: Thu, 23 Apr 2026 16:46:45 -0400 Subject: [PATCH] =?UTF-8?q?doc:=20MUI=20framework=20documentation=20?= =?UTF-8?q?=E2=80=94=20whitepaper=20overview,=20SDK-derived=20architecture?= =?UTF-8?q?,=20layout=20mockups?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- 09_intuition/README.md | 1 + 09_intuition/frameworks/README.md | 24 + .../frameworks/mui/01-introduction.md | 133 ++ .../frameworks/mui/02-architecture.md | 529 ++++++++ .../frameworks/mui/03-getting-started.md | 273 ++++ .../frameworks/mui/04-core-concepts.md | 235 ++++ .../frameworks/mui/05-layout-system.md | 478 +++++++ .../frameworks/mui/06-widgets-overview.md | 374 ++++++ .../mui/07-windows-and-applications.md | 213 +++ 09_intuition/frameworks/mui/08-menus.md | 211 +++ .../frameworks/mui/09-custom-classes.md | 292 +++++ .../mui/10-events-and-notifications.md | 278 ++++ .../frameworks/mui/11-advanced-patterns.md | 282 ++++ .../frameworks/mui/12-reference-snippets.md | 428 ++++++ 09_intuition/frameworks/mui/README.md | 1160 +++++++++++++++++ 15 files changed, 4911 insertions(+) create mode 100644 09_intuition/frameworks/README.md create mode 100644 09_intuition/frameworks/mui/01-introduction.md create mode 100644 09_intuition/frameworks/mui/02-architecture.md create mode 100644 09_intuition/frameworks/mui/03-getting-started.md create mode 100644 09_intuition/frameworks/mui/04-core-concepts.md create mode 100644 09_intuition/frameworks/mui/05-layout-system.md create mode 100644 09_intuition/frameworks/mui/06-widgets-overview.md create mode 100644 09_intuition/frameworks/mui/07-windows-and-applications.md create mode 100644 09_intuition/frameworks/mui/08-menus.md create mode 100644 09_intuition/frameworks/mui/09-custom-classes.md create mode 100644 09_intuition/frameworks/mui/10-events-and-notifications.md create mode 100644 09_intuition/frameworks/mui/11-advanced-patterns.md create mode 100644 09_intuition/frameworks/mui/12-reference-snippets.md create mode 100644 09_intuition/frameworks/mui/README.md diff --git a/09_intuition/README.md b/09_intuition/README.md index ac4d314..2f00653 100644 --- a/09_intuition/README.md +++ b/09_intuition/README.md @@ -17,6 +17,7 @@ Intuition is the AmigaOS windowing system and user interface manager. It sits be | [idcmp.md](idcmp.md) | IDCMP message classes and IntuiMessage | | [boopsi.md](boopsi.md) | BOOPSI object-oriented gadget system | | [input_events.md](input_events.md) | InputEvent, input.device, commodities | +| **[frameworks/](frameworks/)** | **GUI Frameworks: MUI, ReAction, BGUI** | --- diff --git a/09_intuition/frameworks/README.md b/09_intuition/frameworks/README.md new file mode 100644 index 0000000..c5f7327 --- /dev/null +++ b/09_intuition/frameworks/README.md @@ -0,0 +1,24 @@ +[← Home](../../README.md) · [Intuition](../README.md) + +# GUI Frameworks + +AmigaOS spawned several GUI frameworks beyond the built-in GadTools/BOOPSI system. These frameworks provide layout management, user customization, and richer widget sets — features that the base Intuition API does not offer. + +## Framework Index + +| File | Framework | Era | Description | +|---|---|---|---| +| [mui/](mui/) | **MUI** (Magic User Interface) | 1993– | De-facto standard. Architecture, developer guide, code examples | +| *reaction.md* | **ReAction** / ClassAct | 1997– | Hyperion's official OS 3.5+ GUI toolkit | +| *bgui.md* | **BGUI** | 1994– | Lightweight BOOPSI-based layout system | + +## Comparison + +| Feature | MUI | ReAction | BGUI | GadTools | +|---|---|---|---|---| +| Layout engine | ✅ Constraint-based | ✅ Constraint-based | ✅ Basic | ❌ Manual | +| User preferences | ✅ Full | ⚠ Limited | ❌ None | ❌ None | +| Custom classes | ✅ MCC ecosystem | ✅ MakeClass | ✅ MakeClass | ❌ N/A | +| Availability | Aminet (free runtime) | OS 3.5+ bundled | Aminet (free) | ROM built-in | +| Platforms | AmigaOS, MorphOS, AROS | AmigaOS 3.5+ only | AmigaOS 2.0+ | AmigaOS 2.0+ | +| Adoption | Very high | Moderate | Low | Universal | diff --git a/09_intuition/frameworks/mui/01-introduction.md b/09_intuition/frameworks/mui/01-introduction.md new file mode 100644 index 0000000..6332690 --- /dev/null +++ b/09_intuition/frameworks/mui/01-introduction.md @@ -0,0 +1,133 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Introduction + +## What is MUI? + +MUI (Magic User Interface) is a system for generating and maintaining graphical user interfaces on Amiga computers. Created by Stefan Stuntz between 1992 and 1997, it provides an object-oriented framework built on top of AmigaOS's native BOOPSI (Basic Object-Oriented Programming System for Intuition) subsystem. + +MUI's defining characteristic is that end users can customize the appearance of any MUI application through a central preferences program. Developers define the structure and behavior; users control the visual style. + +## Key Features + +- **Object-oriented design** - Every UI element is an object with attributes and methods +- **BOOPSI integration** - Inherits from and extends AmigaOS's native class system +- **User customization** - Skinnable via the MUI preferences program without code changes +- **Layout management** - Automatic and custom layout systems handle widget positioning +- **Notification system** - Declarative event binding between objects +- **Comprehensive widget set** - Text, strings, lists, sliders, buttons, gauges, palettes, and more +- **Custom class support** - Developers can create reusable components by subclassing existing classes + +## MUI in the Amiga Software Stack + +``` ++---------------------------+ +| Your Application | ++---------------------------+ +| MUI | +| (classes, layout, events)| ++---------------------------+ +| BOOPSI | +| (Intuition classes) | ++---------------------------+ +| Intuition | +| (windows, input) | ++---------------------------+ +| Graphics | +| (drawing, rastports) | ++---------------------------+ +| Exec | +| (tasks, signals) | ++---------------------------+ +``` + +### BOOPSI (Basic Object-Oriented Programming System for Intuition) + +BOOPSI is AmigaOS's native object system introduced in AmigaOS 2.0 (V37). It provides the foundational mechanisms that MUI extends: + +- **Class registry** - BOOPSI maintains a system-wide database of classes via `AddClass()` and `FindClass()` +- **Object instantiation** - Objects are created with `NewObjectA()` using tag-based attribute lists +- **Method dispatch** - Methods are invoked through `DoMethod()`, which routes calls through a class dispatcher +- **Inheritance** - Classes specify a superclass and inherit methods and default attributes + +MUI's entire class hierarchy (Notify, Area, Group, Window, Application, etc.) is built as a BOOPSI class tree. Every MUI object is a BOOPSI object. When you call `DoMethod(obj, MUIM_Draw, ...)` you are using BOOPSI dispatch. When MUI creates objects internally, it calls `NewObjectA()` against its own class IDs. The `libraries/mui.h` header defines `MUIC_*` constants that resolve to BOOPSI class names registered with Intuition. + +MUI adds three major capabilities on top of BOOPSI: + +1. **Layout engine** - BOOPSI has no concept of automatic layout; MUI's Group classes compute sizes and positions +2. **Notification system** - BOOPSI has no built-in attribute monitoring; MUI's `MUIM_Notify` adds declarative event binding +3. **Extended attribute system** - MUI standardizes attribute naming (MUIA_*) and documents I/S/G flags + +### Intuition + +Intuition is AmigaOS's windowing and input subsystem. It is the layer below BOOPSI and the direct interface to the display hardware through the Graphics library. + +MUI's relationship to Intuition: + +- **Windows** - MUI's Window class creates and manages an Intuition window internally. You never call `OpenWindowTagList()` directly; MUI handles it when you set `MUIA_Window_Open` to `TRUE`. The `WindowObject` macro configures Intuition window properties (title, ID, screen) through MUI attributes. +- **Input handling** - Intuition captures mouse and keyboard events and routes them through its IDCMP (Intuition Direct Communication Message Port) system. MUI intercepts these events at the Window level and dispatches them to the appropriate widget via `MUIM_HandleEvent` or attribute changes. +- **Gadgets** - Intuition provides basic gadget types (boolean, string, proportional). MUI's Gadget subclass wraps Intuition gadgets but replaces their rendering and behavior with MUI's own system. The Boopsi class allows embedding native BOOPSI gadgets inside MUI layouts. +- **Screens** - MUI windows open on Intuition screens. You can specify a public screen with `MUIA_Window_Screen` or let MUI use the default Workbench screen. +- **RastPorts** - When a custom class renders in `MUIM_Draw`, it draws into an Intuition RastPort obtained via MUI's `_rp(obj)` macro. MUI manages clipping, layer locking, and damage regions. + +Key Intuition structures you encounter when reading MUI code: + +| Structure | Role in MUI | +|-----------|-------------| +| `struct Window` | Underlying Intuition window managed by MUI Window class | +| `struct RastPort` | Drawing context for custom rendering | +| `struct DrawInfo` | Pen colors and fonts via `_dri(obj)` | +| `struct Screen` | Display surface for windows | +| `struct Gadget` | Base structure for Intuition gadgets | + +### GadTools + +GadTools is AmigaOS's higher-level widget library built on top of Intuition gadgets. It provides standardized buttons, checkboxes, sliders, and menus with a consistent look. + +MUI's relationship to GadTools: + +- **Menu compatibility** - MUI can consume GadTools `NewMenu` structures directly via `MUI_MakeObject(MUIO_MenustripNM, newmenu, 0)`. This parses the GadTools menu definition and converts it into MUI's own Menustrip/Menu/Menuitem object tree. This compatibility layer made it easy for existing applications to adopt MUI without rewriting menu code. +- **Gadget avoidance** - MUI generally does not use GadTools gadgets for its own widgets. MUI's Area, Text, String, and Button classes are fully independent implementations with their own rendering and input handling. This is why MUI widgets can be skinned through the MUI preferences program while GadTools gadgets have a fixed appearance. +- **Requesters** - MUI's Popasl class can invoke GadTools ASL (Amiga Standard Library) file and font requesters as popup subwindows. +- **Shared concepts** - Both GadTools and MUI use tag-based construction, so developers familiar with `GTTagList` patterns find MUI's `TAG_DONE` object creation intuitive. + +The official MUI examples demonstrate the GadTools bridge explicitly in `Menus.c`, where a conventional `NewMenu` array is passed to MUI and then manipulated with MUI notifications. + +### Summary of Dependencies + +| Layer | What MUI Uses From It | What MUI Adds | +|-------|----------------------|---------------| +| **Exec** | Tasks, signals, memory allocation, message ports | Application event loop integration | +| **Graphics** | RastPort drawing, pens, regions, fonts | Automatic clipping and damage handling | +| **Intuition** | Windows, screens, IDCMP input, layers | Object-oriented window management | +| **BOOPSI** | Class registry, `NewObjectA()`, `DoMethodA()` | Layout, notification, extended attributes | +| **GadTools** | `NewMenu` structures, ASL requesters | Independent widget rendering and skinning | + +## Version History + +The material in this bootcamp corresponds to **MUI 3.8**, the developer release. Key version details: + +- `muimaster.library` name and version requirements: + - Minimum version: **V11** (`MUIMASTER_VMIN`) + - Latest version at release: **V19** (`MUIMASTER_VLATEST`) +- Some macros in `libraries/mui.h` require V11 or above +- The developer package requires AmigaOS 2.0 (v37.175) include files and linker libraries + +## Distribution and Licensing + +MUI was distributed as shareware. The developer package (`mui38dev.lha`) contained autodocs, C examples, includes, and FD files. The user package (`mui38usr.lha`) contained the runtime libraries, preferences program, and demo applications. + +Applications using MUI were encouraged to include a credit file indicating MUI usage. + +## Why MUI Still Matters + +For retrocomputing, emulation, and FPGA platforms like MiSTer, MUI remains relevant because: + +- It represents one of the most sophisticated GUI toolkits available on classic Amiga hardware +- Its source code and examples demonstrate mature 1990s C and BOOPSI patterns +- Many Amiga applications were built with MUI and understanding it helps with porting and preservation +- The architecture influenced later GUI frameworks in its separation of structure from presentation + +--- + +Next: [Architecture](02-architecture.md) diff --git a/09_intuition/frameworks/mui/02-architecture.md b/09_intuition/frameworks/mui/02-architecture.md new file mode 100644 index 0000000..2e2aad1 --- /dev/null +++ b/09_intuition/frameworks/mui/02-architecture.md @@ -0,0 +1,529 @@ +[← 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
zero or more Window children"] + W1["Window 1"] + W2["Window 2"] + ROOT1["Group
root object"] + ROOT2["Group
root object"] + 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
Dispatcher"] + DISP_MY -->|"handled"| RESULT["Return value"] + DISP_MY -->|"DoSuperMethodA()"| DISP_AREA["Area
Dispatcher"] + DISP_AREA -->|"DoSuperMethodA()"| DISP_NOTIFY["Notify
Dispatcher"] + DISP_NOTIFY -->|"DoSuperMethodA()"| DISP_ROOT["rootclass
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
Text weight=100, Button weight=100
→ 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
(mouse, keyboard)"] + APP["MUIM_Application_NewInput"] + DISPATCH["MUI dispatches to
active/relevant objects"] + HANDLE["MUIM_HandleInput
(on each object)"] + NOTIFY["Notifications fire
(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_.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) diff --git a/09_intuition/frameworks/mui/03-getting-started.md b/09_intuition/frameworks/mui/03-getting-started.md new file mode 100644 index 0000000..20332d4 --- /dev/null +++ b/09_intuition/frameworks/mui/03-getting-started.md @@ -0,0 +1,273 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Getting Started + +## Required Includes + +Every MUI application needs at minimum: + +```c +#include +``` + +Depending on your compiler, you also need one of the following for function prototypes: + +```c +/* For SAS/C, DICE, and other traditional compilers */ +#include +#include + +/* For GCC */ +#include +``` + +A typical MUI program also includes standard AmigaOS headers: + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +``` + +## Opening the Library + +Before using any MUI functions, open `muimaster.library`: + +```c +#define MUIMASTER_NAME "muimaster.library" +#define MUIMASTER_VMIN 11 + +struct Library *MUIMasterBase; + +if (!(MUIMasterBase = OpenLibrary(MUIMASTER_NAME, MUIMASTER_VMIN))) +{ + /* handle error */ +} +``` + +The minimum required version is **11**. Some macros in `libraries/mui.h` require V11 or above. + +When your application exits, close the library: + +```c +CloseLibrary(MUIMasterBase); +``` + +## Minimal "Hello MUI" Application + +Here is the smallest possible MUI application that opens a window with some text: + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Library *MUIMasterBase; +extern struct Library *SysBase; + +int main(int argc, char *argv[]) +{ + APTR app, window; + + if (!(MUIMasterBase = OpenLibrary(MUIMASTER_NAME, MUIMASTER_VMIN))) + { + printf("Failed to open %s\n", MUIMASTER_NAME); + return 20; + } + + app = ApplicationObject, + MUIA_Application_Title , "HelloMUI", + MUIA_Application_Version , "$VER: HelloMUI 1.0", + MUIA_Application_Copyright , "Your Name", + MUIA_Application_Author , "Your Name", + MUIA_Application_Description, "A minimal MUI application", + MUIA_Application_Base , "HELLOMUI", + + SubWindow, window = WindowObject, + MUIA_Window_Title, "Hello MUI", + MUIA_Window_ID , MAKE_ID('H','L','O','1'), + + WindowContents, VGroup, + Child, TextObject, + MUIA_Text_Contents, "\33cHello, MUI World!", + End, + End, + + End, + End; + + if (!app) + { + printf("Failed to create Application.\n"); + CloseLibrary(MUIMasterBase); + return 20; + } + + /* Close window when requested */ + 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); + CloseLibrary(MUIMasterBase); + + return 0; +} +``` + +## The demo.h Pattern + +The official MUI examples use a common header file called `demo.h` that centralizes includes and provides helper functions. This is a recommended pattern for your own projects: + +```c +/* demo.h - common includes and helpers for MUI applications */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __GNUC__ +#include +#else +#include +#endif + +#include +#include +#include + +extern struct Library *SysBase, *IntuitionBase, *UtilityBase; +extern struct Library *GfxBase, *DOSBase, *IconBase; +struct Library *MUIMasterBase; + +/* A fail function that cleans up and exits */ +static VOID fail(APTR app, char *str) +{ + if (app) + MUI_DisposeObject(app); + +#ifndef _DCC + if (MUIMasterBase) + CloseLibrary(MUIMasterBase); +#endif + + if (str) + { + puts(str); + exit(20); + } + exit(0); +} + +/* Open muimaster.library */ +static VOID init(VOID) +{ +#ifndef _DCC + if (!(MUIMasterBase = OpenLibrary(MUIMASTER_NAME, MUIMASTER_VMIN))) + fail(NULL, "Failed to open " MUIMASTER_NAME "."); +#endif +} +``` + +## Compiler Setup + +### SAS/C + +The official examples were written for SAS/C. Key settings: + +- Stack size: at least 8192 bytes (`LONG __stack = 8192;`) +- Include pragmas for library calls +- Use `__saveds` and `__asm` register keywords for dispatcher functions + +### DICE + +DICE requires slightly different macros: + +```c +#define REG(x) __ ## x +#define ASM +#define SAVEDS __geta4 +``` + +DICE also handles library opening differently; the examples use conditional compilation for DICE. + +### GCC + +GCC uses inline headers rather than pragmas: + +```c +#include +``` + +GCC does not need `__asm` or `__saveds` for dispatcher functions. + +### MAXON C + +Similar to GCC in that `ASM` and `SAVEDS` are defined as empty: + +```c +#define ASM +#define SAVEDS +``` + +## Build Workflow + +A typical build for SAS/C: + +``` +sc LINK AppWindow.c demo.h +``` + +For GCC cross-compilation: + +``` +m68k-amigaos-gcc -o AppWindow AppWindow.c -lmuimaster +``` + +Ensure your NDK (Native Developer Kit) include paths are set correctly so that `libraries/mui.h` and the clib/inline headers are found. + +--- + +Previous: [Architecture](02-architecture.md) +Next: [Core Concepts](04-core-concepts.md) diff --git a/09_intuition/frameworks/mui/04-core-concepts.md b/09_intuition/frameworks/mui/04-core-concepts.md new file mode 100644 index 0000000..a63476c --- /dev/null +++ b/09_intuition/frameworks/mui/04-core-concepts.md @@ -0,0 +1,235 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Core Concepts + +## Naming Conventions + +MUI uses a strict prefix system defined in `libraries/mui.h`. Every identifier type has its own prefix: + +| Prefix | Meaning | Example | +|--------|---------|---------| +| `MUIC_` | Class name | `MUIC_Area`, `MUIC_Window` | +| `MUIM_` | Method | `MUIM_Draw`, `MUIM_AskMinMax` | +| `MUIP_` | Method parameter structure | `MUIP_Draw`, `MUIP_AskMinMax` | +| `MUIV_` | Special method/attribute value | `MUIV_Application_ReturnID_Quit` | +| `MUIA_` | Attribute | `MUIA_Window_Title` | +| `MUIE_` | Error return code | `MUIE_OutOfMemory` | +| `MUII_` | Standard MUI image | `MUII_BACKGROUND`, `MUII_SHADOW` | +| `MUIX_` | Control code for text strings | `MUIX_C` (center), `MUIX_B` (bold) | +| `MUIO_` | Object type for `MUI_MakeObject()` | `MUIO_Button`, `MUIO_HBar` | + +Learning these prefixes makes reading MUI code significantly easier. + +## Attributes + +Attributes are the properties of MUI objects. Each attribute definition in `libraries/mui.h` is followed by a comment with the letters **I**, **S**, and/or **G**: + +- **I** - Can be specified at object creation time +- **S** - Can be changed later with `SetAttrs()` or the `set()` macro +- **G** - Can be read with `GetAttr()` or the `get()` macro + +Example: + +```c +#define MUIA_Window_Title 0x8042c21d /* ISG */ +``` + +This means `MUIA_Window_Title` can be set at creation, changed later, and queried at any time. + +### Setting Attributes + +At creation time, attributes are passed as tag-value pairs: + +```c +app = ApplicationObject, + MUIA_Application_Title, "MyApp", + MUIA_Application_Base , "MYAPP", + TAG_DONE; +``` + +After creation, use `set()`: + +```c +set(window, MUIA_Window_Open, TRUE); +``` + +Or use `SetAttrs()` directly: + +```c +SetAttrs(window, MUIA_Window_Open, TRUE, TAG_DONE); +``` + +### Getting Attributes + +```c +ULONG is_open; +get(window, MUIA_Window_Open, &is_open); +``` + +## Object Creation Macros + +MUI provides convenience macros that look like function calls but expand into `NewObject()` calls with the appropriate class and tags. These macros make code readable and maintainable. + +### Common Creation Macros + +```c +/* Application and Window */ +ApplicationObject, ... , TAG_DONE; +WindowObject, ... , TAG_DONE; + +/* Groups */ +VGroup, ... , End; +HGroup, ... , End; +GroupObject, ... , End; + +/* Widgets */ +TextObject, ... , End; +StringObject, ... , End; +ListviewObject, ... , End; +ListObject, ... , End; +SliderObject, ... , End; +CycleObject, ... , End; +RadioObject, ... , End; +GaugeObject, ... , End; +ScaleObject, ... , End; +ImageObject, ... , End; +BitmapObject, ... , End; +ColorfieldObject, ... , End; +PaletteObject, ... , End; + +/* Popups */ +PopstringObject, ... , End; +PopaslObject, ... , End; +PopobjectObject, ... , End; +PoplistObject, ... , End; + +/* Special helpers */ +SimpleButton("Label") +HSpace(x) +VSpace(x) +``` + +The pattern is consistent: the macro name corresponds to the class, attributes follow as tag-value pairs, and the object is terminated with `End` or `TAG_DONE`. + +## Tag-Based Construction + +MUI objects are created using the Amiga tag system. Tags are 32-bit values where the upper bits identify the attribute and the lower bits hold the value (or a pointer). + +```c +app = ApplicationObject, + MUIA_Application_Title , "Demo", + MUIA_Application_Version , "$VER: Demo 1.0", + MUIA_Application_Copyright , "1997 Author", + MUIA_Application_Author , "Author", + MUIA_Application_Description, "A demo program", + MUIA_Application_Base , "DEMO", + SubWindow, window = WindowObject, + MUIA_Window_Title, "Demo Window", + MUIA_Window_ID , MAKE_ID('D','E','M','O'), + WindowContents, VGroup, + Child, TextObject, + TextFrame, + MUIA_Background, MUII_TextBack, + MUIA_Text_Contents, "Hello", + End, + End, + End, + End; +``` + +Notice how nesting works: `ApplicationObject` contains `SubWindow`, which contains `WindowObject`, which contains `WindowContents`, which contains a `VGroup`, which contains `Child` widgets. + +## Methods + +Methods are operations you perform on objects. The universal way to invoke a method is `DoMethod()`: + +```c +DoMethod(obj, MUIM_MethodName, arg1, arg2, ...); +``` + +### Common Methods + +| Method | Purpose | +|--------|---------| +| `MUIM_Application_NewInput` | Process input events (preferred loop) | +| `MUIM_Application_Input` | Process input events (legacy loop) | +| `MUIM_Application_ReturnID` | Trigger a return ID | +| `MUIM_Notify` | Set up attribute notifications | +| `MUIM_CallHook` | Invoke a callback hook | +| `MUIM_Set` | Set an attribute (method form) | +| `MUIM_Get` | Get an attribute (method form) | +| `MUIM_Draw` | Render an object | +| `MUIM_AskMinMax` | Query minimum/maximum sizes | + +## Object Lifecycle + +### Creation + +Objects are created with the macro constructors. The constructor returns a pointer or `NULL` on failure. + +```c +APTR obj = TextObject, + MUIA_Text_Contents, "Hello", + End; + +if (!obj) + /* handle error */; +``` + +### Usage + +Objects are manipulated through attributes and methods. They exist within a parent container (usually a Group) which manages their layout and visibility. + +### Disposal + +When an object is no longer needed, dispose it: + +```c +MUI_DisposeObject(obj); +``` + +For the top-level Application object, disposing it automatically disposes all child Windows and their contents. This is the normal shutdown pattern: + +```c +MUI_DisposeObject(app); /* disposes entire tree */ +``` + +For custom classes, you must also delete the class itself: + +```c +MUI_DeleteCustomClass(mcc); +``` + +## The `Child` Keyword + +When adding widgets to a Group, the `Child` tag is used to introduce each child object: + +```c +VGroup, + Child, widget1, + Child, widget2, + Child, HGroup, + Child, subwidget1, + Child, subwidget2, + End, + End, +``` + +`Child` is not a macro for a class; it is a tag that tells the Group class to add the following object to its internal list of children. + +## Special Attribute Values + +MUI defines several special values used across multiple attributes: + +| Value | Meaning | +|-------|---------| +| `MUIV_EveryTime` | Trigger notification on any attribute change | +| `MUIV_TriggerValue` | Pass the triggering value as an argument | +| `MUIV_Notification_Value` | Use the value from the notification setup | + +These are used primarily with `MUIM_Notify` to create flexible event bindings. + +--- + +Previous: [Getting Started](03-getting-started.md) +Next: [Layout System](05-layout-system.md) diff --git a/09_intuition/frameworks/mui/05-layout-system.md b/09_intuition/frameworks/mui/05-layout-system.md new file mode 100644 index 0000000..bd27f45 --- /dev/null +++ b/09_intuition/frameworks/mui/05-layout-system.md @@ -0,0 +1,478 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Layout System + +## Overview + +MUI handles widget positioning automatically through its Group classes. Rather than specifying absolute coordinates, you declare the structure of your UI and MUI computes sizes and positions based on: + +- Each widget's minimum, default, and maximum size requirements +- Group direction (horizontal or vertical) +- Spacing, framing, and balancing hints +- User preferences from the MUI settings program + +## Group Classes + +Groups are containers that arrange their children. There are three primary group macros: + +| Macro | Direction | Description | +|-------|-----------|-------------| +| `VGroup` | Vertical | Stacks children top-to-bottom | +| `HGroup` | Horizontal | Stacks children left-to-right | +| `GroupObject` | Configurable | Defaults to vertical; can be changed with `MUIA_Group_Horiz` | + +### Basic Vertical Layout + +```c +WindowContents, VGroup, + Child, TextObject, + MUIA_Text_Contents, "First line", + End, + Child, TextObject, + MUIA_Text_Contents, "Second line", + End, + Child, StringObject, + StringFrame, + MUIA_String_Contents, "Input here", + End, + End, +``` + +**Visual result:** + +```mermaid +graph TB + T1["Text: First line"] --> T2["Text: Second line"] --> S1["String: Input here"] + + style T1 fill:#e8f4fd,stroke:#2196f3,color:#333 + style T2 fill:#e8f4fd,stroke:#2196f3,color:#333 + style S1 fill:#fff3e0,stroke:#ff9800,color:#333 + linkStyle default stroke:#ccc,stroke-width:1px +``` + +> VGroup stacks children **top-to-bottom**. Each child gets the full window width. + +### Basic Horizontal Layout + +```c +WindowContents, HGroup, + Child, SimpleButton("OK"), + Child, SimpleButton("Cancel"), + End, +``` + +**Visual result:** + +```mermaid +graph LR + OK["OK"] --- CANCEL["Cancel"] + + style OK fill:#e8f5e9,stroke:#4caf50,color:#333 + style CANCEL fill:#ffebee,stroke:#f44336,color:#333 +``` + +> HGroup arranges children **left-to-right**. Both buttons share available width equally. + +### Nested Groups + +Complex layouts are built by nesting groups: + +```c +WindowContents, VGroup, + Child, TextObject, + TextFrame, + MUIA_Background, MUII_TextBack, + MUIA_Text_Contents, "\33cTitle", + End, + + Child, HGroup, + Child, VGroup, + Child, SimpleButton("A"), + Child, SimpleButton("B"), + End, + Child, VGroup, + Child, SimpleButton("C"), + Child, SimpleButton("D"), + End, + End, + + Child, HGroup, + Child, HSpace(0), + Child, SimpleButton("Close"), + Child, HSpace(0), + End, + + End, +``` + +**Visual result:** + +```mermaid +graph TB + TITLE["Title"] --> HGROUP["HGroup"] + HGROUP --> A["A"] & C["C"] + A --> B["B"] + C --> D["D"] + TITLE --> BOTTOM["HGroup"] + BOTTOM --> CLOSE["Close"] + + style TITLE fill:#e8f4fd,stroke:#2196f3,color:#333 + style A fill:#e8f5e9,stroke:#4caf50,color:#333 + style B fill:#e8f5e9,stroke:#4caf50,color:#333 + style C fill:#e8f5e9,stroke:#4caf50,color:#333 + style D fill:#e8f5e9,stroke:#4caf50,color:#333 + style CLOSE fill:#fff3e0,stroke:#ff9800,color:#333 + style HGROUP fill:#f5f5f5,stroke:#bdbdbd,color:#333 + style BOTTOM fill:#f5f5f5,stroke:#bdbdbd,color:#333 + linkStyle default stroke:#ccc,stroke-width:1px +``` + +> The outer VGroup stacks Title → HGroup → Close vertically. The HGroup lays out VGroup1 and VGroup2 side-by-side. A and B stack within VGroup1; C and D within VGroup2. + +## Spacing Objects + +### Rectangle + +A Rectangle is an invisible spacing object. Use it to create fixed or flexible gaps. + +```c +Child, RectangleObject, + MUIA_Rectangle_HMin, 20, + MUIA_Rectangle_VMin, 10, + End, +``` + +### Balance + +A Balance object is a draggable separator that divides space between adjacent children. Users can drag it to resize the areas. + +```c +Child, ListviewObject, + MUIA_Listview_List, myList, + End, +Child, BalanceObject, End, +Child, TextObject, + MUIA_Text_Contents, "Details pane", + End, +``` + +### HSpace and VSpace + +Quick macros for adding flexible spacing: + +```c +Child, HSpace(0), /* expands to fill available horizontal space */ +Child, VSpace(0), /* expands to fill available vertical space */ +``` + +Use these to push widgets to the edges or center them. + +## Frames and Backgrounds + +Frames are visual borders around objects. MUI provides frame macros that set both the frame style and background: + +| Macro | Appearance | +|-------|------------| +| `TextFrame` | Standard text field frame | +| `StringFrame` | Input field frame | +| `GroupFrame` | Group border with optional title | +| `ReadListFrame` | Listview frame | +| `ButtonFrame` | Button frame | + +Usage: + +```c +Child, TextObject, + TextFrame, + MUIA_Background, MUII_TextBack, + MUIA_Text_Contents, "Framed text", + End, +``` + +Backgrounds can be specified with standard MUI images: + +| Constant | Meaning | +|----------|---------| +| `MUII_BACKGROUND` | Standard background | +| `MUII_SHADOW` | Shadow color | +| `MUII_SHINE` | Highlight color | +| `MUII_FILL` | Fill pattern | +| `MUII_TEXTBACK` | Text background | +| `MUII_BUTTONBACK` | Button background | + +## Group Attributes + +### Same Size + +Force all children to have the same size: + +```c +HGroup, MUIA_Group_SameSize, TRUE, + Child, SimpleButton("Short"), + Child, SimpleButton("A much longer label"), + End, +``` + +Both buttons will expand to fit the widest label. + +### Columns + +Arrange children in a grid with a fixed number of columns: + +```c +GroupObject, MUIA_Group_Columns, 3, + Child, SimpleButton("1"), + Child, SimpleButton("2"), + Child, SimpleButton("3"), + Child, SimpleButton("4"), + Child, SimpleButton("5"), + End, +``` + +**Visual result (3-column grid, 5 children):** + +```mermaid +graph TB + subgraph "ColGroup 3" + direction TB + subgraph "Row 1" + direction LR + B1["1"] + B2["2"] + B3["3"] + end + subgraph "Row 2" + direction LR + B4["4"] + B5["5"] + EMPTY[" "] + end + end + + style B1 fill:#e8f5e9,stroke:#4caf50,color:#333 + style B2 fill:#e8f5e9,stroke:#4caf50,color:#333 + style B3 fill:#e8f5e9,stroke:#4caf50,color:#333 + style B4 fill:#e8f5e9,stroke:#4caf50,color:#333 + style B5 fill:#e8f5e9,stroke:#4caf50,color:#333 + style EMPTY fill:none,stroke:none +``` + +### Horiz + +Make a Group horizontal instead of vertical: + +```c +GroupObject, MUIA_Group_Horiz, TRUE, + ... + End, +``` + +## Custom Layout Hooks + +For complex layouts that MUI's built-in groups cannot express, you can provide a custom layout hook. The hook receives layout messages and positions children manually. + +### Hook Structure + +```c +SAVEDS ULONG __asm LayoutFunc(REG(a0) struct Hook *h, + REG(a2) Object *obj, + REG(a1) struct MUI_LayoutMsg *lm) +{ + switch (lm->lm_Type) + { + case MUILM_MINMAX: + /* Calculate and return min/max/default sizes */ + lm->lm_MinMax.MinWidth = ...; + lm->lm_MinMax.MinHeight = ...; + lm->lm_MinMax.DefWidth = ...; + lm->lm_MinMax.DefHeight = ...; + lm->lm_MinMax.MaxWidth = MUI_MAXMAX; + lm->lm_MinMax.MaxHeight = MUI_MAXMAX; + return 0; + + case MUILM_LAYOUT: + /* Position each child with MUI_Layout() */ + Object *cstate = (Object *)lm->lm_Children->mlh_Head; + Object *child; + + while (child = NextObject(&cstate)) + { + if (!MUI_Layout(child, left, top, width, height, 0)) + return FALSE; + } + return TRUE; + } + return MUILM_UNKNOWN; +} +``` + +### Attaching the Hook + +```c +static struct Hook LayoutHook = { {0,0}, LayoutFunc, NULL, NULL }; + +... + +GroupObject, + MUIA_Group_LayoutHook, &LayoutHook, + Child, ..., + Child, ..., + End, +``` + +### Important Notes + +- In `MUILM_MINMAX`, the children's min/max values are already calculated. You can use them to derive your group's size. +- In `MUILM_LAYOUT`, you must call `MUI_Layout()` for every child. The rectangle you are given is `(0, 0, width-1, height-1)`. +- Return `MUILM_UNKNOWN` for any message type you do not handle. +- Avoid errors during layout; MUI does not handle them gracefully. + +## Virtual Groups and Scroll Groups + +When content exceeds available space, use a virtual group with scrollbars: + +```c +Child, ScrollgroupObject, + MUIA_Scrollgroup_Contents, VirtgroupObject, + Child, /* lots of widgets */, + Child, /* lots of widgets */, + End, + End, +``` + +The `Virtgroup` creates a virtual canvas. The `Scrollgroup` adds scrollbars as needed. + +**Scrollgroup structure:** + +```mermaid +graph TB + subgraph "ScrollgroupObject" + direction TB + subgraph "Visible viewport" + V1["Widget 1"] + V2["Widget 2"] + V3["Widget 3"] + end + VSCROLL["▲ Scrollbar ▼"] + subgraph "Hidden (below viewport)" + V4["Widget 4"] + V5["Widget 5"] + V6["..."] + end + end + + style V1 fill:#e8f5e9,stroke:#4caf50,color:#333 + style V2 fill:#e8f5e9,stroke:#4caf50,color:#333 + style V3 fill:#e8f5e9,stroke:#4caf50,color:#333 + style V4 fill:#f5f5f5,stroke:#bdbdbd,color:#999 + style V5 fill:#f5f5f5,stroke:#bdbdbd,color:#999 + style V6 fill:#f5f5f5,stroke:#bdbdbd,color:#999 + style VSCROLL fill:#fff3e0,stroke:#ff9800,color:#333 +``` + +## Layout Algorithm + +```mermaid +sequenceDiagram + participant App as Application + participant Win as Window + participant Root as Root Group + participant C1 as Child 1 + participant C2 as Child 2 + + Note over App,C2: Pass 1 — AskMinMax (bottom-up) + Root->>C1: MUIM_AskMinMax + C1-->>Root: min=40, def=80, max=200 + Root->>C2: MUIM_AskMinMax + C2-->>Root: min=60, def=100, max=300 + Root-->>Win: group min=100, def=180, max=500 + + Note over App,C2: Window opens at calculated size + Win->>Win: OpenWindow(def_width=180) + + Note over App,C2: Pass 2 — Layout (top-down) + Win->>Root: Layout(0, 0, 180, h) + Root->>Root: Distribute 180px by weight + Root->>C1: MUI_Layout(x=0, w=80) + Root->>C2: MUI_Layout(x=82, w=98) + + Note over App,C2: Pass 3 — Draw + Root->>C1: MUIM_Draw + Root->>C2: MUIM_Draw +``` + +### On Window Resize + +```mermaid +sequenceDiagram + participant User + participant Win as Window + participant Root as Root Group + participant C1 as Child 1 + participant C2 as Child 2 + + User->>Win: Drags size gadget + Win->>C1: MUIM_Hide + Win->>C2: MUIM_Hide + Win->>Root: Layout(0, 0, new_w, new_h) + Root->>Root: Redistribute space + Root->>C1: MUI_Layout(new rect) + Root->>C2: MUI_Layout(new rect) + Win->>C1: MUIM_Show + MUIM_Draw + Win->>C2: MUIM_Show + MUIM_Draw +``` + +### File Requester — Real-World Layout Example + +From the official MUI SDK, a file requester demonstrates nested group layout: + +```mermaid +graph TB + subgraph "Window (VGroup)" + direction TB + subgraph "HGroup — Lists" + direction LR + FILELIST["File List
(Listview)
C/
Classes/
Devs/
..."] + DEVLIST["Device List
(Listview)
dh0:
dh1:
df0:
ram:"] + end + PATH["Path: ___________________"] + FILE["File: ___________________"] + subgraph "HGroup — Buttons" + direction LR + OK["OK"] + SPACE1[" "] + CANCEL["Cancel"] + end + end + + style FILELIST fill:#e8f4fd,stroke:#2196f3,color:#333 + style DEVLIST fill:#e8f4fd,stroke:#2196f3,color:#333 + style PATH fill:#fff3e0,stroke:#ff9800,color:#333 + style FILE fill:#fff3e0,stroke:#ff9800,color:#333 + style OK fill:#e8f5e9,stroke:#4caf50,color:#333 + style CANCEL fill:#ffebee,stroke:#f44336,color:#333 + style SPACE1 fill:none,stroke:none +``` + +**Corresponding MUI code:** + +```c +VGroup, + Child, HGroup, + Child, FileListview(), + Child, DeviceListview(), + End, + Child, PathGadget(), + Child, FileGadget(), + Child, HGroup, + Child, OkayButton(), + Child, HSpace(0), + Child, CancelButton(), + End, + End; +``` + +--- + +Previous: [Core Concepts](04-core-concepts.md) +Next: [Widgets Overview](06-widgets-overview.md) diff --git a/09_intuition/frameworks/mui/06-widgets-overview.md b/09_intuition/frameworks/mui/06-widgets-overview.md new file mode 100644 index 0000000..e0118ee --- /dev/null +++ b/09_intuition/frameworks/mui/06-widgets-overview.md @@ -0,0 +1,374 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Widgets Overview + +MUI provides a comprehensive set of built-in widget classes. This section provides a quick tour of the most commonly used ones with code snippets. + +## Text + +The Text class displays static text. It supports control codes for formatting. + +```c +Child, TextObject, + MUIA_Text_Contents, "\33cCentered text", + End, +``` + +### Text Control Codes (MUIX_) + +| Code | Effect | +|------|--------| +| `\33c` or `MUIX_C` | Center align | +| `\33r` or `MUIX_R` | Right align | +| `\33l` or `MUIX_L` | Left align | +| `\33b` or `MUIX_B` | Bold | +| `\33i` or `MUIX_I` | Italic | +| `\33u` or `MUIX_U` | Underline | + +Multiple codes can be combined: + +```c +"\33c\33bBold centered text" +``` + +### Common Text Attributes + +| Attribute | Description | +|-----------|-------------| +| `MUIA_Text_Contents` | The string to display | +| `MUIA_Text_HiChar` | Underline this character as shortcut | +| `MUIA_Text_PreParse` | Pre-parse string with control codes | +| `MUIA_Text_SetVMax` | Set vertical maximum to font height | +| `MUIA_Text_SetMin` | Set minimum size to text dimensions | + +## String + +The String class is a single-line text input field. + +```c +Child, StringObject, + StringFrame, + MUIA_String_Contents, "Default text", + MUIA_String_MaxLen, 256, + MUIA_String_Format, MUIV_String_Format_Left, + End, +``` + +### String Attributes + +| Attribute | Description | +|-----------|-------------| +| `MUIA_String_Contents` | Current string contents | +| `MUIA_String_MaxLen` | Maximum length | +| `MUIA_String_Format` | Left, center, or right alignment | +| `MUIA_String_Integer` | Parse contents as integer | +| `MUIA_String_Accept` | Valid character set | +| `MUIA_String_Reject` | Invalid character set | + +## Buttons + +The simplest button uses the `SimpleButton` macro: + +```c +Child, SimpleButton("OK"), +``` + +This is shorthand for: + +```c +Child, TextObject, + ButtonFrame, + MUIA_Background, MUII_ButtonBack, + MUIA_Text_Contents, "OK", + MUIA_Text_PreParse, "\33c", + MUIA_InputMode, MUIV_InputMode_RelVerify, + End, +``` + +## List and Listview + +Lists store lines of data. A Listview wraps a List with scrollbars and input handling. + +### Basic Listview + +```c +Child, ListviewObject, + MUIA_Listview_Input, FALSE, + MUIA_Listview_List, ListObject, + ReadListFrame, + MUIA_List_ConstructHook, MUIV_List_ConstructHook_String, + MUIA_List_DestructHook, MUIV_List_DestructHook_String, + End, + End, +``` + +### Adding Items + +```c +char *entry = "New item"; +DoMethod(list, MUIM_List_InsertSingle, entry, MUIV_List_Insert_Bottom); +``` + +### Clearing the List + +```c +DoMethod(list, MUIM_List_Clear); +``` + +### List Attributes + +| Attribute | Description | +|-----------|-------------| +| `MUIA_List_SourceArray` | Initialize from a NULL-terminated array | +| `MUIA_List_ConstructHook` | Hook to construct entries | +| `MUIA_List_DestructHook` | Hook to destruct entries | +| `MUIA_List_CompareHook` | Hook for sorting | +| `MUIA_List_DisplayHook` | Hook for custom rendering | +| `MUIA_List_Quiet` | Suppress updates during bulk operations | +| `MUIA_List_Active` | Currently selected entry | + +### Specialized List Classes + +| Class | Purpose | +|-------|---------| +| `Floattext` | Display multi-line text with word wrap | +| `Dirlist` | Display directory contents | +| `Volumelist` | Display mounted volumes | +| `Scrmodelist` | Display available screen modes | + +## Numeric Family + +### Slider + +```c +Child, SliderObject, + MUIA_Numeric_Min, 0, + MUIA_Numeric_Max, 100, + MUIA_Numeric_Value, 50, + MUIA_Slider_Horiz, TRUE, + End, +``` + +### Knob + +```c +Child, KnobObject, + MUIA_Numeric_Min, 0, + MUIA_Numeric_Max, 255, + End, +``` + +### Levelmeter + +```c +Child, LevelmeterObject, + MUIA_Numeric_Min, 0, + MUIA_Numeric_Max, 100, + MUIA_Levelmeter_Label, "Volume", + End, +``` + +### Numericbutton + +A compact popup slider: + +```c +Child, NumericbuttonObject, + MUIA_Numeric_Min, 0, + MUIA_Numeric_Max, 999, + End, +``` + +## Cycle + +A cycle gadget cycles through a set of string labels: + +```c +static char *choices[] = { "First", "Second", "Third", NULL }; + +Child, CycleObject, + MUIA_Cycle_Entries, choices, + End, +``` + +Or using the shorthand macro: + +```c +Child, MUI_MakeObject(MUIO_Cycle, NULL, choices), +``` + +## Radio + +Radio buttons for exclusive selection: + +```c +static char *options[] = { "Option A", "Option B", "Option C", NULL }; + +Child, RadioObject, + MUIA_Radio_Entries, options, + End, +``` + +Or using the shorthand: + +```c +Child, MUI_MakeObject(MUIO_Radio, NULL, options), +``` + +## Gauge + +A horizontal or vertical progress bar: + +```c +Child, GaugeObject, + MUIA_Gauge_Current, 50, + MUIA_Gauge_Max, 100, + MUIA_Gauge_Horiz, TRUE, + End, +``` + +## Scale + +A percentage display: + +```c +Child, ScaleObject, + MUIA_Scale_Horiz, TRUE, + End, +``` + +## Colorfield and Palette + +### Colorfield + +Displays a color that can be changed: + +```c +Child, ColorfieldObject, + MUIA_Colorfield_RGB, rgb_value, + End, +``` + +### Palette + +A full palette selection gadget: + +```c +Child, PaletteObject, + MUIA_Palette_Entries, palette_entries, + End, +``` + +## Image and Bitmap + +### Image + +Display a built-in or custom image: + +```c +Child, ImageObject, + MUIA_Image_Spec, MUII_Close, + End, +``` + +Standard images include `MUII_Close`, `MUII_TapePlay`, `MUII_TapeStop`, `MUII_TapePause`, `MUII_ArrowLeft`, `MUII_ArrowRight`, `MUII_ArrowUp`, `MUII_ArrowDown`, and many others. + +### Bitmap + +Display a custom bitmap: + +```c +Child, BitmapObject, + MUIA_Bitmap_Width, 32, + MUIA_Bitmap_Height, 32, + MUIA_Bitmap_Bitmap, myBitMap, + MUIA_Bitmap_Transparent, 0, + End, +``` + +### Bodychunk + +Create a bitmap from an ILBM body chunk (useful for embedding images): + +```c +Child, BodychunkObject, + MUIA_Bitmap_Width, 32, + MUIA_Bitmap_Height, 32, + MUIA_Bitmap_Bitmap, myBitMap, + MUIA_Bodychunk_Body, body_data, + MUIA_Bodychunk_Compression, cmpByteRun1, + MUIA_Bodychunk_Depth, 5, + MUIA_Bodychunk_Masking, mskHasMask, + End, +``` + +## Popup Classes + +### Popstring + +Base class for popup string gadgets. Usually subclassed rather than used directly. + +### Popasl + +Popup an ASL file or font requester: + +```c +Child, PopaslObject, + MUIA_Popstring_String, StringObject, StringFrame, End, + MUIA_Popasl_Type, ASL_FileRequest, + MUIA_Popasl_StartHook, &startHook, + End, +``` + +### Poplist + +Popup a simple list: + +```c +Child, PoplistObject, + MUIA_Popstring_String, StringObject, StringFrame, End, + MUIA_Poplist_Array, string_array, + End, +``` + +### Popobject + +Popup any arbitrary object tree: + +```c +Child, PopobjectObject, + MUIA_Popstring_String, StringObject, StringFrame, End, + MUIA_Popobject_Object, ListviewObject, + MUIA_Listview_List, customList, + End, + End, +``` + +## Register + +A tabbed group where each page is a separate group: + +```c +Child, RegisterGroup(labels), + Child, VGroup, + /* Page 1 contents */ + End, + Child, VGroup, + /* Page 2 contents */ + End, + Child, VGroup, + /* Page 3 contents */ + End, + End, +``` + +Where `labels` is a NULL-terminated array of tab titles: + +```c +static char *labels[] = { "General", "Advanced", "About", NULL }; +``` + +--- + +Previous: [Layout System](05-layout-system.md) +Next: [Windows and Applications](07-windows-and-applications.md) diff --git a/09_intuition/frameworks/mui/07-windows-and-applications.md b/09_intuition/frameworks/mui/07-windows-and-applications.md new file mode 100644 index 0000000..59f0d20 --- /dev/null +++ b/09_intuition/frameworks/mui/07-windows-and-applications.md @@ -0,0 +1,213 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Windows and Applications + +## The Application Object + +Every MUI program needs exactly one Application object. It is the root of the object tree and manages: + +- All windows (via `SubWindow`) +- The program's identity (title, version, author) +- The main input loop +- Iconification and AppMessage handling +- Return ID dispatching + +### Creating an Application + +```c +app = ApplicationObject, + MUIA_Application_Title , "MyApp", + MUIA_Application_Version , "$VER: MyApp 1.0 (01.01.97)", + MUIA_Application_Copyright , "1997 Author Name", + MUIA_Application_Author , "Author Name", + MUIA_Application_Description, "Description of what this does", + MUIA_Application_Base , "MYAPP", + + SubWindow, window = WindowObject, + MUIA_Window_Title, "Main Window", + MUIA_Window_ID , MAKE_ID('M','A','I','N'), + WindowContents, VGroup, + /* widgets */ + End, + End, + + End; +``` + +### Application Attributes + +| Attribute | Description | +|-----------|-------------| +| `MUIA_Application_Title` | Program title shown in system exchanges | +| `MUIA_Application_Version` | Version string, conventionally `$VER: Name Version (Date)` | +| `MUIA_Application_Copyright` | Copyright notice | +| `MUIA_Application_Author` | Author name | +| `MUIA_Application_Description` | Short description | +| `MUIA_Application_Base` | Base name for disk object, ARexx port, etc. | +| `MUIA_Application_Window` | Alternative to `SubWindow` for adding windows | +| `MUIA_Application_DropObject` | Object to receive icons dropped on app icon | + +## The Window Object + +Windows are children of the Application. Each Window contains a single root widget specified with `WindowContents`. + +### Window Attributes + +| Attribute | Description | +|-----------|-------------| +| `MUIA_Window_Title` | Window title bar text | +| `MUIA_Window_ID` | Four-byte identifier for state persistence | +| `MUIA_Window_Open` | Open or close the window (ISG) | +| `MUIA_Window_Screen` | Public screen to open on | +| `MUIA_Window_Menustrip` | Menu strip for this window | +| `MUIA_Window_AppWindow` | Accept Workbench icons (TRUE/FALSE) | +| `MUIA_Window_NoMenus` | Disable menus for this window | +| `MUIA_Window_ActiveObject` | Object to receive initial activation | +| `MUIA_Window_CloseRequest` | Set when user clicks close gadget (triggers notification) | + +### Opening and Closing + +```c +/* Open the window */ +set(window, MUIA_Window_Open, TRUE); + +/* Later, close it */ +set(window, MUIA_Window_Open, FALSE); +``` + +Always close windows before disposing the Application. + +## SubWindow vs Standalone Windows + +The `SubWindow` tag inside `ApplicationObject` both creates the window and registers it with the application in one step. This is the recommended pattern: + +```c +app = ApplicationObject, + ... + SubWindow, window = WindowObject, ... , End, + SubWindow, window2 = WindowObject, ... , End, + End; +``` + +Alternatively, you can create windows separately and add them later with `MUIA_Application_Window`, but `SubWindow` is more common in examples. + +## Multiple Windows + +An application can have multiple windows. Each window can be opened and closed independently: + +```c +app = ApplicationObject, + ... + SubWindow, mainWin = WindowObject, + MUIA_Window_Title, "Main", + MUIA_Window_ID , MAKE_ID('M','A','I','N'), + ... + End, + + SubWindow, helpWin = WindowObject, + MUIA_Window_Title, "Help", + MUIA_Window_ID , MAKE_ID('H','E','L','P'), + ... + End, + End; +``` + +Each window should have a unique `MUIA_Window_ID` so MUI can save and restore its position and size. + +## AppWindows + +An AppWindow accepts icons dragged from Workbench. Enable it with `MUIA_Window_AppWindow`: + +```c +WindowObject, + MUIA_Window_Title , "Drop icons on me!", + MUIA_Window_ID , MAKE_ID('A','P','P','W'), + MUIA_Window_AppWindow, TRUE, + ... + End, +``` + +When an icon is dropped, the object that received it gets an `AppMessage`. You can handle this with a notification: + +```c +SAVEDS ASM LONG AppMsgFunc(REG(a2) APTR obj, REG(a1) struct AppMessage **x) +{ + struct AppMessage *amsg = *x; + struct WBArg *ap; + int i; + static char buf[256]; + + for (ap = amsg->am_ArgList, i = 0; i < amsg->am_NumArgs; i++, ap++) + { + NameFromLock(ap->wa_Lock, buf, sizeof(buf)); + AddPart(buf, ap->wa_Name, sizeof(buf)); + DoMethod(obj, MUIM_List_Insert, &buf, 1, MUIV_List_Insert_Bottom); + } + return 0; +} + +static const struct Hook AppMsgHook = { + { NULL, NULL }, (VOID *)AppMsgFunc, NULL, NULL +}; + +/* In setup: */ +DoMethod(lv1, MUIM_Notify, MUIA_AppMessage, MUIV_EveryTime, + lv1, 3, MUIM_CallHook, &AppMsgHook, MUIV_TriggerValue); +``` + +### DropObject + +When the application is iconified, icons dropped on its AppIcon can be routed to a specific object: + +```c +set(app, MUIA_Application_DropObject, targetList); +``` + +## Iconification + +MUI handles iconification automatically. When the user clicks the zoom gadget (or uses the menu item), the application window closes and an AppIcon appears on Workbench. Pressing the AppIcon restores the window. + +You can react to iconification state changes via notifications on `MUIA_Application_Iconified`. + +## Window State Persistence + +MUI automatically saves window positions and sizes using the `MUIA_Window_ID`. The ID is a four-byte value conventionally created with `MAKE_ID`: + +```c +#define MAKE_ID(a,b,c,d) ((ULONG)(a)<<24 | (ULONG)(b)<<16 | (ULONG)(c)<<8 | (ULONG)(d)) + +MUIA_Window_ID, MAKE_ID('M','A','I','N'), +``` + +Each window in your application should have a unique ID. MUI stores positions in the `ENV:` directory. + +## Common Window Patterns + +### Centered Dialog + +```c +WindowObject, + MUIA_Window_Title, "Confirm", + MUIA_Window_ID , MAKE_ID('C','N','F','M'), + MUIA_Window_Width, MUIV_Window_Width_MinMax(20), + WindowContents, VGroup, + Child, TextObject, + TextFrame, + MUIA_Text_Contents, "\33cAre you sure?", + End, + Child, HGroup, + Child, SimpleButton("Yes"), + Child, SimpleButton("No"), + End, + End, + End, +``` + +### Modal Window + +MUI does not have a built-in modal window concept, but you can simulate it by disabling input on the main window and running a sub-loop for the dialog window. + +--- + +Previous: [Widgets Overview](06-widgets-overview.md) +Next: [Menus](08-menus.md) diff --git a/09_intuition/frameworks/mui/08-menus.md b/09_intuition/frameworks/mui/08-menus.md new file mode 100644 index 0000000..5475a58 --- /dev/null +++ b/09_intuition/frameworks/mui/08-menus.md @@ -0,0 +1,211 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Menus + +## Menu System Architecture + +MUI's menu system consists of three class types: + +| Class | Role | +|-------|------| +| `Menustrip` | Root container holding all menus | +| `Menu` | A single pull-down menu (e.g., "Project") | +| `Menuitem` | An individual item within a menu | + +A Menustrip is attached to either a specific Window (via `MUIA_Window_Menustrip`) or to the Application (via `MUIA_Application_Menustrip`). When attached to the Application, all windows inherit the menu unless overridden. + +## Using GadTools NewMenu + +The easiest way to define menus is with the familiar GadTools `NewMenu` structure. MUI converts this into its own object tree automatically. + +### Define the Menu Structure + +```c +enum { + MEN_PROJECT = 1, + MEN_ABOUT, + MEN_QUIT, + MEN_EDIT, + MEN_CUT, + MEN_COPY, + MEN_PASTE +}; + +static struct NewMenu MenuData[] = { + { NM_TITLE, "Project" , 0, 0, 0, (APTR)MEN_PROJECT }, + { NM_ITEM , "About...", "?", 0, 0, (APTR)MEN_ABOUT }, + { NM_ITEM , NM_BARLABEL, 0, 0, 0, (APTR)0 }, + { NM_ITEM , "Quit" , "Q", 0, 0, (APTR)MEN_QUIT }, + + { NM_TITLE, "Edit" , 0, 0, 0, (APTR)MEN_EDIT }, + { NM_ITEM , "Cut" , "X", 0, 0, (APTR)MEN_CUT }, + { NM_ITEM , "Copy" , "C", 0, 0, (APTR)MEN_COPY }, + { NM_ITEM , "Paste" , "V", 0, 0, (APTR)MEN_PASTE }, + + { NM_END, NULL, 0, 0, 0, (APTR)0 }, +}; +``` + +### Create and Attach the Menu + +```c +app = ApplicationObject, + ... + SubWindow, win = WindowObject, + MUIA_Window_Title , "Menus", + MUIA_Window_ID , MAKE_ID('M','E','N','1'), + MUIA_Window_Menustrip, strip = MUI_MakeObject(MUIO_MenustripNM, MenuData, 0), + WindowContents, VGroup, + ... + End, + End, + End; +``` + +`MUI_MakeObject(MUIO_MenustripNM, MenuData, 0)` parses the `NewMenu` array and returns a Menustrip object. + +## Handling Menu Selections + +### Method 1: Return IDs in the Input Loop + +Each menu item's `UserData` becomes its Return ID. In the main loop, check for it: + +```c +while (running) +{ + switch (DoMethod(app, MUIM_Application_Input, &signals)) + { + case MUIV_Application_ReturnID_Quit: + running = FALSE; + break; + + case MEN_ABOUT: + /* show about box */ + break; + + case MEN_CUT: + /* perform cut */ + break; + } + + if (running && signals) + Wait(signals); +} +``` + +### Method 2: Notifications + +Bind menu items directly to actions using `MUIM_Notify`: + +```c +APTR aboutItem; + +/* Find the item using its userdata */ +aboutItem = (APTR)DoMethod(strip, MUIM_FindUserData, MEN_ABOUT); + +if (aboutItem) +{ + DoMethod(aboutItem, MUIM_Notify, MUIA_Menuitem_Trigger, MUIV_EveryTime, + app, 2, MUIM_Application_ReturnID, MEN_ABOUT); +} +``` + +### Method 3: Hooks + +For more complex actions, use `MUIM_CallHook`: + +```c +SAVEDS ASM LONG AboutFunc(REG(a2) APTR obj, REG(a1) APTR msg) +{ + MUI_Request(app, win, 0, "About", "*OK", + "MyApp\nVersion 1.0\nBy Author"); + return 0; +} + +static struct Hook AboutHook = { {0,0}, (VOID *)AboutFunc, NULL, NULL }; + +/* In setup: */ +DoMethod(aboutItem, MUIM_Notify, MUIA_Menuitem_Trigger, MUIV_EveryTime, + app, 3, MUIM_CallHook, &AboutHook, MUIV_TriggerValue); +``` + +## Dynamic Menu Manipulation + +### Enabling and Disabling Items + +```c +/* Disable a menu item */ +set(menuItem, MUIA_Menuitem_Enabled, FALSE); + +/* Enable it again */ +set(menuItem, MUIA_Menuitem_Enabled, TRUE); +``` + +### Checkmarks and Radio Groups + +Use standard GadTools flags in the `NewMenu` structure: + +```c +#define RB CHECKIT + +static struct NewMenu MenuData[] = { + { NM_TITLE, "Settings" , 0 ,0 ,0,(APTR)MEN_SETTINGS }, + { NM_ITEM , "Hardware" , 0 ,NM_ITEMDISABLED,0,(APTR)MEN_HARDWARE }, + { NM_SUB , "A1000" ,"1",RB|CHECKED,2|4|8 ,(APTR)MEN_A1000 }, + { NM_SUB , "A2000" ,"2",RB ,1|4|8 ,(APTR)MEN_A2000 }, + { NM_SUB , "A3000" ,"3",RB ,1|2|8 ,(APTR)MEN_A3000 }, + { NM_SUB , "A4000" ,"4",RB ,1|2|4 ,(APTR)MEN_A4000 }, + { NM_END , NULL , 0 ,0 ,0,(APTR)0 }, +}; +``` + +`CHECKIT` makes the item checkable. `CHECKED` pre-checks it. The mutual exclude field (e.g., `2|4|8`) defines radio groups: items with overlapping bits cannot be checked simultaneously. + +### Toggle Items + +Use `MENUTOGGLE` for items that toggle on/off independently: + +```c +#define TG CHECKIT|MENUTOGGLE + +{ NM_SUB, "Option", "O", TG, 0, (APTR)MEN_OPTION }, +``` + +## Creating Menus Programmatically + +Instead of `NewMenu`, you can build the menu tree manually: + +```c +MUIA_Window_Menustrip, MenustripObject, + Child, MenuObject, + MUIA_Menu_Title, "Project", + Child, MenuitemObject, + MUIA_Menuitem_Title, "About...", + MUIA_Menuitem_Shortcut, "?", + End, + Child, MenuitemObject, + MUIA_Menuitem_Title, NM_BARLABEL, + End, + Child, MenuitemObject, + MUIA_Menuitem_Title, "Quit", + MUIA_Menuitem_Shortcut, "Q", + End, + End, + End, +``` + +This is more verbose but gives you direct access to every menu item object for notifications. + +## Menu Best Practices + +- Always provide keyboard shortcuts for common actions +- Use `NM_BARLABEL` to separate logical groups +- Keep the `NewMenu` userdata values unique across the entire menu +- Attach menus to the Application if all windows share the same menu +- Attach menus to individual Windows only when menus differ per window +- Use `MUIM_FindUserData` on the Menustrip to locate individual items for notifications + +--- + +Previous: [Windows and Applications](07-windows-and-applications.md) +Next: [Custom Classes](09-custom-classes.md) diff --git a/09_intuition/frameworks/mui/09-custom-classes.md b/09_intuition/frameworks/mui/09-custom-classes.md new file mode 100644 index 0000000..ecddd51 --- /dev/null +++ b/09_intuition/frameworks/mui/09-custom-classes.md @@ -0,0 +1,292 @@ +[← 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) diff --git a/09_intuition/frameworks/mui/10-events-and-notifications.md b/09_intuition/frameworks/mui/10-events-and-notifications.md new file mode 100644 index 0000000..a596b6a --- /dev/null +++ b/09_intuition/frameworks/mui/10-events-and-notifications.md @@ -0,0 +1,278 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Events and Notifications + +## The MUI Event Model + +MUI replaces traditional callback registration with a declarative notification system. Instead of writing: + +```c +/* Traditional GUI toolkit */ +button_set_callback(myButton, on_click, myHandler); +``` + +You write: + +```c +/* MUI */ +DoMethod(myButton, MUIM_Notify, MUIA_Pressed, FALSE, + targetObj, 3, MUIM_CallHook, &myHook, MUIV_TriggerValue); +``` + +This establishes a relationship: when the source attribute changes to a specific value, invoke a method on the target object. + +## MUIM_Notify + +The notification method signature: + +```c +DoMethod(sourceObj, MUIM_Notify, + sourceAttribute, triggerValue, + targetObj, numArgs, targetMethod, ...); +``` + +| Parameter | Description | +|-----------|-------------| +| `sourceObj` | Object to monitor | +| `sourceAttribute` | Attribute to watch (e.g., `MUIA_Pressed`) | +| `triggerValue` | Value that triggers the notification | +| `targetObj` | Object to invoke the method on | +| `numArgs` | Number of arguments passed to the target method | +| `targetMethod` | Method to invoke (e.g., `MUIM_Application_ReturnID`) | +| `...` | Additional arguments | + +### Common Trigger Values + +| Value | Meaning | +|-------|---------| +| `MUIV_EveryTime` | Trigger on any change, not just a specific value | +| `TRUE` / `FALSE` | Trigger only when boolean attribute becomes true/false | +| Specific value | Trigger only when attribute equals this exact value | + +### Simple Example: Close Window + +```c +DoMethod(window, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, + app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit); +``` + +When the window's close request flag becomes `TRUE` (user clicks the close gadget), the Application receives a ReturnID of `Quit`. + +### Button Press + +```c +DoMethod(button, MUIM_Notify, MUIA_Pressed, FALSE, + app, 2, MUIM_Application_ReturnID, ID_SAVE); +``` + +`MUIA_Pressed` becomes `FALSE` when the user releases the mouse button over the gadget. This is the standard way to detect button clicks. + +### Slider Value Change + +```c +DoMethod(slider, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, + textObj, 3, MUIM_Set, MUIA_Text_Contents, MUIV_TriggerValue); +``` + +Every time the slider value changes, update a text object. `MUIV_TriggerValue` passes the new slider value as the argument. + +## Input Loops + +MUI provides two methods for running the application's main loop. + +### Modern Loop: MUIM_Application_NewInput + +This is the preferred method. It is faster because it integrates signal waiting. + +```c +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; + } +} +``` + +How it works: + +1. `DoMethod(app, MUIM_Application_NewInput, &sigs)` processes pending input +2. It returns a ReturnID if one was triggered, or 0 +3. It fills `sigs` with the signal mask the application should wait on +4. Your code calls `Wait()` with those signals +5. The loop repeats + +This is significantly more efficient than the legacy loop because MUI tells you exactly which signals to wait for. + +### Legacy Loop: MUIM_Application_Input + +Older examples use this method. It is simpler but less efficient. + +```c +ULONG signals; +BOOL running = TRUE; + +while (running) +{ + switch (DoMethod(app, MUIM_Application_Input, &signals)) + { + case MUIV_Application_ReturnID_Quit: + running = FALSE; + break; + + case ID_ABOUT: + /* handle about */ + break; + + case ID_SAVE: + /* handle save */ + break; + } + + if (running && signals) + Wait(signals); +} +``` + +The difference: `MUIM_Application_Input` handles waiting internally but returns a generic signal mask. `MUIM_Application_NewInput` gives you finer control. + +## Return IDs + +Return IDs are how notifications communicate with the input loop. MUI defines a standard quit ID: + +| Constant | Value | Meaning | +|----------|-------|---------| +| `MUIV_Application_ReturnID_Quit` | -1 | Application should terminate | + +You can define your own IDs: + +```c +#define ID_ABOUT 1 +#define ID_SAVE 2 +#define ID_OPEN 3 +``` + +Return IDs are triggered by notifications: + +```c +DoMethod(button, MUIM_Notify, MUIA_Pressed, FALSE, + app, 2, MUIM_Application_ReturnID, ID_SAVE); +``` + +## Hooks + +For complex actions that cannot be expressed as a simple method call, use hooks. A hook is a standard AmigaOS callback structure. + +### Hook Structure + +```c +struct Hook +{ + struct MinNode h_MinNode; + ULONG (*h_Entry)(); /* function pointer */ + ULONG (*h_SubEntry)(); + APTR h_Data; /* user data */ +}; +``` + +### Defining a Hook + +```c +SAVEDS ASM LONG MyHookFunc(REG(a2) APTR obj, REG(a1) APTR param) +{ + /* obj is the object that triggered the hook */ + /* param is the argument passed from the notification */ + return 0; +} + +static struct Hook MyHook = { + { NULL, NULL }, (VOID *)MyHookFunc, NULL, NULL +}; +``` + +### Invoking a Hook via Notification + +```c +DoMethod(button, MUIM_Notify, MUIA_Pressed, FALSE, + app, 3, MUIM_CallHook, &MyHook, MUIV_TriggerValue); +``` + +### Hook Register Conventions + +The MUI examples use register arguments for hook functions: + +| Register | Parameter | +|----------|-----------| +| `a0` | struct Hook * | +| `a2` | Object * (source object) | +| `a1` | Msg / parameter | + +Different compilers handle register keywords differently, so the examples use conditional macros: + +```c +#ifdef _DCC +#define REG(x) __ ## x +#define ASM +#define SAVEDS __geta4 +#else +#define REG(x) register __ ## x +#if defined __MAXON__ || defined __GNUC__ +#define ASM +#define SAVEDS +#else +#define ASM __asm +#define SAVEDS __saveds +#endif +#endif +``` + +## Notification Chains + +Notifications can be chained. One notification can trigger an attribute change that causes another notification: + +```c +/* When checkbox is checked, enable the button */ +DoMethod(checkbox, MUIM_Notify, MUIA_Selected, TRUE, + button, 3, MUIM_Set, MUIA_Disabled, FALSE); + +/* When checkbox is unchecked, disable the button */ +DoMethod(checkbox, MUIM_Notify, MUIA_Selected, FALSE, + button, 3, MUIM_Set, MUIA_Disabled, TRUE); +``` + +## Common Notification Patterns + +### Synchronize Two Widgets + +```c +/* Slider and numeric button show the same value */ +DoMethod(slider, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, + numButton, 3, MUIM_Set, MUIA_Numeric_Value, MUIV_TriggerValue); + +DoMethod(numButton, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, + slider, 3, MUIM_Set, MUIA_Numeric_Value, MUIV_TriggerValue); +``` + +### Update Window Title + +```c +DoMethod(string, MUIM_Notify, MUIA_String_Contents, MUIV_EveryTime, + window, 3, MUIM_Set, MUIA_Window_Title, MUIV_TriggerValue); +``` + +### Enable Widget Based on Selection + +```c +DoMethod(listview, MUIM_Notify, MUIA_List_Active, MUIV_EveryTime, + app, 2, MUIM_CallHook, &UpdateButtonHook); +``` + +--- + +Previous: [Custom Classes](09-custom-classes.md) +Next: [Advanced Patterns](11-advanced-patterns.md) diff --git a/09_intuition/frameworks/mui/11-advanced-patterns.md b/09_intuition/frameworks/mui/11-advanced-patterns.md new file mode 100644 index 0000000..56178db --- /dev/null +++ b/09_intuition/frameworks/mui/11-advanced-patterns.md @@ -0,0 +1,282 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Advanced Patterns + +## Drag and Drop + +MUI supports drag and drop between listviews and custom objects. The drag source initiates the operation; the destination decides whether to accept it. + +### Enabling Drag and Drop on Lists + +```c +ListviewObject, + MUIA_Listview_DragType, MUIV_Listview_DragType_Default, + MUIA_Listview_List, ListObject, + MUIA_List_DragSortable, TRUE, + End, + End, +``` + +`MUIA_List_DragSortable` allows the user to reorder list items by dragging. + +### Custom Drag Query + +For custom classes, implement `MUIM_DragQuery` to accept or reject drags: + +```c +ULONG DragQuery(struct IClass *cl, Object *obj, struct MUIP_DragDrop *msg) +{ + if (msg->obj == obj) + { + /* Dragging onto ourselves - let superclass handle it */ + return DoSuperMethodA(cl, obj, msg); + } + else if (msg->obj == (Object *)muiUserData(obj)) + { + /* Accept drags from our predefined source */ + return MUIV_DragQuery_Accept; + } + + /* Reject everything else */ + return MUIV_DragQuery_Refuse; +} +``` + +Return values: + +| Value | Meaning | +|-------|---------| +| `MUIV_DragQuery_Accept` | Accept the drag | +| `MUIV_DragQuery_Refuse` | Reject the drag | +| `MUIV_DragQuery_Ask` | Ask user (rarely used) | + +### Handling the Drop + +Implement `MUIM_DragDrop` to process the actual drop: + +```c +ULONG DragDrop(struct IClass *cl, Object *obj, struct MUIP_DragDrop *msg) +{ + /* msg->obj is the source, obj is the destination */ + /* Perform the drop operation */ + return DoSuperMethodA(cl, obj, msg); +} +``` + +## Settings Persistence + +MUI provides a mechanism to save and restore the state of widgets using `Dataspace` objects. + +### Saving Settings + +```c +APTR dataspace; + +/* Create a dataspace to hold settings */ +dataspace = MUI_NewObject(MUIC_Dataspace, TAG_DONE); + +/* Ask each object to export its settings */ +DoMethod(window, MUIM_Export, dataspace); + +/* Save the dataspace to disk */ +/* (Implementation depends on your storage format) */ +``` + +### Loading Settings + +```c +/* Load dataspace from disk, then: */ +DoMethod(window, MUIM_Import, dataspace); + +/* Update the UI */ +MUI_DisposeObject(dataspace); +``` + +Many built-in MUI classes support `MUIM_Export` and `MUIM_Import` automatically. Custom classes need to implement these methods if they want to participate. + +## Subtasks and Background Processing + +For long-running operations, perform work in a separate task and update the UI periodically. The `Subtask.c` example demonstrates this with a fractal renderer. + +### SubTask Structure + +```c +struct SubTask +{ + struct Task *st_Task; /* Sub task pointer */ + struct MsgPort *st_Port; /* Allocated by sub task */ + struct MsgPort *st_Reply; /* Allocated by main task */ + APTR st_Data; /* Initial data */ + struct SubTaskMsg st_Message; +}; + +struct SubTaskMsg +{ + struct Message stm_Message; + WORD stm_Command; + APTR stm_Parameter; + LONG stm_Result; +}; +``` + +### Communication Protocol + +```c +#define STC_STARTUP -2 +#define STC_SHUTDOWN -1 +#define STC_START 0 +#define STC_STOP 1 + +LONG SendSubTaskMsg(struct SubTask *st, WORD command, APTR params) +{ + st->st_Message.stm_Message.mn_ReplyPort = st->st_Reply; + st->st_Message.stm_Message.mn_Length = sizeof(struct SubTaskMsg); + st->st_Message.stm_Command = command; + st->st_Message.stm_Parameter = params; + st->st_Message.stm_Result = 0; + + PutMsg(command == STC_STARTUP + ? &((struct Process *)st->st_Task)->pr_MsgPort + : st->st_Port, + (struct Message *)&st->st_Message); + + WaitPort(st->st_Reply); + GetMsg(st->st_Reply); + + return st->st_Message.stm_Result; +} +``` + +### Spawning a Subtask + +```c +struct SubTask *SpawnSubTask(char *name, VOID (*func)(VOID), APTR data) +{ + struct SubTask *st; + + if (st = AllocVec(sizeof(struct SubTask), MEMF_PUBLIC | MEMF_CLEAR)) + { + if (st->st_Reply = CreateMsgPort()) + { + st->st_Data = data; + + if (st->st_Task = CreateNewProcTags( + NP_Entry, func, + NP_Name, name, + NP_Priority, 0, + TAG_DONE)) + { + /* Send startup message with SubTask pointer */ + SendSubTaskMsg(st, STC_STARTUP, st); + return st; + } + } + } + /* cleanup on error */ + return NULL; +} +``` + +### Updating the UI from the Subtask + +The subtask should not directly call MUI methods. Instead, it signals the main task, which then updates the UI: + +```c +/* In subtask: calculate a line, then signal main task */ +Signal(mainTask, SIGF_SINGLE); + +/* In main task input loop or notification handler: */ +/* Check for update flag and redraw affected area */ +``` + +Alternatively, use `MUIM_Application_PushMethod` to safely queue a method call from another task: + +```c +/* Thread-safe: push a method onto the application's queue */ +DoMethod(app, MUIM_Application_PushMethod, obj, 2, MUIM_Redraw, MADF_DRAWOBJECT); +``` + +## BOOPSI Gadget Integration + +MUI can host native BOOPSI gadgets through the `Boopsi` class: + +```c +Child, BoopsiObject, + MUIA_Boopsi_ClassID, "gadgetclass", + MUIA_Boopsi_MinWidth, 100, + MUIA_Boopsi_MinHeight, 20, + MUIA_Boopsi_Gadget, myGadget, + TAG_DONE, +``` + +This is useful when you have existing BOOPSI gadgets that you want to embed in a MUI layout. + +## Complex UI State Management + +When multiple widgets depend on the same state, use a Dataspace or a central model object with notifications: + +```c +/* Central model object (can be a simple Notify subclass) */ +APTR model = MUI_NewObject(MUIC_Notify, + MUIA_UserData, initialValue, + TAG_DONE); + +/* Widget A reflects model state */ +DoMethod(model, MUIM_Notify, MUIA_UserData, MUIV_EveryTime, + widgetA, 3, MUIM_Set, MUIA_Numeric_Value, MUIV_TriggerValue); + +/* Widget B also reflects model state */ +DoMethod(model, MUIM_Notify, MUIA_UserData, MUIV_EveryTime, + widgetB, 3, MUIM_Set, MUIA_Numeric_Value, MUIV_TriggerValue); + +/* Changing the model updates both widgets */ +set(model, MUIA_UserData, newValue); +``` + +## Notification Chains for Wizard UIs + +For multi-step UIs, chain notifications to show/hide pages: + +```c +/* Show page 2 when Next is pressed */ +DoMethod(nextButton, MUIM_Notify, MUIA_Pressed, FALSE, + page1, 3, MUIM_Set, MUIA_ShowMe, FALSE); + +DoMethod(nextButton, MUIM_Notify, MUIA_Pressed, FALSE, + page2, 3, MUIM_Set, MUIA_ShowMe, TRUE); + +/* Show page 1 when Back is pressed */ +DoMethod(backButton, MUIM_Notify, MUIA_Pressed, FALSE, + page2, 3, MUIM_Set, MUIA_ShowMe, FALSE); + +DoMethod(backButton, MUIM_Notify, MUIA_Pressed, FALSE, + page1, 3, MUIM_Set, MUIA_ShowMe, TRUE); +``` + +## Using MUI_MakeObject + +For simple widgets that don't need attributes at creation time, `MUI_MakeObject` provides a compact syntax: + +```c +/* Cycle gadget */ +APTR cycle = MUI_MakeObject(MUIO_Cycle, NULL, choices); + +/* Radio buttons */ +APTR radio = MUI_MakeObject(MUIO_Radio, NULL, options); + +/* Horizontal bar */ +APTR hbar = MUI_MakeObject(MUIO_HBar, 4); + +/* Vertical bar */ +APTR vbar = MUI_MakeObject(MUIO_VBar, 4); + +/* Menu strip from NewMenu */ +APTR strip = MUI_MakeObject(MUIO_MenustripNM, newMenu, 0); +``` + +This reduces verbosity for simple cases. + +--- + +Previous: [Events and Notifications](10-events-and-notifications.md) +Next: [Reference Snippets](12-reference-snippets.md) diff --git a/09_intuition/frameworks/mui/12-reference-snippets.md b/09_intuition/frameworks/mui/12-reference-snippets.md new file mode 100644 index 0000000..a35c151 --- /dev/null +++ b/09_intuition/frameworks/mui/12-reference-snippets.md @@ -0,0 +1,428 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [Frameworks](../README.md) + +# Reference Snippets + +Quick copy-paste templates for common MUI patterns. + +## Table of Contents + +- [Minimal Application Skeleton](#minimal-application-skeleton) +- [Window with Close Handler](#window-with-close-handler) +- [Vertical Layout with Text and Button](#vertical-layout-with-text-and-button) +- [Horizontal Button Row](#horizontal-button-row) +- [Listview with Strings](#listview-with-strings) +- [Slider with Value Display](#slider-with-value-display) +- [Cycle Gadget](#cycle-gadget) +- [Radio Buttons](#radio-buttons) +- [Notification Patterns](#notification-patterns) +- [Custom Class Boilerplate](#custom-class-boilerplate) +- [Menu Definition Template](#menu-definition-template) +- [Hook Function Template](#hook-function-template) +- [AppWindow Drag and Drop](#appwindow-drag-and-drop) +- [Input Loop Variants](#input-loop-variants) + +## Minimal Application Skeleton + +```c +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Library *MUIMasterBase; +extern struct Library *SysBase; + +int main(int argc, char *argv[]) +{ + APTR app, window; + + if (!(MUIMasterBase = OpenLibrary(MUIMASTER_NAME, MUIMASTER_VMIN))) + return 20; + + app = ApplicationObject, + MUIA_Application_Title , "App", + MUIA_Application_Version , "$VER: App 1.0", + MUIA_Application_Copyright , "Author", + MUIA_Application_Author , "Author", + MUIA_Application_Description, "Description", + MUIA_Application_Base , "APP", + + SubWindow, window = WindowObject, + MUIA_Window_Title, "Window", + MUIA_Window_ID , MAKE_ID('W','I','N','1'), + WindowContents, VGroup, + Child, TextObject, + MUIA_Text_Contents, "Hello MUI", + End, + End, + End, + End; + + if (!app) + { + CloseLibrary(MUIMasterBase); + return 20; + } + + 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); + CloseLibrary(MUIMasterBase); + return 0; +} +``` + +## Window with Close Handler + +```c +DoMethod(window, MUIM_Notify, MUIA_Window_CloseRequest, TRUE, + app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit); +``` + +## Vertical Layout with Text and Button + +```c +WindowContents, VGroup, + Child, TextObject, + TextFrame, + MUIA_Background, MUII_TextBack, + MUIA_Text_Contents, "\33cTitle", + End, + Child, StringObject, + StringFrame, + MUIA_String_Contents, "", + End, + Child, HGroup, + Child, HSpace(0), + Child, SimpleButton("OK"), + Child, SimpleButton("Cancel"), + Child, HSpace(0), + End, + End, +``` + +## Horizontal Button Row + +```c +Child, HGroup, + MUIA_Group_SameSize, TRUE, + Child, SimpleButton("New"), + Child, SimpleButton("Open"), + Child, SimpleButton("Save"), + Child, SimpleButton("Quit"), + End, +``` + +## Listview with Strings + +```c +APTR lv; + +Child, lv = ListviewObject, + MUIA_Listview_Input, TRUE, + MUIA_Listview_List, ListObject, + ReadListFrame, + MUIA_List_ConstructHook, MUIV_List_ConstructHook_String, + MUIA_List_DestructHook, MUIV_List_DestructHook_String, + End, + End, + +/* Insert items */ +DoMethod(lv, MUIM_List_InsertSingle, "First", MUIV_List_Insert_Bottom); +DoMethod(lv, MUIM_List_InsertSingle, "Second", MUIV_List_Insert_Bottom); + +/* Clear */ +DoMethod(lv, MUIM_List_Clear); +``` + +## Slider with Value Display + +```c +APTR slider, valueText; + +Child, HGroup, + Child, slider = SliderObject, + MUIA_Numeric_Min, 0, + MUIA_Numeric_Max, 100, + MUIA_Numeric_Value, 50, + MUIA_Slider_Horiz, TRUE, + End, + Child, valueText = TextObject, + MUIA_Text_Contents, "50", + End, + End, + +/* Sync slider to text */ +DoMethod(slider, MUIM_Notify, MUIA_Numeric_Value, MUIV_EveryTime, + valueText, 3, MUIM_Set, MUIA_Text_Contents, MUIV_TriggerValue); +``` + +## Cycle Gadget + +```c +static char *choices[] = { "Low", "Medium", "High", NULL }; + +Child, CycleObject, + MUIA_Cycle_Entries, choices, + End, +``` + +## Radio Buttons + +```c +static char *options[] = { "Option 1", "Option 2", "Option 3", NULL }; + +Child, RadioObject, + MUIA_Radio_Entries, options, + End, +``` + +## Notification Patterns + +### Button Click -> Quit + +```c +DoMethod(button, MUIM_Notify, MUIA_Pressed, FALSE, + app, 2, MUIM_Application_ReturnID, MUIV_Application_ReturnID_Quit); +``` + +### Button Click -> Custom Return ID + +```c +#define ID_SAVE 1 + +DoMethod(button, MUIM_Notify, MUIA_Pressed, FALSE, + app, 2, MUIM_Application_ReturnID, ID_SAVE); +``` + +### Checkbox Enables/Disables Widget + +```c +DoMethod(checkbox, MUIM_Notify, MUIA_Selected, TRUE, + widget, 3, MUIM_Set, MUIA_Disabled, FALSE); + +DoMethod(checkbox, MUIM_Notify, MUIA_Selected, FALSE, + widget, 3, MUIM_Set, MUIA_Disabled, TRUE); +``` + +### String -> Window Title Sync + +```c +DoMethod(string, MUIM_Notify, MUIA_String_Contents, MUIV_EveryTime, + window, 3, MUIM_Set, MUIA_Window_Title, MUIV_TriggerValue); +``` + +## Custom Class Boilerplate + +```c +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; + /* custom rendering here */ + 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); +} + +/* In main() */ +struct MUI_CustomClass *mcc; +mcc = MUI_CreateCustomClass(NULL, MUIC_Area, NULL, + sizeof(struct MyData), MyDispatcher); +if (!mcc) /* handle error */; + +APTR obj = NewObject(mcc->mcc_Class, NULL, TAG_DONE); + +/* Cleanup */ +MUI_DeleteCustomClass(mcc); +``` + +## Menu Definition Template + +```c +enum { MEN_PROJECT=1, MEN_ABOUT, MEN_QUIT, MEN_EDIT, MEN_CUT, MEN_COPY, MEN_PASTE }; + +static struct NewMenu MenuData[] = { + { NM_TITLE, "Project" , 0, 0, 0, (APTR)MEN_PROJECT }, + { NM_ITEM , "About...", "?", 0, 0, (APTR)MEN_ABOUT }, + { NM_ITEM , NM_BARLABEL, 0, 0, 0, (APTR)0 }, + { NM_ITEM , "Quit" , "Q", 0, 0, (APTR)MEN_QUIT }, + { NM_TITLE, "Edit" , 0, 0, 0, (APTR)MEN_EDIT }, + { NM_ITEM , "Cut" , "X", 0, 0, (APTR)MEN_CUT }, + { NM_ITEM , "Copy" , "C", 0, 0, (APTR)MEN_COPY }, + { NM_ITEM , "Paste" , "V", 0, 0, (APTR)MEN_PASTE }, + { NM_END , NULL , 0, 0, 0, (APTR)0 }, +}; + +/* Attach to window */ +MUIA_Window_Menustrip, MUI_MakeObject(MUIO_MenustripNM, MenuData, 0), + +/* Handle in loop */ +case MEN_ABOUT: /* show about */ break; +case MEN_QUIT: running = FALSE; break; +``` + +## Hook Function Template + +```c +SAVEDS ASM LONG MyHookFunc(REG(a2) APTR obj, REG(a1) APTR param) +{ + /* obj = object that triggered the hook */ + /* param = argument from notification */ + return 0; +} + +static struct Hook MyHook = { + { NULL, NULL }, (VOID *)MyHookFunc, NULL, NULL +}; + +/* Usage in notification */ +DoMethod(button, MUIM_Notify, MUIA_Pressed, FALSE, + app, 3, MUIM_CallHook, &MyHook, MUIV_TriggerValue); +``` + +## AppWindow Drag and Drop + +```c +/* Enable AppWindow */ +WindowObject, + MUIA_Window_Title , "Drop Zone", + MUIA_Window_ID , MAKE_ID('D','R','O','P'), + MUIA_Window_AppWindow, TRUE, + ... + End, + +/* Hook to handle dropped icons */ +SAVEDS ASM LONG AppMsgFunc(REG(a2) APTR obj, REG(a1) struct AppMessage **x) +{ + struct AppMessage *amsg = *x; + struct WBArg *ap; + int i; + static char buf[256]; + + for (ap = amsg->am_ArgList, i = 0; i < amsg->am_NumArgs; i++, ap++) + { + NameFromLock(ap->wa_Lock, buf, sizeof(buf)); + AddPart(buf, ap->wa_Name, sizeof(buf)); + DoMethod(obj, MUIM_List_InsertSingle, buf, MUIV_List_Insert_Bottom); + } + return 0; +} + +static const struct Hook AppMsgHook = { + { NULL, NULL }, (VOID *)AppMsgFunc, NULL, NULL +}; + +/* Connect notification */ +DoMethod(listview, MUIM_Notify, MUIA_AppMessage, MUIV_EveryTime, + listview, 3, MUIM_CallHook, &AppMsgHook, MUIV_TriggerValue); +``` + +## Input Loop Variants + +### Modern (Recommended) + +```c +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; + } +} +``` + +### Legacy with Return IDs + +```c +ULONG signals; +BOOL running = TRUE; +while (running) +{ + switch (DoMethod(app, MUIM_Application_Input, &signals)) + { + case MUIV_Application_ReturnID_Quit: + running = FALSE; + break; + case ID_SAVE: + /* handle save */ + break; + } + if (running && signals) + Wait(signals); +} +``` + +### Legacy with Hooks (No Return IDs) + +```c +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; + } +} +/* All actions handled via MUIM_CallHook notifications */ +``` + +--- + +Previous: [Advanced Patterns](11-advanced-patterns.md) +Back to [MUI Bootcamp](README.md) diff --git a/09_intuition/frameworks/mui/README.md b/09_intuition/frameworks/mui/README.md new file mode 100644 index 0000000..1a418e8 --- /dev/null +++ b/09_intuition/frameworks/mui/README.md @@ -0,0 +1,1160 @@ +[← Home](../../../README.md) · [Intuition](../../README.md) · [GUI Frameworks](../README.md) + +# 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:** + +```mermaid +graph TB + subgraph "The Problem (1985–1990)" + MANUAL["Manual pixel-coordinate GUIs
break on font/resolution changes"] + CALLBACK["Callback hell
tight coupling, fragile dispatch"] + end + + subgraph "Independent Solutions (1988–1994)" + NEXT["NeXTSTEP (1988)
Interface Builder
outlet/action wiring"] + BOOPSI["AmigaOS 2.0 (1990)
BOOPSI object model
tag-based attributes"] + MUI["MUI (1993)
MUIM_Notify reactive bindings
constraint layout engine"] + QT["Qt (1992–94)
signals/slots
layout managers"] + MOTIF["Motif (1991)
XmForm constraints
widget resources"] + end + + subgraph "Modern Descendants" + COCOA["Cocoa / UIKit
Auto Layout, KVO"] + REACT["React / SwiftUI
declarative, reactive"] + QTMOD["Qt 5/6
QML, property bindings"] + CSS["CSS Flexbox / Grid
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 30–50 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: + +```c +/* 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: + +```mermaid +graph TB + subgraph "What Goes Wrong with Fixed Layout" + FONT["User changes system font
Topaz 8 → Helvetica 11"] + LOCALE["German locale
'Name' → 'Benutzername'"] + RES["Different screen resolution
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: + +```c +/* 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 + +```mermaid +graph LR + subgraph "GadTools (Fixed)" + G_CREATE["CreateGadget()
x=120, y=24, w=200, h=14"] + G_NEXT["ng.TopEdge += 20"] + G_DRAW["Draw labels manually
IntuiText at fixed pos"] + G_RESIZE["Window not resizable
WFLG_SIZEGADGET absent"] + G_REFRESH["App handles REFRESHWINDOW
BeginRefresh/EndRefresh"] + end + + subgraph "MUI (Dynamic)" + M_CREATE["VGroup / ColGroup
Children declared, no coords"] + M_NEXT["Child, next object
auto-positioned"] + M_DRAW["Labels are objects
auto-measured, auto-aligned"] + M_RESIZE["Window freely resizable
layout recalculates"] + M_REFRESH["MUI handles all refresh
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: + +```c +/* 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**: + +```c +/* 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: + +```c +/* 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 + +```c +/* 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 + +```c +/* 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/.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 (~2–5× per widget) | +| **Startup time** | Loading and initialising 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 + +```mermaid +graph TB + subgraph "Application Code" + APP_CODE["Your Program
MUI_NewObject / DoMethod calls"] + end + + subgraph "MUI Framework" + MUIMASTER["muimaster.library
Class registry, object lifecycle"] + PREFS["MUI Preferences
Global + per-app appearance"] + LAYOUT["Layout Engine
Min/max/def sizing, font scaling"] + NOTIFY["Notification System
MUIM_Notify inter-object events"] + end + + subgraph "BOOPSI Layer" + BOOPSI["BOOPSI Dispatcher
OM_NEW, OM_SET, OM_GET
rootclass → MUI classes"] + end + + subgraph "Intuition / Graphics" + INT["intuition.library
Screens, Windows"] + GFX["graphics.library
RastPort, Layers"] + end + + subgraph "Hardware" + CUSTOM["Custom Chips
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 + +```mermaid +graph TB + ROOT["rootclass
(BOOPSI root)"] + + NOTIFY["Notify
Attribute change notifications
MUIM_Notify, MUIM_Set"] + + FAMILY["Family
Parent-child relationships"] + + AREA["Area
Base visual class
Sizing, drawing, input"] + + GROUP["Group
Layout container
Horiz / Vert / 2D"] + RECT["Rectangle
Spacer / separator"] + IMAGE["Image
Image display"] + TEXT["Text
Text display + formatting"] + STRING["String
Text input field"] + LIST["List
Scrollable item list"] + PROP["Prop / Scrollbar
Value slider"] + CYCLE["Cycle
Popup selection"] + SLIDER["Slider
Numeric range"] + RADIO["Radio
Exclusive choice"] + GAUGE["Gauge
Progress bar"] + SCALE["Scale
Percentage display"] + NLIST["NList
Multi-column list (3rd party)"] + BALANCE["Balance
Drag divider"] + + WINDOW["Window
Intuition window wrapper"] + APP["Application
Program root object"] + ABOUTMUI["Aboutmui
Standard About MUI dialog"] + + MENU["Menustrip
Menu hierarchy"] + MENUOBJ["Menu
Menu title"] + MENUITEM["Menuitem
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: + +```c +#include + +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 + +```mermaid +graph TB + APPLICATION["Application
'MyApp'"] + WINDOW["Window
'Main Window'"] + VGROUP["VGroup
(vertical layout)"] + TEXT["Text
'Hello, MUI!'"] + HGROUP["HGroup
(horizontal layout)"] + BT_OK["Button
'OK'"] + BT_CANCEL["Button
'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 + +```mermaid +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
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: + +```c +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 | Behaviour | +|---|---|---| +| 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**: + +```mermaid +graph LR + subgraph "Declarative" + CLOSE["Window.CloseRequest
changes to TRUE"] + QUIT["Application.ReturnID
receives Quit"] + end + + subgraph "Traditional (without MUI)" + IDCMP["GetMsg(UserPort)"] + SWITCH["switch(msg->Class)"] + HANDLER["case CLOSEWINDOW:
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 + +```c +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 + +```c +/* 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: + +```c +/* 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 + +```mermaid +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
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 + +```c +/* 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: + { + /* Initialise 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 + +```mermaid +graph TB + METHOD["Incoming Method
(OM_NEW, MUIM_Draw, etc.)"] + DISPATCH["MySlider_Dispatcher()"] + KNOWN{"Method
recognized?"} + HANDLE["Handle locally
(custom logic)"] + SUPER["DoSuperMethodA()
(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/.mcp` | +| **Per-application** | Single application | `ENV:MUI/.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 + +```c +#include +#include +#include +#include + +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+ | + +--- + +## Popular Third-Party MUI Classes (MCCs) + +| 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](01-introduction.md) | BOOPSI foundation, Intuition relationship, version history | +| 2 | [Architecture](02-architecture.md) | Full class tree from `libraries/mui.h`, object model | +| 3 | [Getting Started](03-getting-started.md) | Compiler setup, includes, Hello MUI, `demo.h` pattern | +| 4 | [Core Concepts](04-core-concepts.md) | Naming conventions (MUIC/MUIM/MUIA), I/S/G attributes, lifecycle | +| 5 | [Layout System](05-layout-system.md) | Groups, spacing, frames, custom layout hooks, virtual groups | +| 6 | [Widgets Overview](06-widgets-overview.md) | Text, String, Button, List, Slider, Cycle, Radio, Gauge, Popup | +| 7 | [Windows & Applications](07-windows-and-applications.md) | Application object, SubWindow, AppWindows, iconification | +| 8 | [Menus](08-menus.md) | GadTools NewMenu bridge, dynamic menus, radio/checkmark items | +| 9 | [Custom Classes](09-custom-classes.md) | Instance data, dispatchers, AskMinMax, Draw, rendering macros | +| 10 | [Events & Notifications](10-events-and-notifications.md) | Input loops, Return IDs, hooks, register conventions | +| 11 | [Advanced Patterns](11-advanced-patterns.md) | Drag & drop, subtasks, settings persistence, BOOPSI integration | +| 12 | [Reference Snippets](12-reference-snippets.md) | Copy-paste templates for every common pattern | + +*Source material: Official MUI 3.8 developer release — 66 autodocs, 22 C examples, `libraries/mui.h`*