docs: expand 7 Tier 3 articles to Deep quality

- gcc_amiga.md: 101→606 lines — pipeline, Docker, platform builds, flags, antipatterns, FAQ
- trackdisk.md: 178→428 lines — MFM encoding, 16-command reference, antipatterns, FPGA impact
- console.md: 244→470 lines — decision guide, TUI/progress cookbooks, antipatterns, pitfalls
- layers.md: 224→739 lines — ClipRect engine, LVO API, backfill hooks, 4 antipatterns, optimization
- text_fonts.md: 215→708 lines — ColorFont, Compugraphic outlines, 3 cookbooks, 4 antipatterns
- windows.md: 370→778 lines — 5 antipatterns, decision guide, 3 cookbooks, modern analogies
- menus.md: 378→695 lines — render chain diagram, 5 antipatterns, lifecycle cookbook, 6 FAQ
This commit is contained in:
Ilia Sharin 2026-05-12 22:04:16 -04:00
parent ab88118dc1
commit 9f5d9de1ed
8 changed files with 3129 additions and 209 deletions

View file

@ -367,12 +367,329 @@ Intuition automatically intercepts Right-Amiga+key combinations matching menu sh
6. **Disable items** with `OffMenu()` when they don't apply — don't hide them
7. **Clean up in order**: `ClearMenuStrip()``CloseWindow()``FreeMenus()``FreeVisualInfo()`
8. **Use `GTMN_NewLookMenus, TRUE`** for the modern 3D menu appearance (OS 3.0+)
9. **Keep menus shallow** — max 3 levels (menu → item → sub-item); deeper nesting confuses users
10. **Disable rather than remove** — users learn where commands live; removing them breaks spatial memory
11. **Use keyboard shortcuts for the top 10 commands** — the rest don't need them
12. **Never modify menu structs while attached** — always `ClearMenuStrip()` first
---
## Menu Render Chain
When the user presses the right mouse button, Intuition renders the menu system through a specific pipeline:
```mermaid
sequenceDiagram
participant User
participant Intuition
participant MenuStrip
participant RastPort
participant Layer
Note over User,Layer: Right mouse button pressed
User->>Intuition: RMB down event
Intuition->>Intuition: Freeze window input (menu mode)
Intuition->>Layer: Lock screen layers
Intuition->>RastPort: Render menu strip across screen top
Note over RastPort: All menu titles rendered in title bar area
User->>Intuition: Mouse moves to "Edit" title
Intuition->>RastPort: Highlight "Edit" title
Intuition->>RastPort: Render dropdown box with Edit items
Note over RastPort: Items drawn with checkmarks, shortcuts, separators
User->>Intuition: Mouse moves to "Copy" item
Intuition->>RastPort: Highlight "Copy" (invert/complement)
Note over User,Layer: Right mouse button released
User->>Intuition: RMB up event
Intuition->>RastPort: Erase all menu rendering
Intuition->>Layer: Unlock screen layers
Intuition->>Intuition: Send IDCMP_MENUPICK to window
Note over Intuition: Code = packed menu/item/sub numbers
```
### Render Details
| Phase | What Intuition Does | Memory Impact |
|-------|--------------------|---------------|
| **Strip rendering** | Draws all menu titles across the screen title bar | Backing store saved for Smart Refresh windows obscured by strip |
| **Dropdown** | Draws a filled rectangle with items inside | Obscures part of the window beneath |
| **Highlight** | `HIGHCOMP` = XOR complement, `HIGHBOX` = draw rectangle | Non-destructive — can be undone by same operation |
| **Cleanup** | Restores all obscured content from backing store | Smart refresh handles this automatically |
> [!NOTE]
> While the menu is active, Intuition **freezes input** to the window. No `IDCMP_RAWKEY`, `IDCMP_MOUSEBUTTONS`, or gadget events are delivered until the menu closes. The application cannot draw during menu mode because Intuition holds the layer lock.
---
## Named Antipatterns
### "The Multi-Select Ghost" — Ignoring NextSelect
```c
/* BAD: Only processes the first item selected.
If the user multi-selects (hold RMB, click several items),
only the first one is handled — the rest silently vanish. */
case IDCMP_MENUPICK:
{
struct MenuItem *item = ItemAddress(menuStrip, code);
HandleCommand(GTMENUITEM_USERDATA(item));
break; /* BUG: drops all subsequent selections */
}
```
```c
/* CORRECT: Walk the NextSelect chain */
case IDCMP_MENUPICK:
{
UWORD menuCode = code;
while (menuCode != MENUNULL)
{
struct MenuItem *item = ItemAddress(menuStrip, menuCode);
HandleCommand(GTMENUITEM_USERDATA(item));
menuCode = item->NextSelect;
}
break;
}
```
### "The Stale Menu" — Modifying Items While Attached
```c
/* BAD: Modifying MenuItem flags while the menu strip is active.
If the user opens the menu at exactly this moment, Intuition
reads partially-modified state — corrupted rendering or crash. */
struct MenuItem *item = ItemAddress(menuStrip, FULLMENUNUM(0, 2, NOSUB));
item->Flags |= CHECKED; /* RACE CONDITION */
```
```c
/* CORRECT: Detach, modify, reattach */
ClearMenuStrip(win);
struct MenuItem *item = ItemAddress(menuStrip, FULLMENUNUM(0, 2, NOSUB));
item->Flags |= CHECKED;
SetMenuStrip(win, menuStrip);
```
> [!TIP]
> For simple enable/disable or check/uncheck, use `OnMenu()`/`OffMenu()` instead — these are safe to call while the menu is attached.
### "The Cleanup Reversal" — Wrong Free Order
```c
/* BAD: Freeing menus before clearing from window — Intuition
may access freed memory on the next menu open attempt. */
FreeMenus(menuStrip); /* freed while still attached */
ClearMenuStrip(win); /* too late — dangling pointer */
CloseWindow(win);
```
```c
/* CORRECT: ClearMenuStrip → CloseWindow → FreeMenus */
ClearMenuStrip(win);
CloseWindow(win);
FreeMenus(menuStrip); /* now safe — no window references it */
FreeVisualInfo(vi);
UnlockPubScreen(NULL, scr);
```
### "The Shortcut Collision" — Duplicate CommKey Letters
```c
/* BAD: Two items in the same menu share shortcut "S".
Only the first match fires — "Save As" is unreachable via keyboard. */
{ NM_ITEM, "Save", "S", 0, 0, (APTR)MENU_SAVE },
{ NM_ITEM, "Save As...", "S", 0, 0, (APTR)MENU_SAVEAS },
```
```c
/* CORRECT: Each shortcut must be unique within its menu */
{ NM_ITEM, "Save", "S", 0, 0, (APTR)MENU_SAVE },
{ NM_ITEM, "Save As...", "A", 0, 0, (APTR)MENU_SAVEAS },
```
### "The Phantom VisualInfo" — Wrong Screen
```c
/* BAD: Getting VisualInfo from one screen, opening window on another.
LayoutMenus calculates positions for the wrong font/resolution. */
struct Screen *scr1 = LockPubScreen(NULL);
APTR vi = GetVisualInfo(scr1, TAG_DONE);
/* ... later, open window on a DIFFERENT screen ... */
struct Screen *scr2 = /* custom screen */;
struct Window *win = OpenWindowTags(NULL, WA_CustomScreen, scr2, TAG_DONE);
SetMenuStrip(win, menuStrip); /* WRONG VI — menu positions are for scr1 */
```
```c
/* CORRECT: Get VisualInfo from the SAME screen the window uses */
struct Screen *scr = /* the screen your window is on */;
APTR vi = GetVisualInfo(scr, TAG_DONE);
```
---
## Practical Cookbook: Complete Menu Lifecycle
```c
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/gadtools.h>
#include <libraries/gadtools.h>
enum { MENU_QUIT = 1, MENU_NEW, MENU_OPEN, MENU_CUT, MENU_COPY, MENU_PASTE };
struct NewMenu menuDef[] = {
{ NM_TITLE, "Project", NULL, 0, 0, NULL },
{ NM_ITEM, "New", "N", 0, 0, (APTR)MENU_NEW },
{ NM_ITEM, "Open...", "O", 0, 0, (APTR)MENU_OPEN },
{ NM_ITEM, NM_BARLABEL, NULL, 0, 0, NULL },
{ NM_ITEM, "Quit", "Q", 0, 0, (APTR)MENU_QUIT },
{ NM_TITLE, "Edit", NULL, 0, 0, NULL },
{ NM_ITEM, "Cut", "X", 0, 0, (APTR)MENU_CUT },
{ NM_ITEM, "Copy", "C", 0, 0, (APTR)MENU_COPY },
{ NM_ITEM, "Paste", "V", 0, 0, (APTR)MENU_PASTE },
{ NM_END, NULL, NULL, 0, 0, NULL }
};
struct Menu *CreateAndAttachMenus(struct Window *win)
{
struct Screen *scr = win->WScreen;
APTR vi = GetVisualInfo(scr, TAG_DONE);
if (!vi) return NULL;
struct Menu *menuStrip = CreateMenus(menuDef, TAG_DONE);
if (!menuStrip) { FreeVisualInfo(vi); return NULL; }
if (!LayoutMenus(menuStrip, vi,
GTMN_NewLookMenus, TRUE,
TAG_DONE))
{
FreeMenus(menuStrip);
FreeVisualInfo(vi);
return NULL;
}
SetMenuStrip(win, menuStrip);
FreeVisualInfo(vi); /* safe to free after LayoutMenus + SetMenuStrip */
return menuStrip;
}
void ProcessMenuPick(struct Window *win, struct Menu *menuStrip, UWORD code)
{
while (code != MENUNULL)
{
struct MenuItem *item = ItemAddress(menuStrip, code);
ULONG cmd = (ULONG)GTMENUITEM_USERDATA(item);
switch (cmd)
{
case MENU_NEW: NewDocument(); break;
case MENU_OPEN: OpenDocument(); break;
case MENU_CUT: CutSelection(); break;
case MENU_COPY: CopySelection(); break;
case MENU_PASTE: PasteClipboard(); break;
case MENU_QUIT: /* signal main loop to exit */ break;
}
code = item->NextSelect;
}
}
void CleanupMenus(struct Window *win, struct Menu *menuStrip)
{
ClearMenuStrip(win);
FreeMenus(menuStrip);
}
```
---
## Historical Context & Modern Analogies
### Competitive Landscape
| Platform | Menu System | Keyboard Shortcuts | Dynamic Modification | Sub-Menus | Notes |
|----------|------------|-------------------|---------------------|-----------|-------|
| **AmigaOS Intuition** | Right-click pull-down | Right-Amiga+key | `OnMenu()`/`OffMenu()`, `SetMenuStrip()` | 1 level only | Multi-select is unique — no other platform supports it |
| **Mac OS (Classic)** | Click-and-hold in menu bar | Command+key | `EnableMenuItem()`/`DisableMenuItem()` | Cascading | First platform with standard menu bar |
| **Windows 3.x** | Click on menu bar | Alt+letter, Ctrl+key | `EnableMenuItem()` | Cascading | Menu bar in each window |
| **Atari ST GEM** | Click on menu bar | Alt+key | Limited | No | Very basic — no checkmarks, no radio groups |
| **X11/Motif** | Click on menu bar | Alt+key (motif) | `XtSetSensitive()` | Cascading | Server-side rendering |
The Amiga's **right-click-to-activate** menu system was unique. Every other platform used a left-click menu bar. The right-click approach meant menus never interfered with left-click window operations, but it confused users coming from Mac/Windows.
**Multi-select** (holding right button while clicking multiple items) was an Amiga-exclusive feature. No other platform supported selecting Cut, Copy, and Paste in a single menu interaction.
### Modern Analogies
| Amiga Concept | Modern Equivalent | Notes |
|--------------|-------------------|-------|
| Right-click menu activation | Right-click context menu (all platforms) | Amiga: right-click = global menu bar; Modern: right-click = context menu |
| `struct NewMenu[]` array | XUL `<menubar>` / GTK `GtkUIManager` / Qt `QMenuBar` | Declarative menu definition |
| `CreateMenus()` + `LayoutMenus()` | `gtk_menu_bar_new_from_model()` / Qt Designer | Build + layout in one step |
| `SetMenuStrip()` | `gtk_application_set_menubar()` / macOS `NSMenu` | Attach to window |
| `GTMENUITEM_USERDATA()` | `gtk_buildable_get_name()` / Qt `QObject::property()` | User data for dispatch |
| `IDCMP_MENUPICK` | `activate` signal (GTK) / `triggered` (Qt) / `@IBAction` (macOS) | Selection event |
| `MENUNUM/ITEMNUM/SUBNUM` | None — modern APIs pass the menu item object directly | Amiga packs 3 levels into one UWORD |
| `NextSelect` chain | None — modern menus fire one event per selection | Amiga's multi-select is unique |
| `OnMenu()`/`OffMenu()` | `gtk_widget_set_sensitive()` / `NSMenuItem.enabled` | Enable/disable |
| `NM_BARLABEL` separator | GTK `gtk_separator_menu_item_new()` / Qt `addSeparator()` | Visual divider |
---
## Use Cases
| Application | Menu Structure | Notable Features |
|-------------|---------------|-----------------|
| **Workbench** | System (disk operations), Special (snapshot, cleanup) | Backdrop menu — menus available from Workbench screen |
| **Word processors (Final Writer, WordWorth)** | Project, Edit, Format, Tools, Print | Mutual exclusion for font size, toggle for bold/italic |
| **Image editors (Deluxe Paint)** | Picture, Brush, Mode,Prefs | Dynamic menus — mode options change with tool |
| **Terminal emulators** | Connect, Edit, Settings | Menu-only commands — no toolbar |
| **IDE / dev tools (SAS/C, DevPac)** | Project, Edit, Search, Run, Debug, Options | Extensive menus with many disabled states |
| **Games with GUI frontends** | Game, Options, Help | Simple 2-3 menu strip; menus disabled during gameplay |
| **File managers (Directory Opus)** | File, Edit, View, Tools, Settings | Heavily customized menus with gadget-driven alternatives |
---
## FAQ
**Q: Can I have more than 31 menus?**
A: No — the menu number is packed into 5 bits (031). In practice, no application needs more than 1012 top-level menus. The Mac's Human Interface Guidelines recommend 57.
**Q: Can menus use custom fonts?**
A: No — Intuition renders menus using the screen's default font (set via Font Preferences). GadTools' `LayoutMenus()` uses the screen's `Font` field for sizing. There is no per-menu font override.
**Q: What happens if the user opens a menu while I'm drawing?**
A: Intuition freezes input and locks the screen's layers during menu mode. Your drawing calls will block until the menu interaction completes (user releases right mouse button). This is why long drawing operations should be done in a separate task.
**Q: Can I add images/icons to menu items?**
A: Yes — by manually constructing `struct MenuItem` with `ItemFill` pointing to a `struct Image`. GadTools' `CreateMenus()` does not support image items. This is rarely done in practice.
**Q: How do I detect keyboard shortcuts in my event loop?**
A: Intuition automatically handles Right-Amiga+key combinations matching `nm_CommKey` and generates `IDCMP_MENUPICK`. You do NOT need to handle them in `IDCMP_RAWKEY` — in fact, Intuition intercepts those key combinations before your window sees them.
**Q: What is `NOSUB` in `FULLMENUNUM`?**
A: `NOSUB` (value 0) indicates that the item has no sub-item. Use it when constructing menu numbers for `OnMenu()`/`OffMenu()` on regular (non-sub) items.
---
## References
- NDK 3.9: `intuition/intuition.h`, `libraries/gadtools.h`
### NDK Headers
- `intuition/intuition.h``struct Menu`, `struct MenuItem`, `MENUNUM/ITEMNUM/SUBNUM` macros
- `libraries/gadtools.h``struct NewMenu`, `NM_*` types/flags, `GTMENUITEM_USERDATA()`
### Autodocs
- ADCD 2.1: `CreateMenus()`, `LayoutMenus()`, `SetMenuStrip()`, `ClearMenuStrip()`, `FreeMenus()`
- AmigaOS Reference Manual (RKRM): Libraries, Chapter 6 — Menus
- See also: [IDCMP](idcmp.md), [Windows](windows.md), [GadTools (Gadgets)](gadgets.md)
- ADCD 2.1: `ItemAddress()`, `OnMenu()`, `OffMenu()`, `GetVisualInfoA()`, `FreeVisualInfo()`
### Related Knowledge Base Articles
- [IDCMP](idcmp.md) — `IDCMP_MENUPICK` event delivery
- [Windows](windows.md) — windows host menu strips
- [Gadgets](gadgets.md) — GadTools creates both gadgets and menus
- [Screens](screens.md) — menu bar renders in the screen title area
- [Intuition Base](intuition_base.md) — Intuition's global state

View file

@ -359,12 +359,420 @@ If you cache a screen pointer and it becomes invalid, `OpenWindowTags()` will cr
6. **Drain IDCMP messages** before calling `CloseWindow()`
7. **Show a busy pointer** during long operations — it prevents user confusion
8. **Use `WA_AutoAdjust, TRUE`** to handle cases where the window doesn't fit the screen
9. **Use `WA_NewLookMenus, TRUE`** on OS 3.0+ for consistent 3D appearance
10. **Never modify `struct Window` fields directly** — use the API functions
11. **Lock the screen before opening a window**`LockPubScreen(NULL)` prevents the screen from closing mid-open
12. **Handle `IDCMP_CLOSEWINDOW` immediately** — don't defer shutdown if user expectations are involved
---
## Named Antipatterns
### "The Border Collision" — Drawing at (0,0) in a Normal Window
```c
/* BAD: (0,0) is the top-left corner of the BORDER, not the content area.
This overwrites the title bar and close gadget. */
Move(win->RPort, 0, 0);
RectFill(win->RPort, 0, 0, 100, 50);
```
```c
/* CORRECT: Offset all drawing by the border widths */
WORD x0 = win->BorderLeft;
WORD y0 = win->BorderTop;
Move(win->RPort, x0, y0);
RectFill(win->RPort, x0, y0, x0 + 100, y0 + 50);
```
> [!TIP]
> If you find yourself adding border offsets everywhere, consider `WFLG_GIMMEZEROZERO` — the content RastPort starts at (0,0). But it costs an extra layer.
### "The Unresponsive Close" — Ignoring IDCMP_CLOSEWINDOW
```c
/* BAD: No handler for IDCMP_CLOSEWINDOW — clicking close does nothing.
The user thinks the application is frozen. */
ULONG signals = Wait(1L << win->UserPort->mp_SigBit);
struct IntuiMessage *msg;
while ((msg = (struct IntuiMessage *)GetMsg(win->UserPort)))
{
/* Handle gadgets, keys, but NO close event... */
ReplyMsg((struct Message *)msg);
}
/* Window never closes. User is stuck. */
```
```c
/* CORRECT: Always handle IDCMP_CLOSEWINDOW */
while ((msg = (struct IntuiMessage *)GetMsg(win->UserPort)))
{
ULONG class = msg->Class;
ReplyMsg((struct Message *)msg); /* reply FIRST */
switch (class)
{
case IDCMP_CLOSEWINDOW:
running = FALSE; /* exit the event loop */
break;
case IDCMP_GADGETUP:
/* ... handle gadget ... */
break;
}
}
```
### "The Leaked Message" — Accessing IntuiMessage After ReplyMsg
```c
/* BAD: Reading fields from the message AFTER replying.
ReplyMsg() may free the message immediately — the pointer is stale. */
struct IntuiMessage *msg = (struct IntuiMessage *)GetMsg(win->UserPort);
ReplyMsg((struct Message *)msg); /* may invalidate msg */
UWORD code = msg->Code; /* UNDEFINED BEHAVIOR — msg may be freed */
APTR iaddr = msg->IAddress; /* ALSO DANGEROUS */
```
```c
/* CORRECT: Copy what you need BEFORE replying */
struct IntuiMessage *msg = (struct IntuiMessage *)GetMsg(win->UserPort);
ULONG class = msg->Class;
UWORD code = msg->Code;
APTR iaddr = msg->IAddress;
WORD mouseX = msg->MouseX;
WORD mouseY = msg->MouseY;
ReplyMsg((struct Message *)msg); /* NOW it's safe to reply */
/* Use the saved copies */
```
### "The Refresh Loop" — Drawing Outside BeginRefresh/EndRefresh
```c
/* BAD: Drawing on every REFRESHWINDOW without BeginRefresh().
Drawing into the full layer (not just damaged areas) causes NEW damage,
which triggers another REFRESHWINDOW, which draws again... infinite loop. */
case IDCMP_REFRESHWINDOW:
RedrawEverything(win); /* redraws entire window, causes new damage */
break;
```
```c
/* CORRECT: Scope the redraw to damaged regions only */
case IDCMP_REFRESHWINDOW:
BeginRefresh(win);
RedrawEverything(win); /* ClipRects restricted to damaged areas */
EndRefresh(win, TRUE); /* TRUE = fully repaired, clear damage list */
break;
```
### "The Phantom Window" — Using a Screen Pointer After It Closes
```c
/* BAD: Caching a screen pointer between operations.
Another application can close the screen between your cache and use. */
struct Screen *screen = LockPubScreen(NULL);
UnlockPubScreen(NULL, screen);
/* ... later ... */
OpenWindowTags(NULL, WA_PubScreen, screen, TAG_DONE); /* screen may be gone! */
```
```c
/* CORRECT: Lock, open, unlock — all in sequence */
struct Screen *screen = LockPubScreen(NULL);
if (screen) {
struct Window *win = OpenWindowTags(NULL,
WA_PubScreen, screen,
WA_InnerWidth, 400,
WA_InnerHeight, 300,
TAG_DONE);
UnlockPubScreen(NULL, screen);
/* win is now valid — Intuition holds its own reference to the screen */
}
```
---
## Window Type Decision Guide
```mermaid
flowchart TD
Q{"What do you need?"} -->|"Standard app window"| STD["Standard Window\nClose + Drag + Depth + Size"]
Q -->|"Background / desktop"| BD["Backdrop Window\nWFLG_BACKDROP | WFLG_BORDERLESS"]
Q -->|"Fullscreen overlay"| BL["Borderless Window\nWA_Borderless + WA_InnerWidth/Height"]
Q -->|"Simple content,\nborder offset headaches"| GZZ["GimmeZeroZero\nContent RastPort at (0,0)"]
Q -->|"Large scrollable canvas"| SUPER["SuperBitMap\nWA_SuperBitMap + BitMap"]
STD --> REF{"Refresh mode?"}
REF -->|"Most apps"| SMART["Smart Refresh\n(WA_SmartRefresh)"]
REF -->|"Memory-critical"| SIMPLE["Simple Refresh\n(WA_SimpleRefresh)"]
REF -->|"Scrollable canvas"| SUPER
style STD fill:#c8e6c9,stroke:#2e7d32,color:#333
style BD fill:#e8f4fd,stroke:#2196f3,color:#333
style BL fill:#fff9c4,stroke:#f9a825,color:#333
style GZZ fill:#fff9c4,stroke:#f9a825,color:#333
style SMART fill:#c8e6c9,stroke:#2e7d32,color:#333
```
---
## Practical Cookbooks
### Cookbook: Resizable Window with Proper Relayout
```c
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <intuition/intuition.h>
void RunResizableWindow(void)
{
struct Screen *screen = LockPubScreen(NULL);
if (!screen) return;
struct Window *win = OpenWindowTags(NULL,
WA_PubScreen, screen,
WA_InnerWidth, 400,
WA_InnerHeight, 300,
WA_Title, "Resizable",
WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_NEWSIZE |
IDCMP_REFRESHWINDOW,
WA_Flags, WFLG_CLOSEGADGET | WFLG_DRAGBAR |
WFLG_DEPTHGADGET | WFLG_SIZEGADGET |
WFLG_SMART_REFRESH | WFLG_ACTIVATE,
WA_MinWidth, 200,
WA_MinHeight, 100,
WA_MaxWidth, -1,
WA_MaxHeight, -1,
TAG_DONE);
UnlockPubScreen(NULL, screen);
if (!win) return;
BOOL running = TRUE;
while (running)
{
ULONG sigs = Wait(1L << win->UserPort->mp_SigBit);
struct IntuiMessage *msg;
while ((msg = (struct IntuiMessage *)GetMsg(win->UserPort)))
{
ULONG class = msg->Class;
ReplyMsg((struct Message *)msg);
switch (class)
{
case IDCMP_CLOSEWINDOW:
running = FALSE;
break;
case IDCMP_NEWSIZE:
case IDCMP_REFRESHWINDOW:
if (class == IDCMP_REFRESHWINDOW)
BeginRefresh(win);
/* Recalculate layout and redraw */
{
WORD w = win->Width - win->BorderLeft - win->BorderRight;
WORD h = win->Height - win->BorderTop - win->BorderBottom;
SetRast(win->RPort, 0); /* clear */
/* ... your layout code using w, h ... */
}
if (class == IDCMP_REFRESHWINDOW)
EndRefresh(win, TRUE);
break;
}
}
}
CloseWindow(win);
}
```
### Cookbook: Full-Screen Borderless Overlay
```c
/* Open a borderless window covering the entire screen —
useful for games, demos, or presentations */
struct Screen *scr = LockPubScreen(NULL);
struct Window *overlay = OpenWindowTags(NULL,
WA_PubScreen, scr,
WA_Left, 0,
WA_Top, 0,
WA_Width, scr->Width,
WA_Height, scr->Height,
WA_Borderless, TRUE,
WA_SimpleRefresh, TRUE,
WA_NoCareRefresh, TRUE,
WA_RMBTrap, TRUE, /* capture right-click (no menu bar) */
WA_Activate, TRUE,
WA_IDCMP, IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS |
IDCMP_MOUSEMOVE,
TAG_DONE);
UnlockPubScreen(NULL, scr);
/* Draw directly at (0,0) — no borders */
SetAPen(overlay->RPort, 1);
RectFill(overlay->RPort, 0, 0, scr->Width - 1, scr->Height - 1);
```
### Cookbook: Multi-Window Shared Message Port
```c
/* For applications with many windows, sharing one MsgPort is more
efficient than creating a port per window.
See idcmp.md for the full explanation. */
struct MsgPort *sharedPort = CreateMsgPort();
/* Window A */
struct Window *winA = OpenWindowTags(NULL,
WA_Title, "Window A",
WA_InnerWidth, 300, WA_InnerHeight, 200,
WA_Flags, WFLG_CLOSEGADGET | WFLG_DRAGBAR | WFLG_ACTIVATE,
WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_RAWKEY,
TAG_DONE);
/* Window B */
struct Window *winB = OpenWindowTags(NULL,
WA_Title, "Window B",
WA_InnerWidth, 300, WA_InnerHeight, 200,
WA_Flags, WFLG_CLOSEGADGET | WFLG_DRAGBAR,
WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_RAWKEY,
TAG_DONE);
/* Redirect both to the shared port */
winA->UserPort = sharedPort;
winB->UserPort = sharedPort;
ModifyIDCMP(winA, IDCMP_CLOSEWINDOW | IDCMP_RAWKEY);
ModifyIDCMP(winB, IDCMP_CLOSEWINDOW | IDCMP_RAWKEY);
/* Event loop processes both windows from one port */
BOOL running = TRUE;
while (running)
{
Wait(1L << sharedPort->mp_SigBit);
struct IntuiMessage *msg;
while ((msg = (struct IntuiMessage *)GetMsg(sharedPort)))
{
struct Window *src = msg->IDCMPWindow; /* which window? */
ULONG class = msg->Class;
ReplyMsg((struct Message *)msg);
if (class == IDCMP_CLOSEWINDOW)
{
if (src == winA) { CloseWindow(winA); winA = NULL; }
if (src == winB) { CloseWindow(winB); winB = NULL; }
if (!winA && !winB) running = FALSE;
}
}
}
DeleteMsgPort(sharedPort);
```
> [!WARNING]
> When closing a window with a shared port, you **must** drain its messages, detach the port, and strip pending messages. See [idcmp.md](idcmp.md#multi-window-shared-port) for the full `Forbid()` protocol.
---
## Historical Context & Modern Analogies
### Competitive Landscape
| Platform | Overlapping Windows | Clipping System | Backing Store | Notes |
|----------|---------------------|-----------------|---------------|-------|
| **AmigaOS Intuition** | Yes — layered | layers.library ClipRect | Smart Refresh | Lightweight — just a Layer + RastPort |
| **Mac OS (Classic)** | Yes — via Window Manager | Region-based | Window buffer (optional) | Heavyweight GrafPort per window |
| **Windows 1.x** | No — tiled only | Rectangle clip | None | Could not overlap |
| **Windows 2.x3.x** | Yes — overlapping | Region clip | None (app redraws) | `WM_PAINT` driven |
| **Atari ST GEM** | No — tiled (AES limits) | Rectangle only | None | Max 8 windows, no true overlap |
| **X11** | Yes — server-side | Server region clip | Backing store (optional) | Remote protocol overhead |
The Amiga was the **only consumer platform in 1985** with true overlapping windows backed by automatic damage repair (Smart Refresh). The Mac had overlapping windows but required the application to handle all redraw until System 7 (1991).
### Modern Analogies
| Amiga Concept | Modern Equivalent | Notes |
|--------------|-------------------|-------|
| `OpenWindowTags()` | `CreateWindowEx()` (Win32) / `NSWindow()` (macOS) / `gtk_window_new()` | Tag-based extensible API was ahead of its time |
| `struct Window` | `NSWindow` / `GtkWidget` / `wl_surface` | Amiga's is read-only; modern objects have methods |
| `WA_InnerWidth/Height` | `contentRect` (macOS) / `client area` (Win32) | Same concept — content area minus chrome |
| Smart Refresh | Compositor shadow buffer (Wayland) / `CA_LAYER` (macOS) | OS saves obscured content automatically |
| Simple Refresh | `WM_PAINT` (Win32) / `expose-event` (X11/GTK2) | App must redraw damaged areas |
| `IDCMP_CLOSEWINDOW` | `WM_CLOSE` (Win32) / `windowWillClose:` (macOS) | User clicked the close button |
| `IDCMP_NEWSIZE` | `WM_SIZE` (Win32) / `resize-event` (GTK) | Window dimensions changed |
| `WA_RMBTrap` | `button-press-event` right button (GTK) | Capture right-click instead of menu activation |
| `SetWindowTitles()` | `window.title =` (macOS/GTK) | Change title at runtime |
| `SetWindowPointer(WA_BusyPointer)` | `NSCursor.operationSetCursor()` / `gtk_window_set_cursor(GDK_WATCH)` | Hourglass / spinning beach ball |
The key architectural difference: Amiga windows are **immediate-mode** — you draw directly to the screen bitmap via the window's RastPort. Modern windows are **retained-mode** — you describe what to draw, and the compositor renders it off-screen, then presents it. This is why Amiga windows could be so lightweight — no compositor, no texture memory, just rectangle math.
---
## Use Cases
| Software Type | Window Style | Refresh Mode | Notes |
|--------------|-------------|--------------|-------|
| Word processor (Final Writer) | Standard + gadgets | Smart | Full gadget toolbar, resizable |
| Drawing program (Deluxe Paint) | Borderless or standard | SuperBitMap | Canvas in off-screen bitmap |
| Terminal emulator (Term) | Standard | Smart | Scrollback via ClipBlit |
| Spreadsheet (MaxiPlan) | Standard + lots of gadgets | Smart | Grid layout, cell gadgets |
| Game (Lemmings) | Full-screen borderless | Simple | Full redraw every frame anyway |
| Demo scene intro | Borderless + RMBTrap | Simple | No system gadgets, raw key input |
| Workbench background | Backdrop + borderless | Simple | Behind everything, draws desktop pattern |
| File requester (ASL) | Standard + borderless | Smart | Modal dialog pattern |
| System monitor (SysMon) | Standard, small | Smart | Periodic timer-driven redraw |
---
## FAQ
**Q: What happens if `OpenWindowTags()` returns `NULL`?**
A: Either the screen is locked by another application, the system is out of memory (Chip RAM for Smart refresh), or the requested flags are contradictory. Check `IoErr()` for the specific error.
**Q: Can I open a window without a screen?**
A: No — every window must belong to a screen. Use `WA_PubScreen, NULL` for the default (Workbench) screen, or `WA_CustomScreen, myScreen` for your own.
**Q: What's the minimum set of IDCMP flags I should request?**
A: At minimum: `IDCMP_CLOSEWINDOW` (if you have a close gadget) + `IDCMP_GADGETUP` (if you have gadgets). Never request all flags — it floods your message port with mouse move events.
**Q: How do I make a window that can't be moved?**
A: Don't set `WFLG_DRAGBAR`. The window will have no title bar drag area.
**Q: What is GimmeZeroZero good for?**
A: It creates a separate layer for borders so your content RastPort starts at (0,0) — no need to add `BorderLeft`/`BorderTop` offsets to every draw call. The cost is an extra layer (memory + blitter overhead). Useful for applications with complex drawing that don't want to track border offsets.
**Q: Can I change the refresh mode after opening?**
A: No — the refresh mode is determined at open time by the `WFLG_SMART_REFRESH` / `WFLG_SIMPLE_REFRESH` / `WFLG_SUPER_BITMAP` flags. You must close and reopen the window to change it.
**Q: What is `WA_AutoAdjust`?**
A: If the requested window dimensions don't fit on the screen, `WA_AutoAdjust, TRUE` tells Intuition to shrink or reposition the window to make it fit. Without it, `OpenWindowTags()` may fail if the window is too large.
---
## References
- NDK 3.9: `intuition/intuition.h`, `intuition/screens.h`
- ADCD 2.1: `OpenWindowTagList()`, `CloseWindow()`, `ModifyIDCMP()`
- AmigaOS Reference Manual (RKRM): Libraries, Chapter 4 — Intuition Windows
- See also: [IDCMP](idcmp.md), [Screens](screens.md), [Gadgets](gadgets.md)
### NDK Headers
- `intuition/intuition.h``struct Window`, `struct NewWindow`, `WA_*` tags, `WFLG_*` flags
- `intuition/screens.h``LockPubScreen()`, `UnlockPubScreen()`
- `intuition/intuitionbase.h` — IntuitionBase
### Autodocs
- ADCD 2.1: `OpenWindowTagList()`, `CloseWindow()`, `MoveWindow()`, `SizeWindow()`, `ChangeWindowBox()`
- ADCD 2.1: `SetWindowTitles()`, `SetWindowPointer()`, `WindowToFront()`, `WindowToBack()`
- ADCD 2.1: `ModifyIDCMP()`, `RefreshWindowFrame()`
### Related Knowledge Base Articles
- [IDCMP](idcmp.md) — event delivery mechanism, shared message port protocol
- [Screens](screens.md) — screen architecture, Copper-based display switching
- [Gadgets](gadgets.md) — button, string, slider, and custom BOOPSI gadgets
- [Menus](menus.md) — menu bar structure and event handling
- [Layers](../11_libraries/layers.md) — the clipping engine behind every window
- [RastPort](../08_graphics/rastport.md) — drawing context, the window's `RPort`
- [Requesters](requesters.md) — modal and async dialog boxes